001: /**********************************************************************
002: Copyright (c) 2007 Erik Bengtson and others. All rights reserved.
003: Licensed under the Apache License, Version 2.0 (the "License");
004: you may not use this file except in compliance with the License.
005: You may obtain a copy of the License at
006:
007: http://www.apache.org/licenses/LICENSE-2.0
008:
009: Unless required by applicable law or agreed to in writing, software
010: distributed under the License is distributed on an "AS IS" BASIS,
011: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: See the License for the specific language governing permissions and
013: limitations under the License.
014:
015: Contributors:
016: ...
017: **********************************************************************/package org.jpox;
018:
019: import java.util.ArrayList;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.Set;
025:
026: import javax.transaction.Status;
027: import javax.transaction.Synchronization;
028:
029: import org.jpox.exceptions.JPOXDataStoreException;
030: import org.jpox.exceptions.JPOXException;
031: import org.jpox.exceptions.JPOXUserException;
032: import org.jpox.exceptions.TransactionActiveOnBeginException;
033: import org.jpox.exceptions.TransactionNotActiveException;
034: import org.jpox.transaction.HeuristicMixedException;
035: import org.jpox.transaction.HeuristicRollbackException;
036: import org.jpox.transaction.JPOXTransactionException;
037: import org.jpox.transaction.RollbackException;
038: import org.jpox.util.JPOXLogger;
039: import org.jpox.util.Localiser;
040: import org.jpox.util.StringUtils;
041:
042: /**
043: * Implementation of a transaction for a datastore.
044: * {@link org.jpox.Transaction}
045: *
046: * @version $Revision: 1.34 $
047: */
048: public class TransactionImpl implements Transaction {
049: /** Localisation of messages. */
050: protected static final Localiser LOCALISER = Localiser
051: .getInstance("org.jpox.Localisation");
052:
053: /** Object Manager for this transaction. */
054: ObjectManager om;
055:
056: /** Underlying transaction */
057: org.jpox.transaction.Transaction tx;
058:
059: /** Whether the transaction is active. */
060: boolean active = false;
061:
062: /** Flag for whether we are currently committing. */
063: boolean committing;
064:
065: /** Synchronisation object, for committing and rolling back. */
066: Synchronization sync;
067:
068: /** Whether retainValues is enabled. */
069: protected boolean retainValues;
070:
071: /** Whether restoreValues is enabled. */
072: protected boolean restoreValues;
073:
074: /** Whether the transaction is optimistic */
075: protected boolean optimistic;
076:
077: /** Whether non-tx read is enabled. */
078: protected boolean nontransactionalRead;
079:
080: /** Whether non-tx write is enabled. */
081: protected boolean nontransactionalWrite;
082:
083: /** Whether the transaction is marked for rollback. JDO 2.0 section 13.4.5 */
084: protected boolean rollbackOnly = false;
085:
086: Set listeners = new HashSet();
087:
088: Map options = new HashMap();
089:
090: /** start time of the transaction */
091: long beginTime;
092:
093: /**
094: * Constructor for a transaction for the specified ObjectManager.
095: * @param om ObjectManager.
096: */
097: TransactionImpl(ObjectManager om) {
098: this .om = om;
099: PersistenceConfiguration config = om.getOMFContext()
100: .getPersistenceConfiguration();
101: optimistic = config.getOptimistic();
102: retainValues = config.getRetainValues();
103: restoreValues = config.getRestoreValues();
104: nontransactionalRead = config.getNontransactionalRead();
105: nontransactionalWrite = config.getNontransactionalWrite();
106:
107: setOption("transaction.isolation", config
108: .getTransactionIsolation());
109: setOption("transaction.serializeReadObjects", config
110: .getUseUpdateLock());
111: }
112:
113: /**
114: * Method to begin the transaction.
115: */
116: public void begin() {
117: om.getOMFContext().getTransactionManager().begin(om);
118: tx = om.getOMFContext().getTransactionManager().getTransaction(
119: om);
120: internalBegin();
121: }
122:
123: /**
124: * Method to begin the transaction.
125: */
126: protected void internalBegin() {
127: if (active) {
128: throw new TransactionActiveOnBeginException(this );
129: }
130: active = true;
131: beginTime = System.currentTimeMillis();
132: if (om.getOMFContext().getTransactionManager()
133: .getTransactionRuntime() != null) {
134: om.getOMFContext().getTransactionManager()
135: .getTransactionRuntime().transactionStarted();
136: }
137:
138: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
139: JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015000", om, ""
140: + optimistic));
141: }
142:
143: TransactionEventListener[] tel = (TransactionEventListener[]) listeners
144: .toArray(new TransactionEventListener[listeners.size()]);
145: for (int i = 0; i < tel.length; i++) {
146: tel[i].transactionStarted();
147: }
148:
149: om.postBegin();
150: }
151:
152: /**
153: * Method to flush the transaction.
154: */
155: public void flush() {
156: try {
157: TransactionEventListener[] tel = (TransactionEventListener[]) listeners
158: .toArray(new TransactionEventListener[listeners
159: .size()]);
160: for (int i = 0; i < tel.length; i++) {
161: tel[i].transactionFlushed();
162: }
163: } catch (Throwable ex) {
164: if (ex instanceof JPOXException) {
165: throw (JPOXException) ex;
166: }
167: // Wrap all other exceptions in a JPOXTransactionException
168: throw new JPOXTransactionException(LOCALISER.msg("015005"),
169: ex);
170: }
171: }
172:
173: /**
174: * Method to allow the transaction to flush any resources.
175: */
176: public void end() {
177: try {
178: flush();
179: } finally {
180: TransactionEventListener[] tel = (TransactionEventListener[]) listeners
181: .toArray(new TransactionEventListener[listeners
182: .size()]);
183: for (int i = 0; i < tel.length; i++) {
184: tel[i].transactionEnded();
185: }
186: }
187: }
188:
189: /**
190: * Method to commit the transaction.
191: */
192: public void commit() {
193: if (!isActive()) {
194: throw new TransactionNotActiveException();
195: }
196:
197: // JDO 2.0 section 13.4.5 rollbackOnly functionality
198: // It isn't clear from the spec if we are expected to do the rollback here.
199: // The spec simply says that we throw an exception. This is assumed as meaning that the users code will catch
200: // the exception and call rollback themselves. i.e we don't need to close the DB connection or set "active" to false.
201: if (rollbackOnly) {
202: // Throw an exception since can only exit via rollback
203: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
204: JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015020"));
205: }
206:
207: throw new JPOXDataStoreException(LOCALISER.msg("015020"))
208: .setFatal();
209: }
210:
211: long startTime = System.currentTimeMillis();
212: boolean success = false;
213: boolean canComplete = true; //whether the transaction can be completed
214: List errors = new ArrayList();
215: try {
216: flush();
217:
218: internalPreCommit();
219:
220: internalCommit();
221:
222: success = true;
223: } catch (RollbackException e) {
224: //catch only JPOXException because user exceptions can be raised
225: //in Transaction.Synchronization and they should cascade up to user code
226: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
227: JPOXLogger.TRANSACTION.debug(StringUtils
228: .getStringFromStackTrace(e));
229: }
230: errors.add(e);
231: } catch (HeuristicRollbackException e) {
232: //catch only JPOXException because user exceptions can be raised
233: //in Transaction.Synchronization and they should cascade up to user code
234: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
235: JPOXLogger.TRANSACTION.debug(StringUtils
236: .getStringFromStackTrace(e));
237: }
238: errors.add(e);
239: } catch (HeuristicMixedException e) {
240: //catch only JPOXException because user exceptions can be raised
241: //in Transaction.Synchronization and they should cascade up to user code
242: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
243: JPOXLogger.TRANSACTION.debug(StringUtils
244: .getStringFromStackTrace(e));
245: }
246: errors.add(e);
247: } catch (JPOXUserException e) {
248: //catch only JPOXUserException
249: //they must be cascade up to user code and transaction is still alive
250: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
251: JPOXLogger.TRANSACTION.debug(StringUtils
252: .getStringFromStackTrace(e));
253: }
254: canComplete = false;
255: throw e;
256: } catch (JPOXException e) {
257: //catch only JPOXException because user exceptions can be raised
258: //in Transaction.Synchronization and they should cascade up to user code
259: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
260: JPOXLogger.TRANSACTION.debug(StringUtils
261: .getStringFromStackTrace(e));
262: }
263: errors.add(e);
264: } finally {
265: if (canComplete) {
266: try {
267: if (!success) {
268: rollback();
269: } else {
270: internalPostCommit();
271: }
272: } catch (Throwable e) {
273: errors.add(e);
274: }
275: tx = null;
276: }
277: }
278: if (errors.size() > 0) {
279: throw new JPOXTransactionException(LOCALISER.msg("015007"),
280: (Throwable[]) errors.toArray(new Throwable[errors
281: .size()]));
282: }
283:
284: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
285: JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015022",
286: (System.currentTimeMillis() - startTime)));
287: }
288:
289: }
290:
291: /**
292: * Method to perform any pre-commit operations like flushing to the datastore, calling the users
293: * "beforeCompletion", and general preparation for the commit.
294: */
295: protected void internalPreCommit() {
296: committing = true;
297:
298: try {
299: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
300: JPOXLogger.TRANSACTION.debug(LOCALISER
301: .msg("015001", om));
302: }
303:
304: if (sync != null) {
305: // JDO2 $13.4.3 Allow the user to perform any updates before we do loading of fields etc
306: sync.beforeCompletion();
307: }
308:
309: // Perform any pre-commit operations
310: om.preCommit(); // This has to be after setting "committing"
311: } finally {
312:
313: TransactionEventListener[] tel = (TransactionEventListener[]) listeners
314: .toArray(new TransactionEventListener[listeners
315: .size()]);
316: for (int i = 0; i < tel.length; i++) {
317: tel[i].transactionPreCommit();
318: }
319: }
320: }
321:
322: /**
323: * Internal commit, JPOX invokes it's own transaction manager implementation, if
324: * an external transaction manager is not used.
325: */
326: protected void internalCommit() {
327: // optimistic transactions that don't have dirty
328: om.getOMFContext().getTransactionManager().commit(om);
329: }
330:
331: /**
332: * Method to rollback the transaction.
333: */
334: public void rollback() {
335: if (!isActive()) {
336: throw new TransactionNotActiveException();
337: }
338: long startTime = System.currentTimeMillis();
339: try {
340: boolean canComplete = true; //whether the transaction can be completed
341: committing = true;
342: try {
343: try {
344: flush();
345: } finally {
346: //even if flush fails, we ignore and go ahead cleaning up and rolling back everything ahead...
347: try {
348: internalPreRollback();
349: } catch (JPOXUserException e) {
350: //catch only JPOXUserException
351: //they must be cascade up to user code and transaction is still alive
352: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
353: JPOXLogger.TRANSACTION.debug(StringUtils
354: .getStringFromStackTrace(e));
355: }
356: canComplete = false;
357: throw e;
358: } finally {
359: if (canComplete) {
360: internalRollback();
361: }
362: }
363: }
364: } finally {
365: try {
366: if (canComplete) {
367: try {
368: active = false;
369: if (om.getOMFContext()
370: .getTransactionManager()
371: .getTransactionRuntime() != null) {
372: om
373: .getOMFContext()
374: .getTransactionManager()
375: .getTransactionRuntime()
376: .transactionRolledBack(
377: System
378: .currentTimeMillis()
379: - beginTime);
380: }
381: TransactionEventListener[] tel = (TransactionEventListener[]) listeners
382: .toArray(new TransactionEventListener[listeners
383: .size()]);
384: for (int i = 0; i < tel.length; i++) {
385: tel[i].transactionRolledBack();
386: }
387:
388: } finally {
389: listeners.clear();
390: rollbackOnly = false; // Reset rollbackOnly flag
391: tx = null;
392: if (sync != null) {
393: sync
394: .afterCompletion(Status.STATUS_ROLLEDBACK);
395: }
396: }
397: }
398: } finally {
399: committing = false;
400: }
401: }
402: } catch (JPOXUserException e) {
403: throw e;
404: } catch (JPOXException e) {
405: throw new JPOXDataStoreException(LOCALISER.msg("015009"), e);
406: }
407:
408: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
409: JPOXLogger.TRANSACTION.debug(LOCALISER.msg("015023",
410: (System.currentTimeMillis() - startTime)));
411: }
412: }
413:
414: /**
415: * Call om.preRollback() and listeners.
416: */
417: protected void internalPreRollback() {
418: try {
419: if (JPOXLogger.TRANSACTION.isDebugEnabled()) {
420: JPOXLogger.TRANSACTION.debug(LOCALISER
421: .msg("015002", om));
422: }
423:
424: om.preRollback();
425: } finally {
426: TransactionEventListener[] tel = (TransactionEventListener[]) listeners
427: .toArray(new TransactionEventListener[listeners
428: .size()]);
429: for (int i = 0; i < tel.length; i++) {
430: tel[i].transactionPreRollBack();
431: }
432: }
433: }
434:
435: /**
436: * Internal rollback, JPOX invokes it's own transaction manager implementation, if
437: * an external transaction manager is not used.
438: */
439: protected void internalRollback() {
440: org.jpox.transaction.Transaction tx = om.getOMFContext()
441: .getTransactionManager().getTransaction(om);
442: if (tx != null) {
443: om.getOMFContext().getTransactionManager().rollback(om);
444: }
445: }
446:
447: /**
448: * Method to perform any post-commit operations like calling the users "afterCompletion"
449: * and general clean up after the commit.
450: */
451: protected void internalPostCommit() {
452: try {
453: active = false;
454: if (om.getOMFContext().getTransactionManager()
455: .getTransactionRuntime() != null) {
456: om.getOMFContext().getTransactionManager()
457: .getTransactionRuntime().transactionCommitted(
458: System.currentTimeMillis() - beginTime);
459: }
460:
461: //closeConnection();
462: TransactionEventListener[] tel = (TransactionEventListener[]) listeners
463: .toArray(new TransactionEventListener[listeners
464: .size()]);
465: for (int i = 0; i < tel.length; i++) {
466: tel[i].transactionCommitted();
467: }
468: } finally {
469: listeners.clear();
470: try {
471: om.postCommit();
472: } finally {
473: committing = false;
474: if (sync != null) {
475: sync.afterCompletion(Status.STATUS_COMMITTED);
476: }
477: }
478: }
479: }
480:
481: /**
482: * Accessor for whether the transaction is active.
483: * @return Whether the transaction is active.
484: **/
485: public boolean isActive() {
486: return active;
487: }
488:
489: /**
490: * Accessor for whether the transaction is comitting.
491: * @return Whether the transaction is committing.
492: **/
493: public boolean isCommitting() {
494: return committing;
495: }
496:
497: // ------------------------------- Accessors/Mutators ---------------------------------------
498:
499: /**
500: * Accessor for the nontransactionalRead flag for this transaction.
501: * @return Whether nontransactionalRead is set.
502: */
503: public boolean getNontransactionalRead() {
504: return nontransactionalRead;
505: }
506:
507: /**
508: * Accessor for the nontransactionalWrite flag for this transaction.
509: * @return Whether nontransactionalWrite is set.
510: */
511: public boolean getNontransactionalWrite() {
512: return nontransactionalWrite;
513: }
514:
515: /**
516: * Accessor for the Optimistic setting
517: * @return Whether optimistic transactions are in operation.
518: */
519: public boolean getOptimistic() {
520: return optimistic;
521: }
522:
523: /**
524: * Accessor for the restoreValues flag for this transaction.
525: * @return Whether restoreValues is set.
526: */
527: public boolean getRestoreValues() {
528: return restoreValues;
529: }
530:
531: /**
532: * Accessor for the retainValues flag for this transaction.
533: * @return Whether retainValues is set.
534: */
535: public boolean getRetainValues() {
536: return retainValues;
537: }
538:
539: /**
540: * Accessor for the "rollback only" flag.
541: * @return The rollback only flag
542: * @since 1.1
543: */
544: public boolean getRollbackOnly() {
545: return rollbackOnly;
546: }
547:
548: /**
549: * Accessor for the synchronization object to be notified on transaction completion.
550: * @return The synchronization instance ot be notified on transaction completion.
551: */
552: public Synchronization getSynchronization() {
553: return sync;
554: }
555:
556: /**
557: * Mutator for the setting of nontransactional read.
558: * @param nontransactionalRead Whether to allow nontransactional read operations
559: */
560: public void setNontransactionalRead(boolean nontransactionalRead) {
561: this .nontransactionalRead = nontransactionalRead;
562: }
563:
564: /**
565: * Mutator for the setting of nontransactional write.
566: * @param nontransactionalWrite Whether to allow nontransactional write operations
567: */
568: public synchronized void setNontransactionalWrite(
569: boolean nontransactionalWrite) {
570: this .nontransactionalWrite = nontransactionalWrite;
571: }
572:
573: /**
574: * Mutator for the optimistic transaction setting.
575: * @param optimistic The optimistic transaction setting.
576: */
577: public synchronized void setOptimistic(boolean optimistic) {
578: this .optimistic = optimistic;
579: }
580:
581: /**
582: * Mutator for the setting of restore values.
583: * @param restoreValues Whether to restore values at commit
584: */
585: public synchronized void setRestoreValues(boolean restoreValues) {
586: this .restoreValues = restoreValues;
587: }
588:
589: /**
590: * Mutator for the setting of retain values.
591: * @param retainValues Whether to retain values at commit
592: */
593: public synchronized void setRetainValues(boolean retainValues) {
594: this .retainValues = retainValues;
595: if (retainValues) {
596: nontransactionalRead = true;
597: }
598: }
599:
600: /**
601: * Mutator for the "rollback only" flag. Sets the transaction as for rollback only.
602: * @since 1.1
603: */
604: public void setRollbackOnly() {
605: // Only apply to active transactions
606: if (active) {
607: rollbackOnly = true;
608: }
609: }
610:
611: /**
612: * Mutator for the synchronization object to be notified on transaction completion.
613: * @param sync The synchronization object to be notified on transaction completion
614: */
615: public synchronized void setSynchronization(Synchronization sync) {
616: this .sync = sync;
617: }
618:
619: public void addTransactionEventListener(
620: TransactionEventListener listener) {
621: this .listeners.add(listener);
622: }
623:
624: public void removeTransactionEventListener(
625: TransactionEventListener listener) {
626: this .listeners.remove(listener);
627: }
628:
629: public Map getOptions() {
630: return options;
631: }
632:
633: public void setOption(String option, int value) {
634: options.put(option, new Integer(value));
635: }
636:
637: public void setOption(String option, boolean value) {
638: options.put(option, new Boolean(value));
639: }
640:
641: public void setOption(String option, String value) {
642: options.put(option, value);
643: }
644: }
|