View Javadoc

1   /*
2    * $Id: XaTransaction.java 19191 2010-08-25 21:05:23Z tcarlson $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
5    *
6    * The software in this package is published under the terms of the CPAL v1.0
7    * license, a copy of which has been included with this distribution in the
8    * LICENSE.txt file.
9    */
10  
11  package org.mule.transaction;
12  
13  import org.mule.api.MuleContext;
14  import org.mule.api.transaction.TransactionException;
15  import org.mule.config.i18n.CoreMessages;
16  import org.mule.config.i18n.MessageFactory;
17  
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.Map;
21  
22  import javax.transaction.HeuristicRollbackException;
23  import javax.transaction.InvalidTransactionException;
24  import javax.transaction.RollbackException;
25  import javax.transaction.SystemException;
26  import javax.transaction.Transaction;
27  import javax.transaction.TransactionManager;
28  import javax.transaction.xa.XAResource;
29  
30  /**
31   * <code>XaTransaction</code> represents an XA transaction in Mule.
32   */
33  public class XaTransaction extends AbstractTransaction
34  {
35      /**
36       * The inner JTA transaction
37       */
38      protected Transaction transaction = null;
39  
40      /**
41       * Map of enlisted resources
42       */
43      private Map resources = new HashMap();
44  
45      protected TransactionManager txManager;
46  
47      public XaTransaction(MuleContext context)
48      {
49          super(context);
50          this.txManager = context.getTransactionManager();
51      }
52      
53      protected void doBegin() throws TransactionException
54      {
55          if (txManager == null)
56          {
57              throw new IllegalStateException(
58                      CoreMessages.objectNotRegistered("javax.transaction.TransactionManager", "Transaction Manager").getMessage());
59          }
60  
61          try
62          {
63              txManager.begin();
64              synchronized (this)
65              {
66                  transaction = txManager.getTransaction();
67              }
68          }
69          catch (Exception e)
70          {
71              throw new TransactionException(CoreMessages.cannotStartTransaction("XA"), e);
72          }
73      }
74  
75      protected synchronized void doCommit() throws TransactionException
76      {
77          try
78          {
79              /*
80             JTA spec quotes (parts highlighted by AP), the same applies to both TransactionManager and UserTransaction:
81  
82             3.2.2 Completing a Transaction
83             The TransactionManager.commit method completes the transaction currently
84             associated with the calling thread.
85  
86             ****
87             After the commit method returns, the calling thread is not associated with a transaction.
88             ****
89  
90             If the commit method is called when the thread is
91             not associated with any transaction context, the TM throws an exception. In some
92             implementations, the commit operation is restricted to the transaction originator only.
93             If the calling thread is not allowed to commit the transaction, the TM throws an
94             exception.
95             The TransactionManager.rollback method rolls back the transaction associated
96             with the current thread.
97             ****
98             After the rollback method completes, the thread is associated with no transaction.
99             ****
100 
101            And the following block about Transaction (note there's no thread-tx disassociation clause)
102 
103            3.3.3 Transaction Completion
104            The Transaction.commit and Transaction.rollback methods allow the target
105            object to be comitted or rolled back. The calling thread is not required to have the same
106            transaction associated with the thread.
107            If the calling thread is not allowed to commit the transaction, the transaction manager
108            throws an exception.
109 
110 
111            So what it meant was that one can't use Transaction.commit()/rollback(), as it doesn't
112            properly disassociate the thread of execution from the current transaction. There's no
113            JTA API-way to do that after the call, so the thread's transaction is subject to manual
114            recovery process. Instead TransactionManager or UserTransaction must be used.
115             */
116             delistResources();
117             txManager.commit();
118         }
119         catch (RollbackException e)
120         {
121             throw new TransactionRollbackException(CoreMessages.transactionMarkedForRollback(), e);
122         }
123         catch (HeuristicRollbackException e)
124         {
125             throw new TransactionRollbackException(CoreMessages.transactionMarkedForRollback(), e);
126         }
127         catch (Exception e)
128         {
129             throw new IllegalTransactionStateException(CoreMessages.transactionCommitFailed(), e);
130         }
131         finally
132         {
133             /*
134                 MUST nullify XA ref here, otherwise Transaction.getStatus() doesn't match
135                 javax.transaction.Transaction.getStatus(). Must return STATUS_NO_TRANSACTION and not
136                 STATUS_COMMITTED.
137 
138                 TransactionCoordination unbinds the association immediately on this method's exit.
139             */
140             this.transaction = null;
141             closeResources();
142         }
143     }
144 
145     protected void doRollback() throws TransactionRollbackException
146     {
147         try
148         {
149             /*
150            JTA spec quotes (parts highlighted by AP), the same applies to both TransactionManager and UserTransaction:
151 
152            3.2.2 Completing a Transaction
153            The TransactionManager.commit method completes the transaction currently
154            associated with the calling thread.
155 
156            ****
157            After the commit method returns, the calling thread is not associated with a transaction.
158            ****
159 
160            If the commit method is called when the thread is
161            not associated with any transaction context, the TM throws an exception. In some
162            implementations, the commit operation is restricted to the transaction originator only.
163            If the calling thread is not allowed to commit the transaction, the TM throws an
164            exception.
165            The TransactionManager.rollback method rolls back the transaction associated
166            with the current thread.
167            ****
168            After the rollback method completes, the thread is associated with no transaction.
169            ****
170 
171            And the following block about Transaction (note there's no thread-tx disassociation clause)
172 
173            3.3.3 Transaction Completion
174            The Transaction.commit and Transaction.rollback methods allow the target
175            object to be comitted or rolled back. The calling thread is not required to have the same
176            transaction associated with the thread.
177            If the calling thread is not allowed to commit the transaction, the transaction manager
178            throws an exception.
179 
180 
181            So what it meant was that one can't use Transaction.commit()/rollback(), as it doesn't
182            properly disassociate the thread of execution from the current transaction. There's no
183            JTA API-way to do that after the call, so the thread's transaction is subject to manual
184            recovery process. Instead TransactionManager or UserTransaction must be used.
185             */
186             //delistResources();
187             txManager.rollback();
188         }
189         catch (SystemException e)
190         {
191             throw new TransactionRollbackException(e);
192         }
193         catch (Exception e)
194         {
195             throw new TransactionRollbackException(e);
196         }
197         finally
198         {
199             /*
200                 MUST nullify XA ref here, otherwise Transaction.getStatus() doesn't match
201                 javax.transaction.Transaction.getStatus(). Must return STATUS_NO_TRANSACTION and not
202                 STATUS_COMMITTED.
203 
204                 TransactionCoordination unbinds the association immediately on this method's exit.
205             */
206             this.transaction = null;
207             closeResources();
208         }
209     }
210 
211     public synchronized int getStatus() throws TransactionStatusException
212     {
213         if (transaction == null)
214         {
215             return STATUS_NO_TRANSACTION;
216         }
217 
218         try
219         {
220             return transaction.getStatus();
221         }
222         catch (SystemException e)
223         {
224             throw new TransactionStatusException(e);
225         }
226     }
227 
228     public void setRollbackOnly()
229     {
230         if (transaction == null)
231         {
232             throw new IllegalStateException("Current thread is not associated with a transaction.");
233         }
234 
235         try
236         {
237             synchronized (this)
238             {
239                 transaction.setRollbackOnly();
240             }
241         }
242         catch (SystemException e)
243         {
244             throw (IllegalStateException) new IllegalStateException(
245                     "Failed to set transaction to rollback only: " + e.getMessage()
246             ).initCause(e);
247         }
248     }
249 
250     public synchronized Object getResource(Object key)
251     {
252         return resources.get(key);
253     }
254 
255     public synchronized boolean hasResource(Object key)
256     {
257         return resources.containsKey(key);
258     }
259 
260     public synchronized void bindResource(Object key, Object resource) throws TransactionException
261     {
262         if (resources.containsKey(key))
263         {
264             throw new IllegalTransactionStateException(
265                     CoreMessages.transactionResourceAlreadyListedForKey(key));
266         }
267 
268         resources.put(key, resource);
269         
270         if (key == null)
271         {
272             logger.error("Key for bound resource " + resource + " is null");
273         }
274         
275         if (resource instanceof MuleXaObject)
276         {
277             MuleXaObject xaObject = (MuleXaObject) resource;
278             xaObject.enlist();
279         }
280         else if (resource instanceof XAResource)
281         {
282             enlistResource((XAResource) resource);
283         }
284         else
285         {
286             logger.error("Bound resource " + resource + " is neither a MuleXaObject nor XAResource");
287         }
288     }
289 
290 
291     // moved here from connection wrapper
292     public boolean enlistResource(XAResource resource) throws TransactionException
293     {
294         TransactionManager txManager = muleContext.getTransactionManager();
295         try
296         {
297             Transaction jtaTransaction = txManager.getTransaction();
298             if (jtaTransaction == null)
299             {
300                 throw new TransactionException(MessageFactory.createStaticMessage("XATransaction is null"));
301             }
302             return jtaTransaction.enlistResource(resource);
303         }
304         catch (RollbackException e)
305         {
306             throw new TransactionException(e);
307         }
308         catch (SystemException e)
309         {
310             throw new TransactionException(e);
311         }
312     }
313 
314     public boolean delistResource(XAResource resource, int tmflag) throws TransactionException
315     {
316         TransactionManager txManager = muleContext.getTransactionManager();
317         try
318         {
319             Transaction jtaTransaction = txManager.getTransaction();
320             if (jtaTransaction == null)
321             {
322                 throw new TransactionException(CoreMessages.noJtaTransactionAvailable(Thread.currentThread()));
323             }
324             return jtaTransaction.delistResource(resource, tmflag);
325         }
326         catch (SystemException e)
327         {
328             throw new TransactionException(e);
329         }
330     }
331 
332 
333     public String toString()
334     {
335         return transaction == null ? " <n/a>" : transaction.toString();
336     }
337 
338     public Transaction getTransaction()
339     {
340         return transaction;
341     }
342 
343     public boolean isXA()
344     {
345         return true;
346     }
347 
348     public void resume() throws TransactionException
349     {
350         TransactionManager txManager = muleContext.getTransactionManager();
351 
352         if (txManager == null)
353         {
354             throw new IllegalStateException(
355                     CoreMessages.objectNotRegistered("javax.transaction.TransactionManager", "Transaction Manager").getMessage());
356         }
357         try
358         {
359             txManager.resume(transaction);
360         }
361         catch (InvalidTransactionException e)
362         {
363             throw new TransactionException(e);
364         }
365         catch (SystemException e)
366         {
367             throw new TransactionException(e);
368         }
369     }
370 
371     public Transaction suspend() throws TransactionException
372     {
373         TransactionManager txManager = muleContext.getTransactionManager();
374 
375         if (txManager == null)
376         {
377             throw new IllegalStateException(
378                     CoreMessages.objectNotRegistered("javax.transaction.TransactionManager", "Transaction Manager").getMessage());
379         }
380         try
381         {
382             transaction = txManager.suspend();
383         }
384         catch (SystemException e)
385         {
386             throw new TransactionException(e);
387         }
388         return transaction;
389     }
390 
391     protected void delistResources()
392     {
393         Iterator i = resources.entrySet().iterator();
394         while (i.hasNext())
395         {
396             Map.Entry entry = (Map.Entry) i.next();
397             final Object xaObject = entry.getValue();
398             if (xaObject instanceof MuleXaObject)
399             {
400                 //there is need for reuse object
401                 try
402                 {
403                     ((MuleXaObject) xaObject).delist();
404                 }
405                 catch (Exception e)
406                 {
407                     logger.error("Failed to delist resource " + xaObject, e);
408                 }
409             }
410         }
411     }
412 
413     protected void closeResources()
414     {
415         Iterator i = resources.entrySet().iterator();
416         while (i.hasNext())
417         {
418             Map.Entry entry = (Map.Entry) i.next();
419             final Object value = entry.getValue();
420             if (value instanceof MuleXaObject)
421             {
422                 MuleXaObject xaObject = (MuleXaObject) value;
423                 if (!xaObject.isReuseObject())
424                 {
425                     try
426                     {
427                         xaObject.close();
428                         i.remove();
429                     }
430                     catch (Exception e)
431                     {
432                         logger.error("Failed to close resource " + xaObject, e);
433                     }
434                 }
435             }
436         }
437     }
438 
439     public static interface MuleXaObject
440     {
441 
442         void close() throws Exception;
443 
444         void setReuseObject(boolean reuseObject);
445 
446         boolean isReuseObject();
447 
448         boolean enlist() throws TransactionException;
449         
450         boolean delist() throws Exception;
451 
452         /**
453          * Get XAConnection or XASession from wrapper / proxy
454          *
455          * @return return javax.sql.XAConnection for jdbc or javax.jms.XASession for jms
456          */
457         Object getTargetObject();
458 
459         String SET_REUSE_OBJECT_METHOD_NAME = "setReuseObject";
460         String IS_REUSE_OBJECT_METHOD_NAME = "isReuseObject";
461         String DELIST_METHOD_NAME = "delist";
462         String ENLIST_METHOD_NAME = "enlist";
463         String GET_TARGET_OBJECT_METHOD_NAME = "getTargetObject";
464         String CLOSE_METHOD_NAME = "close";
465     }
466 
467 }