1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.jdbc.kernel;
20
21 import java.sql.Connection;
22 import java.sql.SQLException;
23 import java.util.BitSet;
24 import java.util.Collection;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27
28 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
29 import org.apache.openjpa.jdbc.meta.ClassMapping;
30 import org.apache.openjpa.jdbc.meta.Discriminator;
31 import org.apache.openjpa.jdbc.meta.FieldMapping;
32 import org.apache.openjpa.jdbc.meta.Strategy;
33 import org.apache.openjpa.jdbc.meta.Version;
34 import org.apache.openjpa.jdbc.sql.DBDictionary;
35 import org.apache.openjpa.jdbc.sql.RowManager;
36 import org.apache.openjpa.jdbc.sql.SQLExceptions;
37 import org.apache.openjpa.kernel.OpenJPAStateManager;
38 import org.apache.openjpa.kernel.PCState;
39 import org.apache.openjpa.lib.conf.Configurable;
40 import org.apache.openjpa.lib.conf.Configuration;
41 import org.apache.openjpa.util.ImplHelper;
42 import org.apache.openjpa.util.OpenJPAException;
43 import org.apache.openjpa.util.OptimisticException;
44
45 /**
46 * Base update manager with common functionality.
47 *
48 * @author Abe White
49 */
50 public abstract class AbstractUpdateManager
51 implements UpdateManager, Configurable {
52
53 protected JDBCConfiguration conf = null;
54 protected DBDictionary dict = null;
55
56 public void setConfiguration(Configuration conf) {
57 this.conf = (JDBCConfiguration) conf;
58 dict = this.conf.getDBDictionaryInstance();
59 }
60
61 public void startConfiguration() {
62 }
63
64 public void endConfiguration() {
65 }
66
67 public Collection flush(Collection states, JDBCStore store) {
68 Connection conn = store.getConnection();
69 try {
70 PreparedStatementManager psMgr = newPreparedStatementManager(store,
71 conn);
72 return flush(states, store, psMgr);
73 } finally {
74 try { conn.close(); } catch (SQLException se) {}
75 }
76 }
77
78 private Collection flush(Collection states, JDBCStore store,
79 PreparedStatementManager psMgr) {
80 // run through all the states and update them as necessary
81 RowManager rowMgr = newRowManager();
82 Collection customs = new LinkedList();
83 Collection exceps = null;
84 for (Iterator itr = states.iterator(); itr.hasNext();)
85 exceps = populateRowManager((OpenJPAStateManager) itr.next(),
86 rowMgr, store, exceps, customs);
87
88 // flush rows
89 exceps = flush(rowMgr, psMgr, exceps);
90
91 // now do any custom mappings
92 for (Iterator itr = customs.iterator(); itr.hasNext();) {
93 try {
94 ((CustomMapping) itr.next()).execute(store);
95 } catch (SQLException se) {
96 exceps = addException(exceps, SQLExceptions.getStore(se, dict));
97 } catch (OpenJPAException ke) {
98 exceps = addException(exceps, ke);
99 }
100 }
101
102 // return all exceptions
103 Collection psExceps = psMgr.getExceptions();
104 if (exceps == null)
105 return psExceps;
106 if (psExceps == null)
107 return exceps;
108 exceps.addAll(psExceps);
109 return exceps;
110 }
111
112 /**
113 * Return a new {@link RowManager}.
114 */
115 protected abstract RowManager newRowManager();
116
117 /**
118 * Return a new {@link PreparedStatementManager}.
119 */
120 protected abstract PreparedStatementManager newPreparedStatementManager(
121 JDBCStore store, Connection conn);
122
123 /**
124 * Flush all rows of the given row manager. Add exceptions to
125 * <code>exceps</code> (which may start as null) using
126 * {@link #addException}. Return <code>exceps</code>.
127 */
128 protected abstract Collection flush(RowManager rowMgr,
129 PreparedStatementManager psMgr, Collection exceps);
130
131 /**
132 * Populate the row manager with rows to be flushed for the given state.
133 *
134 * @param exceps exceptions encountered when flushing will be added to
135 * this list and returned; the list may be null initially
136 * @param customs buffer custom mappings
137 * @return the exceptions list
138 */
139 protected Collection populateRowManager(OpenJPAStateManager sm,
140 RowManager rowMgr, JDBCStore store, Collection exceps,
141 Collection customs) {
142 try {
143 BitSet dirty;
144 if (sm.getPCState() == PCState.PNEW && !sm.isFlushed()) {
145 insert(sm, (ClassMapping) sm.getMetaData(), rowMgr, store,
146 customs);
147 } else if (sm.getPCState() == PCState.PNEWFLUSHEDDELETED
148 || sm.getPCState() == PCState.PDELETED) {
149 delete(sm, (ClassMapping) sm.getMetaData(), rowMgr, store,
150 customs);
151 } else if ((dirty = ImplHelper.getUpdateFields(sm)) != null) {
152 update(sm, dirty, (ClassMapping) sm.getMetaData(), rowMgr,
153 store, customs);
154 } else if (sm.isVersionUpdateRequired()) {
155 updateIndicators(sm, (ClassMapping) sm.getMetaData(), rowMgr,
156 store, customs, true);
157 } else if (sm.isVersionCheckRequired()) {
158 if (!((ClassMapping) sm.getMetaData()).getVersion().
159 checkVersion(sm, store, false))
160 exceps = addException(exceps, new OptimisticException(sm.
161 getManagedInstance()));
162 }
163 } catch (SQLException se) {
164 exceps = addException(exceps, SQLExceptions.getStore(se, dict));
165 } catch (OpenJPAException ke) {
166 exceps = addException(exceps, ke);
167 }
168 return exceps;
169 }
170
171 /**
172 * Add the given exception to the given list, which may start out as null.
173 */
174 protected Collection addException(Collection exceps, Exception err) {
175 if (exceps == null)
176 exceps = new LinkedList();
177 exceps.add(err);
178 return exceps;
179 }
180
181 /**
182 * Recursive method to insert the given instance, base class first.
183 */
184 protected void insert(OpenJPAStateManager sm, ClassMapping mapping,
185 RowManager rowMgr, JDBCStore store, Collection customs)
186 throws SQLException {
187 Boolean custom = mapping.isCustomInsert(sm, store);
188 if (!Boolean.FALSE.equals(custom))
189 mapping.customInsert(sm, store);
190 if (Boolean.TRUE.equals(custom))
191 return;
192
193 ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
194 if (sup != null)
195 insert(sm, sup, rowMgr, store, customs);
196
197 mapping.insert(sm, store, rowMgr);
198 FieldMapping[] fields = mapping.getDefinedFieldMappings();
199 BitSet dirty = sm.getDirty();
200 for (int i = 0; i < fields.length; i++) {
201 if (dirty.get(fields[i].getIndex())
202 && !bufferCustomInsert(fields[i], sm, store, customs))
203 fields[i].insert(sm, store, rowMgr);
204 }
205 if (sup == null) {
206 Version vers = mapping.getVersion();
207 if (!bufferCustomInsert(vers, sm, store, customs))
208 vers.insert(sm, store, rowMgr);
209 Discriminator dsc = mapping.getDiscriminator();
210 if (!bufferCustomInsert(dsc, sm, store, customs))
211 dsc.insert(sm, store, rowMgr);
212 }
213 }
214
215 /**
216 * If the given mapping uses a custom insert, places a
217 * {@link CustomMapping} struct for it in the given collection and
218 * returns true, else returns false.
219 */
220 private boolean bufferCustomInsert(Strategy strat, OpenJPAStateManager sm,
221 JDBCStore store, Collection customs) {
222 Boolean custom = strat.isCustomInsert(sm, store);
223 if (!Boolean.FALSE.equals(custom))
224 customs.add(new CustomMapping(CustomMapping.INSERT, sm, strat));
225 return Boolean.TRUE.equals(custom);
226 }
227
228 /**
229 * Recursive method to delete the given instance, base class last.
230 */
231 protected void delete(OpenJPAStateManager sm, ClassMapping mapping,
232 RowManager rowMgr, JDBCStore store, Collection customs)
233 throws SQLException {
234 Boolean custom = mapping.isCustomDelete(sm, store);
235 if (!Boolean.FALSE.equals(custom))
236 mapping.customDelete(sm, store);
237 if (Boolean.TRUE.equals(custom))
238 return;
239
240 FieldMapping[] fields = mapping.getDefinedFieldMappings();
241 for (int i = 0; i < fields.length; i++)
242 if (!bufferCustomDelete(fields[i], sm, store, customs))
243 fields[i].delete(sm, store, rowMgr);
244
245 ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
246 if (sup == null) {
247 Version vers = mapping.getVersion();
248 if (!bufferCustomDelete(vers, sm, store, customs))
249 vers.delete(sm, store, rowMgr);
250 Discriminator dsc = mapping.getDiscriminator();
251 if (!bufferCustomDelete(dsc, sm, store, customs))
252 dsc.delete(sm, store, rowMgr);
253 }
254 mapping.delete(sm, store, rowMgr);
255
256 if (sup != null)
257 delete(sm, sup, rowMgr, store, customs);
258 }
259
260 /**
261 * @see #bufferCustomInsert
262 */
263 private boolean bufferCustomDelete(Strategy strat, OpenJPAStateManager sm,
264 JDBCStore store, Collection customs) {
265 Boolean custom = strat.isCustomDelete(sm, store);
266 if (!Boolean.FALSE.equals(custom))
267 customs.add(new CustomMapping(CustomMapping.DELETE, sm, strat));
268 return Boolean.TRUE.equals(custom);
269 }
270
271 /**
272 * Recursive method to update the given instance.
273 */
274 protected void update(OpenJPAStateManager sm, BitSet dirty,
275 ClassMapping mapping, RowManager rowMgr, JDBCStore store,
276 Collection customs) throws SQLException {
277 Boolean custom = mapping.isCustomUpdate(sm, store);
278 if (!Boolean.FALSE.equals(custom))
279 mapping.customUpdate(sm, store);
280 if (Boolean.TRUE.equals(custom))
281 return;
282
283 // update all fields before all mappings so that the mappings can
284 // detect whether any fields in their rows have been modified
285 FieldMapping[] fields = mapping.getDefinedFieldMappings();
286 for (int i = 0; i < fields.length; i++) {
287 if (dirty.get(fields[i].getIndex())
288 && !bufferCustomUpdate(fields[i], sm, store, customs))
289 fields[i].update(sm, store, rowMgr);
290 }
291
292 ClassMapping sup = mapping.getJoinablePCSuperclassMapping();
293 if (sup == null)
294 updateIndicators(sm, mapping, rowMgr, store, customs, false);
295 else
296 update(sm, dirty, sup, rowMgr, store, customs);
297 mapping.update(sm, store, rowMgr);
298 }
299
300 /**
301 * Update version and discriminator indicators.
302 */
303 protected void updateIndicators(OpenJPAStateManager sm, ClassMapping mapping,
304 RowManager rowMgr, JDBCStore store, Collection customs,
305 boolean versionUpdateOnly) throws SQLException {
306 while (mapping.getJoinablePCSuperclassMapping() != null)
307 mapping = mapping.getJoinablePCSuperclassMapping();
308
309 Version vers = mapping.getVersion();
310 if (!bufferCustomUpdate(vers, sm, store, customs))
311 vers.update(sm, store, rowMgr);
312
313 if (versionUpdateOnly) {
314 // if we are only updating the version column, we need to add
315 // in the primary key select
316 mapping.update(sm, store, rowMgr);
317 } else {
318 // otherwise we need to make sure we update the discriminator too
319 Discriminator dsc = mapping.getDiscriminator();
320 if (!bufferCustomUpdate(dsc, sm, store, customs))
321 dsc.update(sm, store, rowMgr);
322 }
323 }
324
325 /**
326 * @see #bufferCustomInsert
327 */
328 private boolean bufferCustomUpdate(Strategy strat, OpenJPAStateManager sm,
329 JDBCStore store, Collection customs) {
330 Boolean custom = strat.isCustomUpdate(sm, store);
331 if (!Boolean.FALSE.equals(custom))
332 customs.add(new CustomMapping(CustomMapping.UPDATE, sm, strat));
333 return Boolean.TRUE.equals(custom);
334 }
335
336 /**
337 * Executes customized mapping updates.
338 */
339 protected static class CustomMapping {
340
341 public static final int INSERT = 0;
342 public static final int UPDATE = 1;
343 public static final int DELETE = 3;
344
345 private final int _action;
346 private final OpenJPAStateManager _sm;
347 private final Strategy _strat;
348
349 public CustomMapping(int action, OpenJPAStateManager sm, Strategy strat) {
350 _action = action;
351 _sm = sm;
352 _strat = strat;
353 }
354
355 public void execute(JDBCStore store) throws SQLException {
356 switch (_action) {
357 case INSERT:
358 _strat.customInsert(_sm, store);
359 break;
360 case UPDATE:
361 _strat.customUpdate(_sm, store);
362 break;
363 case DELETE:
364 _strat.customDelete(_sm, store);
365 break;
366 }
367 }
368 }
369 }