001: /*
002: * $Id: XaTransaction.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.MuleServer;
014: import org.mule.api.transaction.TransactionException;
015: import org.mule.config.i18n.CoreMessages;
016: import org.mule.config.i18n.MessageFactory;
017:
018: import java.util.HashMap;
019: import java.util.Iterator;
020: import java.util.Map;
021:
022: import javax.transaction.HeuristicRollbackException;
023: import javax.transaction.InvalidTransactionException;
024: import javax.transaction.RollbackException;
025: import javax.transaction.SystemException;
026: import javax.transaction.Transaction;
027: import javax.transaction.TransactionManager;
028: import javax.transaction.xa.XAResource;
029:
030: /**
031: * <code>XaTransaction</code> represents an XA transaction in Mule.
032: */
033: public class XaTransaction extends AbstractTransaction {
034: /**
035: * The inner JTA transaction
036: */
037: private Transaction transaction = null;
038:
039: /**
040: * Map of enlisted resources
041: */
042: private Map resources = new HashMap();
043:
044: private TransactionManager txManager;
045:
046: public XaTransaction(TransactionManager txManager) {
047: this .txManager = txManager;
048: }
049:
050: protected void doBegin() throws TransactionException {
051: if (txManager == null) {
052: throw new IllegalStateException(CoreMessages
053: .objectNotRegistered("Transaction Manager",
054: "Transaction Manager").getMessage());
055: }
056:
057: try {
058: txManager.begin();
059: synchronized (this ) {
060: transaction = txManager.getTransaction();
061: }
062: } catch (Exception e) {
063: throw new TransactionException(CoreMessages
064: .cannotStartTransaction("XA"), e);
065: }
066: }
067:
068: protected synchronized void doCommit() throws TransactionException {
069: try {
070: /*
071: JTA spec quotes (parts highlighted by AP), the same applies to both TransactionManager and UserTransaction:
072:
073: 3.2.2 Completing a Transaction
074: The TransactionManager.commit method completes the transaction currently
075: associated with the calling thread.
076:
077: ****
078: After the commit method returns, the calling thread is not associated with a transaction.
079: ****
080:
081: If the commit method is called when the thread is
082: not associated with any transaction context, the TM throws an exception. In some
083: implementations, the commit operation is restricted to the transaction originator only.
084: If the calling thread is not allowed to commit the transaction, the TM throws an
085: exception.
086: The TransactionManager.rollback method rolls back the transaction associated
087: with the current thread.
088: ****
089: After the rollback method completes, the thread is associated with no transaction.
090: ****
091:
092: And the following block about Transaction (note there's no thread-tx disassociation clause)
093:
094: 3.3.3 Transaction Completion
095: The Transaction.commit and Transaction.rollback methods allow the target
096: object to be comitted or rolled back. The calling thread is not required to have the same
097: transaction associated with the thread.
098: If the calling thread is not allowed to commit the transaction, the transaction manager
099: throws an exception.
100:
101:
102: So what it meant was that one can't use Transaction.commit()/rollback(), as it doesn't
103: properly disassociate the thread of execution from the current transaction. There's no
104: JTA API-way to do that after the call, so the thread's transaction is subject to manual
105: recovery process. Instead TransactionManager or UserTransaction must be used.
106: */
107: // TODO AP check this way to get to TM really works in Mule 2
108: TransactionManager txManager = MuleServer.getMuleContext()
109: .getTransactionManager();
110: delistResources();
111: txManager.commit();
112: } catch (RollbackException e) {
113: throw new TransactionRollbackException(CoreMessages
114: .transactionMarkedForRollback(), e);
115: } catch (HeuristicRollbackException e) {
116: throw new TransactionRollbackException(CoreMessages
117: .transactionMarkedForRollback(), e);
118: } catch (Exception e) {
119: throw new IllegalTransactionStateException(CoreMessages
120: .transactionCommitFailed(), e);
121: } finally {
122: /*
123: MUST nullify XA ref here, otherwise Transaction.getStatus() doesn't match
124: javax.transaction.Transaction.getStatus(). Must return STATUS_NO_TRANSACTION and not
125: STATUS_COMMITTED.
126:
127: TransactionCoordination unbinds the association immediately on this method's exit.
128: */
129: this .transaction = null;
130: closeResources();
131: }
132: }
133:
134: protected void doRollback() throws TransactionRollbackException {
135: try {
136: /*
137: JTA spec quotes (parts highlighted by AP), the same applies to both TransactionManager and UserTransaction:
138:
139: 3.2.2 Completing a Transaction
140: The TransactionManager.commit method completes the transaction currently
141: associated with the calling thread.
142:
143: ****
144: After the commit method returns, the calling thread is not associated with a transaction.
145: ****
146:
147: If the commit method is called when the thread is
148: not associated with any transaction context, the TM throws an exception. In some
149: implementations, the commit operation is restricted to the transaction originator only.
150: If the calling thread is not allowed to commit the transaction, the TM throws an
151: exception.
152: The TransactionManager.rollback method rolls back the transaction associated
153: with the current thread.
154: ****
155: After the rollback method completes, the thread is associated with no transaction.
156: ****
157:
158: And the following block about Transaction (note there's no thread-tx disassociation clause)
159:
160: 3.3.3 Transaction Completion
161: The Transaction.commit and Transaction.rollback methods allow the target
162: object to be comitted or rolled back. The calling thread is not required to have the same
163: transaction associated with the thread.
164: If the calling thread is not allowed to commit the transaction, the transaction manager
165: throws an exception.
166:
167:
168: So what it meant was that one can't use Transaction.commit()/rollback(), as it doesn't
169: properly disassociate the thread of execution from the current transaction. There's no
170: JTA API-way to do that after the call, so the thread's transaction is subject to manual
171: recovery process. Instead TransactionManager or UserTransaction must be used.
172: */
173: TransactionManager txManager = MuleServer.getMuleContext()
174: .getTransactionManager();
175: delistResources();
176: txManager.rollback();
177: } catch (SystemException e) {
178: throw new TransactionRollbackException(e);
179: } catch (Exception e) {
180: throw new TransactionRollbackException(e);
181: } finally {
182: /*
183: MUST nullify XA ref here, otherwise Transaction.getStatus() doesn't match
184: javax.transaction.Transaction.getStatus(). Must return STATUS_NO_TRANSACTION and not
185: STATUS_COMMITTED.
186:
187: TransactionCoordination unbinds the association immediately on this method's exit.
188: */
189: this .transaction = null;
190: closeResources();
191: }
192: }
193:
194: public synchronized int getStatus()
195: throws TransactionStatusException {
196: if (transaction == null) {
197: return STATUS_NO_TRANSACTION;
198: }
199:
200: try {
201: return transaction.getStatus();
202: } catch (SystemException e) {
203: throw new TransactionStatusException(e);
204: }
205: }
206:
207: public void setRollbackOnly() {
208: if (transaction == null) {
209: throw new IllegalStateException(
210: "Current thread is not associated with a transaction.");
211: }
212:
213: try {
214: synchronized (this ) {
215: transaction.setRollbackOnly();
216: }
217: } catch (SystemException e) {
218: throw (IllegalStateException) new IllegalStateException(
219: "Failed to set transaction to rollback only: "
220: + e.getMessage()).initCause(e);
221: }
222: }
223:
224: public synchronized Object getResource(Object key) {
225: return resources.get(key);
226: }
227:
228: public synchronized boolean hasResource(Object key) {
229: return resources.containsKey(key);
230: }
231:
232: public synchronized void bindResource(Object key, Object resource)
233: throws TransactionException {
234: if (resources.containsKey(key)) {
235: throw new IllegalTransactionStateException(CoreMessages
236: .transactionResourceAlreadyListedForKey(key));
237: }
238:
239: resources.put(key, resource);
240: }
241:
242: // moved here from connection wrapper
243: public boolean enlistResource(XAResource resource)
244: throws TransactionException {
245: TransactionManager txManager = MuleServer.getMuleContext()
246: .getTransactionManager();
247: try {
248: Transaction jtaTransaction = txManager.getTransaction();
249: if (jtaTransaction == null) {
250: throw new TransactionException(MessageFactory
251: .createStaticMessage("XATransaction is null"));
252: }
253: return jtaTransaction.enlistResource(resource);
254: } catch (RollbackException e) {
255: throw new TransactionException(e);
256: } catch (SystemException e) {
257: throw new TransactionException(e);
258: }
259: }
260:
261: public boolean delistResource(XAResource resource, int tmflag)
262: throws TransactionException {
263: TransactionManager txManager = MuleServer.getMuleContext()
264: .getTransactionManager();
265: try {
266: Transaction jtaTransaction = txManager.getTransaction();
267: if (jtaTransaction == null) {
268: throw new TransactionException(CoreMessages
269: .noJtaTransactionAvailable(Thread
270: .currentThread()));
271: }
272: return jtaTransaction.delistResource(resource, tmflag);
273: } catch (SystemException e) {
274: throw new TransactionException(e);
275: }
276: }
277:
278: public String toString() {
279: return transaction == null ? " <n/a>" : transaction.toString();
280: }
281:
282: public Transaction getTransaction() {
283: return transaction;
284: }
285:
286: public boolean isXA() {
287: return true;
288: }
289:
290: public void resume() throws TransactionException {
291: TransactionManager txManager = MuleServer.getMuleContext()
292: .getTransactionManager();
293:
294: if (txManager == null) {
295: throw new IllegalStateException(CoreMessages
296: .objectNotRegistered("TransactionManager",
297: "Transaction Manager").getMessage());
298: }
299: try {
300: txManager.resume(transaction);
301: } catch (InvalidTransactionException e) {
302: throw new TransactionException(e);
303: } catch (SystemException e) {
304: throw new TransactionException(e);
305: }
306: }
307:
308: public Transaction suspend() throws TransactionException {
309: TransactionManager txManager = MuleServer.getMuleContext()
310: .getTransactionManager();
311:
312: if (txManager == null) {
313: throw new IllegalStateException(CoreMessages
314: .objectNotRegistered("TransactionManager",
315: "Transaction Manager").getMessage());
316: }
317: try {
318: transaction = txManager.suspend();
319: } catch (SystemException e) {
320: throw new TransactionException(e);
321: }
322: return transaction;
323: }
324:
325: protected void delistResources() {
326: Iterator i = resources.entrySet().iterator();
327: while (i.hasNext()) {
328: Map.Entry entry = (Map.Entry) i.next();
329: if (entry.getValue() instanceof MuleXaObject) {
330: //there is need for reuse object
331: try {
332: ((MuleXaObject) entry.getValue()).delist();
333: } catch (Exception e) {
334: logger.error("Cann't delist resource "
335: + entry.getValue() + " " + e);
336: }
337: }
338: }
339: }
340:
341: protected void closeResources() {
342: Iterator i = resources.entrySet().iterator();
343: while (i.hasNext()) {
344: Map.Entry entry = (Map.Entry) i.next();
345: if (entry.getValue() instanceof MuleXaObject) {
346: MuleXaObject xaObject = (MuleXaObject) entry.getValue();
347: if (!xaObject.isReuseObject()) {
348: try {
349: xaObject.close();
350: } catch (Exception e) {
351: logger.error("Cann't close resource "
352: + xaObject);
353: }
354: }
355: }
356: }
357: }
358:
359: public static interface MuleXaObject {
360:
361: void close() throws Exception;
362:
363: void setReuseObject(boolean reuseObject);
364:
365: boolean isReuseObject();
366:
367: //enlist is called indirectly
368:
369: boolean delist() throws Exception;
370:
371: /**
372: * Get XAConnection or XASession from wrapper / proxy
373: *
374: * @return return javax.sql.XAConnection for jdbc or javax.jms.XASession for jms
375: */
376: Object getTargetObject();
377:
378: String SET_REUSE_OBJECT_METHOD_NAME = "setReuseObject";
379: String IS_REUSE_OBJECT_METHOD_NAME = "isReuseObject";
380: String DELIST_METHOD_NAME = "delist";
381: String GET_TARGET_OBJECT_METHOD_NAME = "getTargetObject";
382: String CLOSE_METHOD_NAME = "close";
383: }
384:
385: }
|