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