View Javadoc

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