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.PreparedStatement;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.sql.Statement;
26 import java.util.ArrayList;
27 import java.util.BitSet;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.Set;
33 import javax.sql.DataSource;
34
35 import org.apache.openjpa.event.OrphanedKeyAction;
36 import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
37 import org.apache.openjpa.jdbc.meta.ClassMapping;
38 import org.apache.openjpa.jdbc.meta.Discriminator;
39 import org.apache.openjpa.jdbc.meta.FieldMapping;
40 import org.apache.openjpa.jdbc.meta.ValueMapping;
41 import org.apache.openjpa.jdbc.sql.DBDictionary;
42 import org.apache.openjpa.jdbc.sql.JoinSyntaxes;
43 import org.apache.openjpa.jdbc.sql.Joins;
44 import org.apache.openjpa.jdbc.sql.Result;
45 import org.apache.openjpa.jdbc.sql.SQLExceptions;
46 import org.apache.openjpa.jdbc.sql.SQLFactory;
47 import org.apache.openjpa.jdbc.sql.Select;
48 import org.apache.openjpa.jdbc.sql.SelectExecutor;
49 import org.apache.openjpa.jdbc.sql.Union;
50 import org.apache.openjpa.kernel.FetchConfiguration;
51 import org.apache.openjpa.kernel.LockManager;
52 import org.apache.openjpa.kernel.OpenJPAStateManager;
53 import org.apache.openjpa.kernel.PCState;
54 import org.apache.openjpa.kernel.QueryLanguages;
55 import org.apache.openjpa.kernel.Seq;
56 import org.apache.openjpa.kernel.StoreContext;
57 import org.apache.openjpa.kernel.StoreManager;
58 import org.apache.openjpa.kernel.StoreQuery;
59 import org.apache.openjpa.kernel.exps.ExpressionParser;
60 import org.apache.openjpa.lib.jdbc.DelegatingConnection;
61 import org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement;
62 import org.apache.openjpa.lib.jdbc.DelegatingStatement;
63 import org.apache.openjpa.lib.rop.MergedResultObjectProvider;
64 import org.apache.openjpa.lib.rop.ResultObjectProvider;
65 import org.apache.openjpa.lib.util.Localizer;
66 import org.apache.openjpa.meta.ClassMetaData;
67 import org.apache.openjpa.meta.FieldMetaData;
68 import org.apache.openjpa.meta.JavaTypes;
69 import org.apache.openjpa.meta.ValueStrategies;
70 import org.apache.openjpa.util.ApplicationIds;
71 import org.apache.openjpa.util.Id;
72 import org.apache.openjpa.util.ImplHelper;
73 import org.apache.openjpa.util.InvalidStateException;
74 import org.apache.openjpa.util.OpenJPAId;
75 import org.apache.openjpa.util.StoreException;
76 import org.apache.openjpa.util.UserException;
77
78 /**
79 * StoreManager plugin that uses JDBC to store persistent data in a
80 * relational data store.
81 *
82 * @author Abe White
83 * @nojavadoc
84 */
85 public class JDBCStoreManager
86 implements StoreManager, JDBCStore {
87
88 private static final Localizer _loc = Localizer.forPackage
89 (JDBCStoreManager.class);
90
91 private StoreContext _ctx = null;
92 private JDBCConfiguration _conf = null;
93 private DBDictionary _dict = null;
94 private SQLFactory _sql = null;
95 private JDBCLockManager _lm = null;
96 private DataSource _ds = null;
97 private RefCountConnection _conn = null;
98 private boolean _active = false;
99
100 // track the pending statements so we can cancel them
101 private Set _stmnts = Collections.synchronizedSet(new HashSet());
102
103 public StoreContext getContext() {
104 return _ctx;
105 }
106
107 public void setContext(StoreContext ctx) {
108 setContext(ctx, (JDBCConfiguration) ctx.getConfiguration());
109 }
110
111 public void setContext(StoreContext ctx, JDBCConfiguration conf) {
112 _ctx = ctx;
113 _conf = conf;
114 _dict = _conf.getDBDictionaryInstance();
115 _sql = _conf.getSQLFactoryInstance();
116
117 LockManager lm = ctx.getLockManager();
118 if (lm instanceof JDBCLockManager)
119 _lm = (JDBCLockManager) lm;
120
121 if (!ctx.isManaged() && _conf.isConnectionFactoryModeManaged())
122 _ds = _conf.getDataSource2(ctx);
123 else
124 _ds = _conf.getDataSource(ctx);
125
126 if (_conf.getUpdateManagerInstance().orderDirty())
127 ctx.setOrderDirtyObjects(true);
128 }
129
130 public JDBCConfiguration getConfiguration() {
131 return _conf;
132 }
133
134 public DBDictionary getDBDictionary() {
135 return _dict;
136 }
137
138 public SQLFactory getSQLFactory() {
139 return _sql;
140 }
141
142 public JDBCLockManager getLockManager() {
143 return _lm;
144 }
145
146 public JDBCFetchConfiguration getFetchConfiguration() {
147 return (JDBCFetchConfiguration) _ctx.getFetchConfiguration();
148 }
149
150 public void beginOptimistic() {
151 }
152
153 public void rollbackOptimistic() {
154 }
155
156 public void begin() {
157 _active = true;
158 try {
159 if ((!_ctx.isManaged() || !_conf.isConnectionFactoryModeManaged())
160 && _conn.getAutoCommit())
161 _conn.setAutoCommit(false);
162 } catch (SQLException se) {
163 _active = false;
164 throw SQLExceptions.getStore(se, _dict);
165 }
166 }
167
168 public void commit() {
169 try {
170 if (!_ctx.isManaged() || !_conf.isConnectionFactoryModeManaged())
171 _conn.commit();
172 } catch (SQLException se) {
173 try {
174 _conn.rollback();
175 } catch (SQLException se2) {
176 }
177 throw SQLExceptions.getStore(se, _dict);
178 } finally {
179 _active = false;
180 }
181 }
182
183 public void rollback() {
184 // already rolled back ourselves?
185 if (!_active)
186 return;
187
188 try {
189 if (_conn != null
190 && (!_ctx.isManaged() || !_conf
191 .isConnectionFactoryModeManaged()))
192 _conn.rollback();
193 } catch (SQLException se) {
194 throw SQLExceptions.getStore(se, _dict);
195 } finally {
196 _active = false;
197 }
198 }
199
200 public void retainConnection() {
201 connect(false);
202 _conn.setRetain(true);
203 }
204
205 public void releaseConnection() {
206 if (_conn != null)
207 _conn.setRetain(false);
208 }
209
210 public Object getClientConnection() {
211 return new ClientConnection(getConnection());
212 }
213
214 public Connection getConnection() {
215 connect(true);
216 return _conn;
217 }
218
219 protected DataSource getDataSource() {
220 return _ds;
221 }
222
223 public boolean exists(OpenJPAStateManager sm, Object context) {
224 // add where conditions on base class to avoid joins if subclass
225 // doesn't use oid as identifier
226 ClassMapping mapping = (ClassMapping) sm.getMetaData();
227 return exists(mapping, sm.getObjectId(), context);
228 }
229
230 private boolean exists(ClassMapping mapping, Object oid, Object context) {
231 // add where conditions on base class to avoid joins if subclass
232 // doesn't use oid as identifier
233 Select sel = _sql.newSelect();
234 while (mapping.getJoinablePCSuperclassMapping() != null)
235 mapping = mapping.getJoinablePCSuperclassMapping();
236
237 sel.wherePrimaryKey(oid, mapping, this);
238 try {
239 return sel.getCount(this) != 0;
240 } catch (SQLException se) {
241 throw SQLExceptions.getStore(se, _dict);
242 }
243 }
244
245 public boolean syncVersion(OpenJPAStateManager sm, Object context) {
246 ClassMapping mapping = (ClassMapping) sm.getMetaData();
247 try {
248 return mapping.getVersion().checkVersion(sm, this, true);
249 } catch (SQLException se) {
250 throw SQLExceptions.getStore(se, _dict);
251 }
252 }
253
254 public int compareVersion(OpenJPAStateManager state, Object v1, Object v2) {
255 ClassMapping mapping = (ClassMapping) state.getMetaData();
256 return mapping.getVersion().compareVersion(v1, v2);
257 }
258
259 public boolean initialize(OpenJPAStateManager sm, PCState state,
260 FetchConfiguration fetch, Object context) {
261 ConnectionInfo info = (ConnectionInfo) context;
262 try {
263 return initializeState(sm, state, (JDBCFetchConfiguration) fetch,
264 info);
265 } catch (ClassNotFoundException cnfe) {
266 throw new UserException(cnfe);
267 } catch (SQLException se) {
268 throw SQLExceptions.getStore(se, _dict);
269 }
270 }
271
272 /**
273 * Initialize a newly-loaded instance.
274 */
275 protected boolean initializeState(OpenJPAStateManager sm, PCState state,
276 JDBCFetchConfiguration fetch, ConnectionInfo info)
277 throws ClassNotFoundException, SQLException {
278 Object oid = sm.getObjectId();
279 ClassMapping mapping = (ClassMapping) sm.getMetaData();
280 Result res = null;
281 try {
282 if (info != null && info.result != null) {
283 res = info.result;
284 info.sm = sm;
285 if (info.mapping == null)
286 info.mapping = mapping;
287 mapping = info.mapping;
288 } else if (oid instanceof OpenJPAId
289 && !((OpenJPAId) oid).hasSubclasses()) {
290 Boolean custom = customLoad(sm, mapping, state, fetch);
291 if (custom != null)
292 return custom.booleanValue();
293 res = getInitializeStateResult(sm, mapping, fetch,
294 Select.SUBS_EXACT);
295 if (res == null && !selectPrimaryKey(sm, mapping, fetch))
296 return false;
297 if (isEmptyResult(res))
298 return false;
299 } else {
300 ClassMapping[] mappings = mapping.
301 getIndependentAssignableMappings();
302 if (mappings.length == 1) {
303 mapping = mappings[0];
304 Boolean custom = customLoad(sm, mapping, state, fetch);
305 if (custom != null)
306 return custom.booleanValue();
307 res = getInitializeStateResult(sm, mapping, fetch,
308 Select.SUBS_ANY_JOINABLE);
309 if (res == null && !selectPrimaryKey(sm, mapping, fetch))
310 return false;
311 } else
312 res = getInitializeStateUnionResult(sm, mapping, mappings,
313 fetch);
314 if (isEmptyResult(res))
315 return false;
316 }
317
318 // figure out what type of object this is; the state manager
319 // only guarantees to provide a base class
320 Class type;
321 if ((type = getType(res, mapping)) == null) {
322 if (res.getBaseMapping() != null)
323 mapping = res.getBaseMapping();
324 res.startDataRequest(mapping.getDiscriminator());
325 try {
326 type = mapping.getDiscriminator().getClass(this, mapping,
327 res);
328 } finally {
329 res.endDataRequest();
330 }
331 }
332
333 // initialize the state manager; this may change the mapping
334 // and the object id instance if the type as determined
335 // from the indicator is a subclass of expected type
336 sm.initialize(type, state);
337
338 // load the selected mappings into the given state manager
339 if (res != null) {
340 // re-get the mapping in case the instance was a subclass
341 mapping = (ClassMapping) sm.getMetaData();
342 load(mapping, sm, fetch, res);
343 getVersion(mapping, sm, res);
344 }
345 return true;
346 } finally {
347 if (res != null && (info == null || res != info.result))
348 res.close();
349 }
350 }
351
352 /**
353 * This method is to provide override for non-JDBC or JDBC-like
354 * implementation of getting version from the result set.
355 */
356 protected void getVersion(ClassMapping mapping, OpenJPAStateManager sm,
357 Result res) throws SQLException {
358 mapping.getVersion().afterLoad(sm, this);
359 }
360
361 /**
362 * This method is to provide override for non-JDBC or JDBC-like
363 * implementation of checking whether the result set is empty or not.
364 */
365 protected boolean isEmptyResult(Result res) throws SQLException {
366 if (res != null && !res.next())
367 return true;
368 return false;
369 }
370
371 /**
372 * This method is to provide override for non-JDBC or JDBC-like
373 * implementation of getting type from the result set.
374 */
375 protected Class getType(Result res, ClassMapping mapping){
376 if (res == null)
377 return mapping.getDescribedType();
378 return null;
379 }
380
381 /**
382 * Allow the mapping to custom load data. Return null if the mapping
383 * does not use custom loading.
384 */
385 private Boolean customLoad(OpenJPAStateManager sm, ClassMapping mapping,
386 PCState state, JDBCFetchConfiguration fetch)
387 throws ClassNotFoundException, SQLException {
388 // check to see if the mapping takes care of initialization
389 if (!mapping.customLoad(sm, this, state, fetch))
390 return null;
391 if (sm.getManagedInstance() != null) {
392 mapping.getVersion().afterLoad(sm, this);
393 return Boolean.TRUE;
394 }
395 return Boolean.FALSE;
396 }
397
398 /**
399 * Select the data for the given instance and return the result. Return
400 * null if there is no data in the current fetch groups to select.
401 */
402 private Result getInitializeStateResult(OpenJPAStateManager sm,
403 ClassMapping mapping, JDBCFetchConfiguration fetch, int subs)
404 throws SQLException {
405 Select sel = _sql.newSelect();
406 if (!select(sel, mapping, subs, sm, null, fetch,
407 JDBCFetchConfiguration.EAGER_JOIN, true, false))
408 return null;
409 sel.wherePrimaryKey(sm.getObjectId(), mapping, this);
410 sel.setExpectedResultCount(1, false);
411 return sel.execute(this, fetch);
412 }
413
414 /**
415 * Select a union of the data for the given instance from possible concrete
416 * mappings and return the result.
417 */
418 private Result getInitializeStateUnionResult(final OpenJPAStateManager sm,
419 ClassMapping mapping, final ClassMapping[] mappings,
420 final JDBCFetchConfiguration fetch) throws SQLException {
421 final JDBCStoreManager store = this;
422 final int eager = Math.min(fetch.getEagerFetchMode(),
423 JDBCFetchConfiguration.EAGER_JOIN);
424
425 Union union = _sql.newUnion(mappings.length);
426 union.setExpectedResultCount(1, false);
427 if (fetch.getSubclassFetchMode(mapping) != fetch.EAGER_JOIN)
428 union.abortUnion();
429 union.select(new Union.Selector() {
430 public void select(Select sel, int i) {
431 sel.select(mappings[i], Select.SUBS_ANY_JOINABLE, store, fetch,
432 eager);
433 sel.wherePrimaryKey(sm.getObjectId(), mappings[i], store);
434 }
435 });
436 return union.execute(this, fetch);
437 }
438
439 /**
440 * Select primary key data to make sure the given instance exists, locking
441 * if needed.
442 */
443 private boolean selectPrimaryKey(OpenJPAStateManager sm,
444 ClassMapping mapping, JDBCFetchConfiguration fetch)
445 throws SQLException {
446 // select pks from base class record to ensure it exists and lock
447 // it if needed
448 ClassMapping base = mapping;
449 while (base.getJoinablePCSuperclassMapping() != null)
450 base = base.getJoinablePCSuperclassMapping();
451
452 Select sel = _sql.newSelect();
453 sel.select(base.getPrimaryKeyColumns());
454 sel.wherePrimaryKey(sm.getObjectId(), base, this);
455 Result exists = sel.execute(this, fetch);
456 try {
457 if (isEmptyResult(exists))
458 return false;
459
460 // record locked?
461 if (_active && _lm != null && exists.isLocking())
462 _lm.loadedForUpdate(sm);
463 return true;
464 } finally {
465 exists.close();
466 }
467 }
468
469 public boolean load(OpenJPAStateManager sm, BitSet fields,
470 FetchConfiguration fetch, int lockLevel, Object context) {
471 JDBCFetchConfiguration jfetch = (JDBCFetchConfiguration) fetch;
472
473 // get a connection, or reuse current one
474 ConnectionInfo info = (ConnectionInfo) context;
475 Result res = null;
476 if (info != null) {
477 // if initialize() fails to load required fields, then this method
478 // is called; make sure not to try to use the given result if it's
479 // the same one we just failed to completely initialize() with
480 if (info.sm != sm)
481 res = info.result;
482 info.sm = null;
483 }
484 try {
485 // if there's an existing result, load all we can from it
486 ClassMapping mapping = (ClassMapping) sm.getMetaData();
487 if (res != null) {
488 load(mapping, sm, jfetch, res);
489 removeLoadedFields(sm, fields);
490 }
491
492 // if the instance is hollow and there's a customized
493 // get by id method, use it
494 if (sm.getLoaded().length() == 0
495 && mapping.customLoad(sm, this, null, jfetch))
496 removeLoadedFields(sm, fields);
497
498 //### select is kind of a big object, and in some cases we don't
499 //### use it... would it be worth it to have a small shell select
500 //### object that only creates a real select when actually used?
501
502 Select sel = _sql.newSelect();
503 if (select(sel, mapping, Select.SUBS_EXACT, sm, fields, jfetch,
504 EagerFetchModes.EAGER_JOIN, true, false)) {
505 sel.wherePrimaryKey(sm.getObjectId(), mapping, this);
506 res = sel.execute(this, jfetch, lockLevel);
507 try {
508 if (isEmptyResult(res))
509 return false;
510 load(mapping, sm, jfetch, res);
511 } finally {
512 res.close();
513 }
514 }
515
516 // now allow the fields to load themselves individually too
517 FieldMapping[] fms = mapping.getFieldMappings();
518 for (int i = 0; i < fms.length; i++)
519 if (fields.get(i) && !sm.getLoaded().get(i))
520 fms[i].load(sm, this, jfetch.traverseJDBC(fms[i]));
521 mapping.getVersion().afterLoad(sm, this);
522 return true;
523 } catch (ClassNotFoundException cnfe) {
524 throw new StoreException(cnfe);
525 } catch (SQLException se) {
526 throw SQLExceptions.getStore(se, _dict);
527 }
528 }
529
530 /**
531 * Return a list formed by removing all loaded fields from the given one.
532 */
533 private void removeLoadedFields(OpenJPAStateManager sm, BitSet fields) {
534 for (int i = 0, len = fields.length(); i < len; i++)
535 if (fields.get(i) && sm.getLoaded().get(i))
536 fields.clear(i);
537 }
538
539 public Collection loadAll(Collection sms, PCState state, int load,
540 FetchConfiguration fetch, Object context) {
541 return ImplHelper.loadAll(sms, this, state, load, fetch, context);
542 }
543
544 public void beforeStateChange(OpenJPAStateManager sm, PCState fromState,
545 PCState toState) {
546 }
547
548 public Collection flush(Collection sms) {
549 return _conf.getUpdateManagerInstance().flush(sms, this);
550 }
551
552 public boolean cancelAll() {
553 // note that this method does not lock the context, since
554 // we want to allow a different thread to be able to cancel the
555 // outstanding statement on a different context
556
557 Collection stmnts;
558 synchronized (_stmnts) {
559 if (_stmnts.isEmpty())
560 return false;
561 stmnts = new ArrayList(_stmnts);
562 }
563
564 try {
565 for (Iterator itr = stmnts.iterator(); itr.hasNext();)
566 ((Statement) itr.next()).cancel();
567 return true;
568 } catch (SQLException se) {
569 throw SQLExceptions.getStore(se, _dict);
570 }
571 }
572
573 public boolean assignObjectId(OpenJPAStateManager sm, boolean preFlush) {
574 ClassMetaData meta = sm.getMetaData();
575 if (meta.getIdentityType() == ClassMetaData.ID_APPLICATION)
576 return ApplicationIds.assign(sm, this, preFlush);
577
578 // datastore identity
579 Object val = ImplHelper.generateIdentityValue(_ctx, meta,
580 JavaTypes.LONG);
581 if (val == null && meta.getIdentityStrategy() != ValueStrategies.NATIVE)
582 return false;
583 if (val == null)
584 val = getDataStoreIdSequence(meta).next(_ctx, meta);
585 sm.setObjectId(newDataStoreId(val, meta));
586 return true;
587 }
588
589 public boolean assignField(OpenJPAStateManager sm, int field,
590 boolean preFlush) {
591 FieldMetaData fmd = sm.getMetaData().getField(field);
592 Object val = ImplHelper.generateFieldValue(_ctx, fmd);
593 if (val == null)
594 return false;
595 sm.store(field, val);
596 return true;
597 }
598
599 public Class getManagedType(Object oid) {
600 if (oid instanceof Id)
601 return ((Id) oid).getType();
602 return null;
603 }
604
605 public Class getDataStoreIdType(ClassMetaData meta) {
606 return Id.class;
607 }
608
609 public Object copyDataStoreId(Object oid, ClassMetaData meta) {
610 Id id = (Id) oid;
611 return new Id(meta.getDescribedType(), id.getId(), id.hasSubclasses());
612 }
613
614 public Object newDataStoreId(Object val, ClassMetaData meta) {
615 return Id.newInstance(meta.getDescribedType(), val);
616 }
617
618 public Id newDataStoreId(long id, ClassMapping mapping, boolean subs) {
619 return new Id(mapping.getDescribedType(), id, subs);
620 }
621
622 public ResultObjectProvider executeExtent(ClassMetaData meta,
623 final boolean subclasses, FetchConfiguration fetch) {
624 ClassMapping mapping = (ClassMapping) meta;
625 final ClassMapping[] mappings;
626 if (subclasses)
627 mappings = mapping.getIndependentAssignableMappings();
628 else
629 mappings = new ClassMapping[] { mapping };
630
631 ResultObjectProvider[] rops = null;
632 final JDBCFetchConfiguration jfetch = (JDBCFetchConfiguration) fetch;
633 if (jfetch.getSubclassFetchMode(mapping) != jfetch.EAGER_JOIN)
634 rops = new ResultObjectProvider[mappings.length];
635
636 try {
637 // check for custom loads
638 ResultObjectProvider rop;
639 for (int i = 0; i < mappings.length; i++) {
640 rop = mappings[i].customLoad(this, subclasses, jfetch, 0,
641 Long.MAX_VALUE);
642 if (rop != null) {
643 if (rops == null)
644 rops = new ResultObjectProvider[mappings.length];
645 rops[i] = rop;
646 }
647 }
648
649 // if we're selecting independent mappings separately or have
650 // custom loads, do individual selects for each class
651 rop = null;
652 if (rops != null) {
653 for (int i = 0; i < mappings.length; i++) {
654 if (rops[i] != null)
655 continue;
656
657 Select sel = _sql.newSelect();
658 sel.setLRS(true);
659 BitSet paged = selectExtent(sel, mappings[i], jfetch,
660 subclasses);
661 if (paged == null)
662 rops[i] = new InstanceResultObjectProvider(sel,
663 mappings[i], this, jfetch);
664 else
665 rops[i] = new PagingResultObjectProvider(sel,
666 mappings[i], this, jfetch, paged, Long.MAX_VALUE);
667 }
668 if (rops.length == 1)
669 return rops[0];
670 return new MergedResultObjectProvider(rops);
671 }
672
673 // perform a union on all independent classes
674 Union union = _sql.newUnion(mappings.length);
675 union.setLRS(true);
676 final BitSet[] paged = new BitSet[mappings.length];
677 union.select(new Union.Selector() {
678 public void select(Select sel, int idx) {
679 paged[idx] = selectExtent(sel, mappings[idx], jfetch,
680 subclasses);
681 }
682 });
683
684 // using paging rop if any union element has paged fields
685 for (int i = 0; i < paged.length; i++) {
686 if (paged[i] != null)
687 return new PagingResultObjectProvider(union, mappings,
688 JDBCStoreManager.this, jfetch, paged, Long.MAX_VALUE);
689 }
690 return new InstanceResultObjectProvider(union, mappings[0], this,
691 jfetch);
692 } catch (SQLException se) {
693 throw SQLExceptions.getStore(se, _dict);
694 }
695 }
696
697 /**
698 * Select the given mapping for use in an extent, returning paged fields.
699 */
700 private BitSet selectExtent(Select sel, ClassMapping mapping,
701 JDBCFetchConfiguration fetch, boolean subclasses) {
702 int subs = (subclasses) ? Select.SUBS_JOINABLE : Select.SUBS_NONE;
703 // decide between paging and standard iteration
704 BitSet paged = PagingResultObjectProvider.getPagedFields(sel, mapping,
705 this, fetch, JDBCFetchConfiguration.EAGER_PARALLEL,
706 Long.MAX_VALUE);
707 if (paged == null)
708 sel.selectIdentifier(mapping, subs, this, fetch,
709 JDBCFetchConfiguration.EAGER_PARALLEL);
710 else
711 sel.selectIdentifier(mapping, subs, this, fetch,
712 JDBCFetchConfiguration.EAGER_JOIN);
713 return paged;
714 }
715
716 public StoreQuery newQuery(String language) {
717 ExpressionParser ep = QueryLanguages.parserForLanguage(language);
718 if (ep != null)
719 return new JDBCStoreQuery(this, ep);
720 if (QueryLanguages.LANG_SQL.equals(language))
721 return new SQLStoreQuery(this);
722 return null;
723 }
724
725 public FetchConfiguration newFetchConfiguration() {
726 return new JDBCFetchConfigurationImpl();
727 }
728
729 public Seq getDataStoreIdSequence(ClassMetaData meta) {
730 if (meta.getIdentityStrategy() == ValueStrategies.NATIVE
731 || meta.getIdentityStrategy() == ValueStrategies.NONE)
732 return _conf.getSequenceInstance();
733 return null;
734 }
735
736 public Seq getValueSequence(FieldMetaData fmd) {
737 return null;
738 }
739
740 public void close() {
741 if (_conn != null)
742 _conn.free();
743 }
744
745 /////////////
746 // Utilities
747 /////////////
748
749 /**
750 * Connect to the db.
751 */
752 private void connect(boolean ref) {
753 _ctx.lock();
754 try {
755 // connect if the connection is currently null, or if
756 // the connection has been closed out from under us
757 if (_conn == null)
758 _conn = connectInternal();
759 if (ref)
760 _conn.ref();
761 } catch (SQLException se) {
762 throw SQLExceptions.getStore(se, _dict);
763 } finally {
764 _ctx.unlock();
765 }
766 }
767
768 /**
769 * Connect to the database. This method is separated out so that it
770 * can be overridden.
771 */
772 protected RefCountConnection connectInternal() throws SQLException {
773 return new RefCountConnection(_ds.getConnection());
774 }
775
776 /**
777 * Find the object with the given oid.
778 */
779 public Object find(Object oid, ValueMapping vm,
780 JDBCFetchConfiguration fetch) {
781 if (oid == null)
782 return null;
783 Object pc = _ctx.find(oid, fetch, null, null, 0);
784 if (pc == null && vm != null) {
785 OrphanedKeyAction action = _conf.getOrphanedKeyActionInstance();
786 pc = action.orphan(oid, null, vm);
787 }
788 return pc;
789 }
790
791 /**
792 * Load the object in the current row of the given result.
793 */
794 public Object load(ClassMapping mapping, JDBCFetchConfiguration fetch,
795 BitSet exclude, Result result) throws SQLException {
796 if (!mapping.isMapped())
797 throw new InvalidStateException(_loc.get("virtual-mapping",
798 mapping));
799
800 // get the object id for the row; base class selects pk columns
801 ClassMapping base = mapping;
802 while (base.getJoinablePCSuperclassMapping() != null)
803 base = base.getJoinablePCSuperclassMapping();
804 Object oid = base.getObjectId(this, result, null, true, null);
805 if (oid == null)
806 return null;
807
808 ConnectionInfo info = new ConnectionInfo();
809 info.result = result;
810 info.mapping = mapping;
811 return _ctx.find(oid, fetch, exclude, info, 0);
812 }
813
814 /**
815 * Load the given state manager with data from the result set. Only
816 * mappings originally selected will be loaded.
817 */
818 private void load(ClassMapping mapping, OpenJPAStateManager sm,
819 JDBCFetchConfiguration fetch, Result res) throws SQLException {
820 FieldMapping eagerToMany = load(mapping, sm, fetch, res, null);
821 if (eagerToMany != null)
822 eagerToMany.loadEagerJoin(sm, this, fetch.traverseJDBC(eagerToMany),
823 res);
824 if (_active && _lm != null && res.isLocking())
825 _lm.loadedForUpdate(sm);
826 }
827
828 /**
829 * Load the fields of the given mapping. Return any to-many eager field
830 * without loading it.
831 */
832 private FieldMapping load(ClassMapping mapping, OpenJPAStateManager sm,
833 JDBCFetchConfiguration fetch, Result res, FieldMapping eagerToMany)
834 throws SQLException {
835 if (mapping.customLoad(sm, this, fetch, res))
836 return eagerToMany;
837
838 // load superclass data; base class loads version
839 ClassMapping parent = mapping.getJoinablePCSuperclassMapping();
840 if (parent != null)
841 eagerToMany = load(parent, sm, fetch, res, eagerToMany);
842 else if (sm.getVersion() == null)
843 mapping.getVersion().load(sm, this, res);
844
845 // load unloaded fields
846 FieldMapping[] fms = mapping.getDefinedFieldMappings();
847 Object eres, processed;
848 for (int i = 0; i < fms.length; i++) {
849 if (fms[i].isPrimaryKey() || sm.getLoaded().get(fms[i].getIndex()))
850 continue;
851
852 // check for eager result, and if not present do standard load
853 eres = res.getEager(fms[i]);
854 res.startDataRequest(fms[i]);
855 try {
856 if (eres == res) {
857 if (eagerToMany == null && fms[i].isEagerSelectToMany())
858 eagerToMany = fms[i];
859 else
860 fms[i].loadEagerJoin(sm, this,
861 fetch.traverseJDBC(fms[i]), res);
862 } else if (eres != null) {
863 processed = fms[i].loadEagerParallel(sm, this,
864 fetch.traverseJDBC(fms[i]), eres);
865 if (processed != eres)
866 res.putEager(fms[i], processed);
867 } else
868 fms[i].load(sm, this, fetch.traverseJDBC(fms[i]), res);
869 } finally {
870 res.endDataRequest();
871 }
872 }
873 return eagerToMany;
874 }
875
876 /**
877 * For implementation use only.
878 * Return a select for the proper mappings. Return null if no select is
879 * needed. The method is designed to be complementary to the load methods.
880 *
881 * @param sel select to build on
882 * @param mapping the mapping for the base type to select for
883 * @param subs whether the select might include subclasses of the
884 * given mapping
885 * @param sm state manager if an instance is being loaded or
886 * initialized, else null
887 * @param fields if a state manager is being loaded, the set of
888 * fields that must be loaded in order, else null
889 * @param fetch the fetch configuration; used if no specific fields
890 * must be loaded, and used when selecting relations
891 * @param eager eager fetch mode to use
892 * @param ident whether to select primary key columns as distinct
893 * identifiers
894 * @param outer whether we're outer-joining to this type
895 * @return true if the select is required, false otherwise
896 */
897 public boolean select(Select sel, ClassMapping mapping, int subs,
898 OpenJPAStateManager sm, BitSet fields, JDBCFetchConfiguration fetch,
899 int eager, boolean ident, boolean outer) {
900 // add class conditions so that they're cloned for any batched selects
901 boolean joinedSupers = false;
902 if ((sm == null || sm.getPCState() == PCState.TRANSIENT)
903 && (subs == Select.SUBS_JOINABLE || subs == Select.SUBS_NONE)) {
904 loadSubclasses(mapping);
905 Joins joins = (outer) ? sel.newOuterJoins() : null;
906 joinedSupers = mapping.getDiscriminator().addClassConditions(sel,
907 subs == Select.SUBS_JOINABLE, joins);
908 }
909
910 // create all our eager selects so that those fields are reserved
911 // and cannot be reused during the actual eager select process,
912 // preventing infinite recursion
913 eager = Math.min(eager, fetch.getEagerFetchMode());
914 FieldMapping eagerToMany = createEagerSelects(sel, mapping, sm, fields,
915 fetch, eager);
916
917 // select all base class mappings; do this after batching so that
918 // the joins needed by these selects don't get in the WHERE clause
919 // of the batched selects
920 int seld = selectBaseMappings(sel, mapping, mapping, sm, fields,
921 fetch, eager, eagerToMany, ident, joinedSupers);
922
923 // select eager to-many relations last because during load they
924 // advance the result set and could exhaust it, so no other mappings
925 // can load afterwords
926 if (eagerToMany != null)
927 eagerToMany.selectEagerJoin(sel, sm, this,
928 fetch.traverseJDBC(eagerToMany), eager);
929
930 // optionally select subclass mappings
931 if (subs == Select.SUBS_JOINABLE || subs == Select.SUBS_ANY_JOINABLE)
932 selectSubclassMappings(sel, mapping, sm, fetch);
933 if (sm != null)
934 sel.setDistinct(false);
935 return seld > 0;
936 }
937
938 /**
939 * Mark the fields of this mapping as reserved so that eager fetches can't
940 * get into infinite recursive situations.
941 */
942 private FieldMapping createEagerSelects(Select sel, ClassMapping mapping,
943 OpenJPAStateManager sm, BitSet fields, JDBCFetchConfiguration fetch,
944 int eager) {
945 if (mapping == null || eager == JDBCFetchConfiguration.EAGER_NONE)
946 return null;
947
948 FieldMapping eagerToMany = createEagerSelects(sel,
949 mapping.getJoinablePCSuperclassMapping(), sm, fields, fetch, eager);
950
951 FieldMapping[] fms = mapping.getDefinedFieldMappings();
952 boolean inEagerJoin = sel.hasEagerJoin(false);
953 int sels;
954 int jtype;
955 int mode;
956 for (int i = 0; i < fms.length; i++) {
957 mode = fms[i].getEagerFetchMode();
958 if (mode == fetch.EAGER_NONE)
959 continue;
960 if (!requiresSelect(fms[i], sm, fields, fetch))
961 continue;
962
963 // try to select with join first
964 jtype = (fms[i].getNullValue() == fms[i].NULL_EXCEPTION)
965 ? sel.EAGER_INNER : sel.EAGER_OUTER;
966 if (mode != fetch.EAGER_PARALLEL && !fms[i].isEagerSelectToMany()
967 && fms[i].supportsSelect(sel, jtype, sm, this, fetch) > 0
968 && sel.eagerClone(fms[i], jtype, false, 1) != null)
969 continue;
970
971 boolean hasJoin = fetch.hasJoin(fms[i].getFullName(false));
972
973 // if the field declares a preferred select mode of join or does not
974 // have a preferred mode and we're doing a by-id lookup, try
975 // to use a to-many join also. currently we limit eager
976 // outer joins to non-LRS, non-ranged selects that don't already
977 // have an eager to-many join
978 if ((hasJoin || mode == fetch.EAGER_JOIN
979 || (mode == fetch.DEFAULT && sm != null))
980 && fms[i].isEagerSelectToMany()
981 && !inEagerJoin
982 && !sel.hasEagerJoin(true)
983 && (!sel.getAutoDistinct() || (!sel.isLRS()
984 && sel.getStartIndex() == 0
985 && sel.getEndIndex() == Long.MAX_VALUE))
986 && fms[i].supportsSelect(sel, jtype, sm, this, fetch) > 0) {
987 if (sel.eagerClone(fms[i], jtype, true, 1) != null)
988 eagerToMany = fms[i];
989 else
990 continue;
991 }
992
993 // finally, try parallel
994 if (eager == fetch.EAGER_PARALLEL
995 && (sels = fms[i].supportsSelect(sel, sel.EAGER_PARALLEL, sm,
996 this, fetch)) != 0)
997 sel.eagerClone(fms[i], Select.EAGER_PARALLEL,
998 fms[i].isEagerSelectToMany(), sels);
999 }
1000 return eagerToMany;
1001 }
1002
1003 /**
1004 * Determine if the given field needs to be selected.
1005 */
1006 private static boolean requiresSelect(FieldMapping fm,
1007 OpenJPAStateManager sm, BitSet fields, JDBCFetchConfiguration fetch) {
1008 if (fields != null)
1009 return fields.get(fm.getIndex());
1010 if (sm != null && sm.getPCState() != PCState.TRANSIENT
1011 && sm.getLoaded().get(fm.getIndex()))
1012 return false;
1013 return fetch.requiresFetch(fm) == FetchConfiguration.FETCH_LOAD;
1014 }
1015
1016 /**
1017 * Select the field mappings of the given class and all its superclasses.
1018 *
1019 * @param sel the select to use
1020 * @param mapping the most-derived type to select for
1021 * @param orig the original mapping type selected
1022 * @param sm the instance being selected for, or null if none
1023 * @param fields the fields to load
1024 * @param fetch fetch configuration to use for loading relations
1025 * @param eager the eager fetch mode to use
1026 * @param joined whether the class has already been joined down to
1027 * its base class
1028 * @return > 0 if the select is required, 0 if data was
1029 * selected but is not required, and < 0 if nothing was selected
1030 */
1031 private int selectBaseMappings(Select sel, ClassMapping mapping,
1032 ClassMapping orig, OpenJPAStateManager sm, BitSet fields,
1033 JDBCFetchConfiguration fetch, int eager, FieldMapping eagerToMany,
1034 boolean ident, boolean joined) {
1035 ClassMapping parent = mapping.getJoinablePCSuperclassMapping();
1036 if (parent == null && !mapping.isMapped())
1037 throw new InvalidStateException(_loc.get("virtual-mapping", mapping.
1038 getDescribedType()));
1039
1040 int seld = -1;
1041 int pseld = -1;
1042
1043 // base class selects pks, etc
1044 if (parent == null) {
1045 // if no instance, select pks
1046 if (sm == null) {
1047 if (ident)
1048 sel.selectIdentifier(mapping.getPrimaryKeyColumns());
1049 else
1050 sel.select(mapping.getPrimaryKeyColumns());
1051 seld = 1;
1052 }
1053
1054 // if no instance or not initialized and not exact oid, select type
1055 if ((sm == null || (sm.getPCState() == PCState.TRANSIENT
1056 && (!(sm.getObjectId() instanceof OpenJPAId)
1057 || ((OpenJPAId) sm.getObjectId()).hasSubclasses())))
1058 && mapping.getDiscriminator().select(sel, orig))
1059 seld = 1;
1060
1061 // if no instance or no version, select version
1062 if ((sm == null || sm.getVersion() == null)
1063 && mapping.getVersion().select(sel, orig))
1064 seld = 1;
1065 } else {
1066 // recurse on parent
1067 pseld = selectBaseMappings(sel, parent, orig, sm, fields,
1068 fetch, eager, eagerToMany, ident, joined);
1069 }
1070
1071 // select the mappings in the given fields set, or based on fetch
1072 // configuration if no fields given
1073 FieldMapping[] fms = mapping.getDefinedFieldMappings();
1074 SelectExecutor esel;
1075 int fseld;
1076 for (int i = 0; i < fms.length; i++) {
1077 // skip eager to-many select; we do that separately in calling
1078 // method
1079 if (fms[i] == eagerToMany)
1080 continue;
1081
1082 // check for eager select
1083 esel = sel.getEager(fms[i]);
1084 if (esel != null) {
1085 if (esel == sel)
1086 fms[i].selectEagerJoin(sel, sm, this,
1087 fetch.traverseJDBC(fms[i]), eager);
1088 else
1089 fms[i].selectEagerParallel(esel, sm, this,
1090 fetch.traverseJDBC(fms[i]), eager);
1091 seld = Math.max(0, seld);
1092 } else if (requiresSelect(fms[i], sm, fields, fetch)) {
1093 fseld = fms[i].select(sel, sm, this,
1094 fetch.traverseJDBC(fms[i]), eager);
1095 seld = Math.max(fseld, seld);
1096 } else if (optSelect(fms[i], sel, sm, fetch)) {
1097 fseld = fms[i].select(sel, sm, this,
1098 fetch.traverseJDBC(fms[i]), fetch.EAGER_NONE);
1099
1100 // don't upgrade seld to > 0 based on these fields, since
1101 // they're not in the calculated field set
1102 if (fseld >= 0 && seld < 0)
1103 seld = 0;
1104 }
1105 }
1106
1107 // join to parent table if the parent / any ancestors have selected
1108 // anything
1109 if (!joined && pseld >= 0 && parent.getTable() != mapping.getTable())
1110 sel.where(mapping.joinSuperclass(sel.newJoins(), false));
1111
1112 // return the highest value
1113 return Math.max(pseld, seld);
1114 }
1115
1116 /**
1117 * When selecting fieldes, a special case is made for mappings that use
1118 * 2-part selects that aren't explicitly *not* in the dfg so that they
1119 * can get their primary table data. This method tests for that special
1120 * case as an optimization.
1121 */
1122 private boolean optSelect(FieldMapping fm, Select sel,
1123 OpenJPAStateManager sm, JDBCFetchConfiguration fetch) {
1124 return !fm.isInDefaultFetchGroup()
1125 && !fm.isDefaultFetchGroupExplicit()
1126 && (sm == null || sm.getPCState() == PCState.TRANSIENT
1127 || !sm.getLoaded().get(fm.getIndex()))
1128 && fm.supportsSelect(sel, sel.TYPE_TWO_PART, sm, this, fetch) > 0;
1129 }
1130
1131 /**
1132 * Select field mappings that match the given fetch configuration for
1133 * subclasses of the given type.
1134 *
1135 * @param sel the select to use
1136 * @param mapping the type whose subclasses to select
1137 * @param sm the instance being selected for, or null if none
1138 * @param fetch the fetch configuration
1139 */
1140 private void selectSubclassMappings(Select sel, ClassMapping mapping,
1141 OpenJPAStateManager sm, JDBCFetchConfiguration fetch) {
1142 loadSubclasses(mapping);
1143 ClassMapping[] subMappings = mapping.getJoinablePCSubclassMappings();
1144 if (subMappings.length == 0)
1145 return;
1146
1147 // select all subclass mappings that match the fetch configuration
1148 // and whose table is in the list of those selected so far; this
1149 // way we select the max possible without selecting any tables that
1150 // aren't present in all possible query matches; a special case
1151 // is made for mappings that use 2-part selects that aren't
1152 // explicitly *not* in the default so that they can get their
1153 // primary table data
1154 FieldMapping[] fms;
1155 boolean joined;
1156 boolean canJoin = _dict.joinSyntax != JoinSyntaxes.SYNTAX_TRADITIONAL
1157 && fetch.getSubclassFetchMode(mapping) != fetch.EAGER_NONE;
1158 for (int i = 0; i < subMappings.length; i++) {
1159 if (!subMappings[i].supportsEagerSelect(sel, sm, this, mapping,
1160 fetch))
1161 continue;
1162
1163 // initialize so that if we can't join, we pretend we already have
1164 joined = !canJoin;
1165 fms = subMappings[i].getDefinedFieldMappings();
1166 for (int j = 0; j < fms.length; j++) {
1167 // make sure in one of configured fetch groups
1168 if (fetch.requiresFetch(fms[j]) != FetchConfiguration.FETCH_LOAD
1169 && ((!fms[j].isInDefaultFetchGroup()
1170 && fms[j].isDefaultFetchGroupExplicit())
1171 || fms[j].supportsSelect(sel, sel.TYPE_TWO_PART, sm, this,
1172 fetch) <= 0))
1173 continue;
1174
1175 // if we can join to the subclass, do so; much better chance
1176 // that the field will be able to select itself without joins
1177 if (!joined) {
1178 // mark joined whether or not we join, so we don't have to
1179 // test conditions again for this subclass
1180 joined = true;
1181 sel.where(joinSubclass(sel, mapping, subMappings[i], null));
1182 }
1183
1184 // if can select with tables already selected, do it
1185 if (fms[j].supportsSelect(sel, sel.TYPE_JOINLESS, sm, this,
1186 fetch) > 0)
1187 fms[j].select(sel, null, this, fetch.traverseJDBC(fms[j]),
1188 fetch.EAGER_NONE);
1189 }
1190 }
1191 }
1192
1193 /**
1194 * Helper method to join from class to its subclass. Recursive to allow
1195 * for multiple hops, starting from the base class.
1196 */
1197 private static Joins joinSubclass(Select sel, ClassMapping base,
1198 ClassMapping sub, Joins joins) {
1199 if (sub == base || sub.getTable() == base.getTable()
1200 || sel.isSelected(sub.getTable()))
1201 return null;
1202
1203 // recurse first so we go least->most derived
1204 ClassMapping sup = sub.getJoinablePCSuperclassMapping();
1205 joins = joinSubclass(sel, base, sup, joins);
1206 if (joins == null)
1207 joins = sel.newJoins();
1208 return sub.joinSuperclass(joins, true);
1209 }
1210
1211 /**
1212 * Makes sure all subclasses of the given type are loaded in the JVM.
1213 * This is usually done automatically.
1214 */
1215 public void loadSubclasses(ClassMapping mapping) {
1216 Discriminator dsc = mapping.getDiscriminator();
1217 if (dsc.getSubclassesLoaded())
1218 return;
1219
1220 // if the subclass list is set, no need to load subs
1221 if (mapping.getRepository().getPersistentTypeNames(false,
1222 _ctx.getClassLoader()) != null) {
1223 dsc.setSubclassesLoaded(true);
1224 return;
1225 }
1226
1227 try {
1228 dsc.loadSubclasses(this);
1229 } catch (ClassNotFoundException cnfe) {
1230 throw new StoreException(cnfe);
1231 } catch (SQLException se) {
1232 throw SQLExceptions.getStore(se, _dict);
1233 }
1234 }
1235
1236 /**
1237 * Make the statement a candidate for cancellation.
1238 */
1239 private void beforeExecuteStatement(Statement stmnt) {
1240 _stmnts.add(stmnt);
1241 }
1242
1243 /**
1244 * Remove the statement from the cancellable set.
1245 */
1246 private void afterExecuteStatement(Statement stmnt) {
1247 _stmnts.remove(stmnt);
1248 }
1249
1250 /**
1251 * Connection returned to client code. Makes sure its wrapped connection
1252 * ref count is decremented on finalize.
1253 */
1254 private static class ClientConnection extends DelegatingConnection {
1255
1256 private boolean _closed = false;
1257
1258 public ClientConnection(Connection conn) {
1259 super(conn);
1260 }
1261
1262 public void close() throws SQLException {
1263 _closed = true;
1264 super.close();
1265 }
1266
1267 protected void finalize() throws SQLException {
1268 if (!_closed)
1269 close();
1270 }
1271 }
1272
1273 /**
1274 * Connection wrapper that keeps an internal ref count so that it knows
1275 * when to really close.
1276 */
1277 protected class RefCountConnection extends DelegatingConnection {
1278
1279 private boolean _retain = false;
1280 private int _refs = 0;
1281 private boolean _freed = false;
1282
1283 public RefCountConnection(Connection conn) {
1284 super(conn);
1285 }
1286
1287 public boolean getRetain() {
1288 return _retain;
1289 }
1290
1291 public void setRetain(boolean retain) {
1292 if (_retain && !retain && _refs <= 0)
1293 free();
1294 _retain = retain;
1295 }
1296
1297 public void ref() {
1298 // don't have to lock; called from connect(), which is locked
1299 _refs++;
1300 }
1301
1302 public void close() throws SQLException {
1303 // lock at broker level to avoid deadlocks
1304 _ctx.lock();
1305 try {
1306 _refs--;
1307 if (_refs <= 0 && !_retain)
1308 free();
1309 } finally {
1310 _ctx.unlock();
1311 }
1312 }
1313
1314 public void free() {
1315 // ensure that we do not close the underlying connection
1316 // multiple times; this could happen if someone (e.g., an
1317 // Extent) holds a RefConnection, and then closes it (e.g., in
1318 // the finalizer) after the StoreManager has already been closed.
1319 if (_freed)
1320 return;
1321
1322 try {
1323 getDelegate().close();
1324 } catch (SQLException se) {
1325 }
1326 _freed = true;
1327 _conn = null;
1328 }
1329
1330 protected Statement createStatement(boolean wrap) throws SQLException {
1331 return new CancelStatement(super.createStatement(false),
1332 RefCountConnection.this);
1333 }
1334
1335 protected Statement createStatement(int rsType, int rsConcur,
1336 boolean wrap) throws SQLException {
1337 return new CancelStatement(super.createStatement(rsType, rsConcur,
1338 false), RefCountConnection.this);
1339 }
1340
1341 protected PreparedStatement prepareStatement(String sql, boolean wrap)
1342 throws SQLException {
1343 return new CancelPreparedStatement(super.prepareStatement(sql,
1344 false), RefCountConnection.this);
1345 }
1346
1347 protected PreparedStatement prepareStatement(String sql, int rsType,
1348 int rsConcur, boolean wrap) throws SQLException {
1349 return new CancelPreparedStatement(super.prepareStatement(sql,
1350 rsType, rsConcur, false), RefCountConnection.this);
1351 }
1352 }
1353
1354 /**
1355 * Statement type that adds and removes itself from the set of active
1356 * statements so that it can be canceled.
1357 */
1358 private class CancelStatement extends DelegatingStatement {
1359
1360 public CancelStatement(Statement stmnt, Connection conn) {
1361 super(stmnt, conn);
1362 }
1363
1364 public int executeUpdate(String sql) throws SQLException {
1365 beforeExecuteStatement(this);
1366 try {
1367 return super.executeUpdate(sql);
1368 } finally {
1369 afterExecuteStatement(this);
1370 }
1371 }
1372
1373 protected ResultSet executeQuery(String sql, boolean wrap)
1374 throws SQLException {
1375 beforeExecuteStatement(this);
1376 try {
1377 return super.executeQuery(sql, wrap);
1378 } finally {
1379 afterExecuteStatement(this);
1380 }
1381 }
1382 }
1383
1384 /**
1385 * Statement type that adds and removes itself from the set of active
1386 * statements so that it can be canceled.
1387 */
1388 private class CancelPreparedStatement extends DelegatingPreparedStatement {
1389
1390 public CancelPreparedStatement(PreparedStatement stmnt,
1391 Connection conn) {
1392 super(stmnt, conn);
1393 }
1394
1395 public int executeUpdate() throws SQLException {
1396 beforeExecuteStatement(this);
1397 try {
1398 return super.executeUpdate();
1399 } finally {
1400 afterExecuteStatement(this);
1401 }
1402 }
1403
1404 protected ResultSet executeQuery(boolean wrap) throws SQLException {
1405 beforeExecuteStatement(this);
1406 try {
1407 return super.executeQuery(wrap);
1408 } finally {
1409 afterExecuteStatement(this);
1410 }
1411 }
1412
1413 public int[] executeBatch() throws SQLException {
1414 beforeExecuteStatement(this);
1415 try {
1416 return super.executeBatch();
1417 } finally {
1418 afterExecuteStatement(this);
1419 }
1420 }
1421 }
1422 }