View Javadoc

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