001: /*
002: * Created on Nov 10, 2004
003: */
004: package uk.org.ponder.transaction;
005:
006: import uk.org.ponder.util.Logger;
007: import uk.org.ponder.util.UniversalRuntimeException;
008:
009: /**
010: * @author Antranig Basman (antranig@caret.cam.ac.uk)
011: *
012: */
013: public class NestedTransactionWrapper implements Transaction {
014: private TransactionThreadMap transmap;
015: private Transaction target;
016: private Transaction listener;
017:
018: /** Given factories for the main target and listener "flat" transactions
019: * that will be wrapped by the required NestedTransactionFactory,
020: * inspect the current threadmap and return any existing NestedTransaction
021: * which is in progress with its use count updated, or create a new one
022: * using the factories if requred and store it.
023: * @param mainfactory A factory producing the main logic flat transaction
024: * to be wrapped.
025: * @param listenerfactory A factory producing a "listener" transaction
026: * that requires (synchronous) notification AFTER successful conclusion
027: * of transation events. This parameter may be <code>null</code>
028: */
029: public static NestedTransactionWrapper beginNestedTransaction(
030: TransactionFactory mainfactory,
031: TransactionFactory listenerfactory,
032: TransactionThreadMap transmap) {
033: NestedTransactionWrapper togo = (NestedTransactionWrapper) transmap
034: .getTransaction();
035: if (togo == null) {
036: Transaction nested = mainfactory.beginTransaction();
037: togo = new NestedTransactionWrapper(nested,
038: listenerfactory == null ? null : listenerfactory
039: .beginTransaction(), transmap);
040: } else {
041: togo.increment();
042: }
043: return togo;
044: }
045:
046: /**
047: * Accepts two Transactions objects, the first being the "important" concrete
048: * transaction object returned by the application, the second being a
049: * subsidiary listener. Both will be notified of transaction events, the
050: * listener receiving notification second and with lower priority in the case
051: * of exceptions.
052: *
053: * @param target
054: * @param listener
055: */
056: public NestedTransactionWrapper(Transaction target,
057: Transaction listener, TransactionThreadMap transmap) {
058: this .target = target;
059: this .listener = listener;
060: this .transmap = transmap;
061: transmap.enterTransaction(this );
062: }
063:
064: static final int ROLLED_BACK = -1;
065:
066: int nestingdepth = 1;
067:
068: public void increment() {
069: ++nestingdepth;
070: }
071:
072: public void commit() {
073: // A commit has no effect except at the very highest stack level attempting.
074: // Two-phase commit is not supported, to the extent that any exception
075: // occuring within a commit will inevitably cause all correct client
076: // code to perform a rollback within a finally block. No point writing
077: // any extra code here to deal with it.
078: if (nestingdepth <= 0) {
079: throw new UniversalRuntimeException(
080: "Error: attempting to commit inactive transaction with count "
081: + nestingdepth);
082: }
083: --nestingdepth;
084: if (nestingdepth == 0) {
085: try {
086: target.commit();
087: transmap.endTransaction();
088: if (listener != null) {
089: listener.commit();
090: }
091: } catch (Throwable t) {
092: throw UniversalRuntimeException.accumulate(t,
093: "Error committing transaction");
094: }
095: }
096: }
097:
098: public void rollback() {
099: if (nestingdepth == 0) {
100: Logger.log
101: .fatal("Fatal logic error: Attempting to roll back transaction which has already been committed");
102: return;
103: }
104: // Having rolled back the transaction once, it is removed from the map
105: // so that a fresh transaction may be started. However, further people
106: // up the stack may attempt independently to roll the transaction back
107: // again from their local copies, this will have no further effect.
108: if (nestingdepth != ROLLED_BACK) {
109: try {
110: target.rollback();
111: } catch (Throwable t) {
112: // THIS METHOD SHOULD NEVER THROW!!!
113: Logger.log.fatal("Error rolling back transaction");
114: } finally {
115: nestingdepth = ROLLED_BACK;
116: transmap.endTransaction();
117: if (listener != null) {
118: listener.rollback();
119: }
120: }
121: }
122: }
123:
124: public String toString() {
125: return target.toString();
126: }
127:
128: public int hashCode() {
129: return target.hashCode();
130: }
131:
132: // we can compare equal either to another nested wrapper mapping
133: // the same target, or the same target itself.
134: public boolean equals(Object other) {
135: if (other instanceof NestedTransactionWrapper) {
136: return target
137: .equals(((NestedTransactionWrapper) other).target);
138: } else
139: return target.equals(other);
140: }
141: }
|