001: /*
002: * $Id: TransactionTemplate.java 10590 2008-01-29 01:39:47Z tcarlson $
003: * --------------------------------------------------------------------------------------
004: * Copyright (c) MuleSource, Inc. All rights reserved. http://www.mulesource.com
005: *
006: * The software in this package is published under the terms of the CPAL v1.0
007: * license, a copy of which has been included with this distribution in the
008: * LICENSE.txt file.
009: */
010:
011: package org.mule.transaction;
012:
013: import org.mule.api.MuleContext;
014: import org.mule.api.transaction.Transaction;
015: import org.mule.api.transaction.TransactionCallback;
016: import org.mule.api.transaction.TransactionConfig;
017: import org.mule.api.transaction.TransactionException;
018: import org.mule.config.i18n.CoreMessages;
019:
020: import java.beans.ExceptionListener;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024:
025: public class TransactionTemplate {
026: private static final Log logger = LogFactory
027: .getLog(TransactionTemplate.class);
028:
029: private final TransactionConfig config;
030: private final ExceptionListener exceptionListener;
031: private final MuleContext context;
032:
033: public TransactionTemplate(TransactionConfig config,
034: ExceptionListener listener, MuleContext context) {
035: this .config = config;
036: exceptionListener = listener;
037: this .context = context;
038: }
039:
040: public Object execute(TransactionCallback callback)
041: throws Exception {
042: if (config == null) {
043: return callback.doInTransaction();
044: } else {
045: byte action = config.getAction();
046: Transaction tx = TransactionCoordination.getInstance()
047: .getTransaction();
048: Transaction suspendedXATx = null;
049:
050: if (action == TransactionConfig.ACTION_NONE && tx != null) {
051: //TODO RM*: I'm not sure there is any value in throwing an exection here, since
052: //there may be a transaction in progress but has nothing to to with this invocation
053: //so maybe we just process outside the tx. Not sure yet
054: //return callback.doInTransaction();
055:
056: /*
057: Reply from AP: There is value at the moment, at least in having fewer surprises
058: with a more explicit config. Current behavior is that of 'Never' TX attribute
059: in Java EE parlance.
060:
061: What you refer to, however, is the 'Not Supported' TX behavior. A SUSPEND is performed
062: in this case with (optional) RESUME later.
063:
064: */
065:
066: throw new IllegalTransactionStateException(CoreMessages
067: .transactionAvailableButActionIs("None"));
068: } else if (action == TransactionConfig.ACTION_ALWAYS_BEGIN
069: && tx != null) {
070: if (logger.isDebugEnabled()) {
071: logger
072: .debug("Transaction action is ACTION_ALWAYS_BEGIN, "
073: + "current TX: " + tx);
074: }
075: if (tx.isXA()) {
076: // suspend current transaction
077: suspendedXATx = tx;
078: suspendXATransaction(suspendedXATx);
079: } else {
080: // commit/rollback
081: resolveTransaction(tx);
082: }
083: //transaction will be started below
084: tx = null;
085: } else if (action == TransactionConfig.ACTION_ALWAYS_JOIN
086: && tx == null) {
087: throw new IllegalTransactionStateException(
088: CoreMessages
089: .transactionNotAvailableButActionIs("Always Join"));
090: }
091:
092: if (action == TransactionConfig.ACTION_ALWAYS_BEGIN
093: || (action == TransactionConfig.ACTION_BEGIN_OR_JOIN && tx == null)) {
094: logger.debug("Beginning transaction");
095: tx = config.getFactory().beginTransaction(context);
096: logger.debug("Transaction successfully started: " + tx);
097: } else {
098: tx = null;
099: }
100: try {
101: Object result = callback.doInTransaction();
102: if (tx != null) {
103: resolveTransaction(tx);
104: if (suspendedXATx != null) {
105: resumeXATransaction(suspendedXATx);
106: tx = suspendedXATx;
107: }
108: }
109: return result;
110: } catch (Exception e) {
111: if (exceptionListener != null) {
112: logger
113: .info("Exception Caught in Transaction template. Handing off to exception handler: "
114: + exceptionListener);
115: exceptionListener.exceptionThrown(e);
116: } else {
117: logger
118: .info("Exception Caught in Transaction template without any exception listeners defined, exception is rethrown.");
119: if (tx != null) {
120: tx.setRollbackOnly();
121: }
122: }
123: if (tx != null) {
124: // The exception strategy can choose to route exception
125: // messages
126: // as part of the current transaction. So only rollback the
127: // tx
128: // if it has been marked for rollback (which is the default
129: // case in the
130: // AbstractExceptionListener)
131: if (tx.isRollbackOnly()) {
132: logger
133: .debug(
134: "Exception caught: rollback transaction",
135: e);
136: }
137: resolveTransaction(tx);
138: if (suspendedXATx != null) {
139: resumeXATransaction(suspendedXATx);
140: }
141: }
142: // we've handled this exception above. just return null now
143: if (exceptionListener != null) {
144: return null;
145: } else {
146: throw e;
147: }
148: } catch (Error e) {
149: if (tx != null) {
150: logger.info("Error caught, rolling back TX " + tx,
151: e);
152: tx.rollback();
153: }
154: throw e;
155: }
156: }
157: }
158:
159: protected void resolveTransaction(Transaction tx)
160: throws TransactionException {
161: if (tx.isRollbackOnly()) {
162: logger
163: .debug("Transaction has been marked rollbackOnly, rolling it back: "
164: + tx);
165: tx.rollback();
166: } else {
167: logger.debug("Committing transaction " + tx);
168: tx.commit();
169: }
170: }
171:
172: protected void suspendXATransaction(Transaction tx)
173: throws TransactionException {
174: if (logger.isDebugEnabled()) {
175: logger.debug("Suspending " + tx);
176: }
177:
178: tx.suspend();
179:
180: if (logger.isDebugEnabled()) {
181: logger.debug("Successfully suspended " + tx);
182: logger
183: .debug("Unbinding the following TX from the current context: "
184: + tx);
185: }
186:
187: TransactionCoordination.getInstance().unbindTransaction(tx);
188: }
189:
190: protected void resumeXATransaction(Transaction tx)
191: throws TransactionException {
192: if (logger.isDebugEnabled()) {
193: logger.debug("Re-binding and Resuming " + tx);
194: }
195:
196: TransactionCoordination.getInstance().bindTransaction(tx);
197: tx.resume();
198: }
199:
200: }
|