1   /*
2    * $Id: AbstractTxThreadAssociationTestCase.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.tck;
12  
13  import org.mule.DefaultExceptionStrategy;
14  import org.mule.api.transaction.TransactionCallback;
15  import org.mule.api.transaction.TransactionConfig;
16  import org.mule.api.transaction.TransactionManagerFactory;
17  import org.mule.transaction.MuleTransactionConfig;
18  import org.mule.transaction.TransactionTemplate;
19  import org.mule.transaction.XaTransaction;
20  import org.mule.transaction.XaTransactionFactory;
21  
22  import javax.transaction.Status;
23  import javax.transaction.Transaction;
24  import javax.transaction.TransactionManager;
25  
26  /**
27   * Validate certain expectations when working with JTA API. It is called to catch discrepancies in TM implementations
28   * and alert early. Subclasses are supposed to plug in specific transaction managers for tests.
29   */
30  public abstract class AbstractTxThreadAssociationTestCase extends AbstractMuleTestCase
31  {
32      /* To allow access from the dead TX threads we spawn. */
33      private TransactionManager tm;
34      protected static final int TRANSACTION_TIMEOUT_SECONDS = 3;
35  
36      protected void doSetUp() throws Exception
37      {
38          super.doSetUp();
39          TransactionManagerFactory factory = getTransactionManagerFactory();
40          tm = factory.create();
41          assertNotNull("Transaction Manager should be available.", tm);
42          assertNull("There sould be no current transaction associated.", tm.getTransaction());
43      }
44  
45      public void testTxHandleCommitKeepsThreadAssociation() throws Exception
46      {
47          // don't wait for ages, has to be set before TX is begun
48          tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
49          tm.begin();
50  
51          Transaction tx = tm.getTransaction();
52          assertNotNull("Transaction should have started.", tx);
53          assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus());
54  
55          tx.commit();
56  
57          tx = tm.getTransaction();
58          assertNotNull("Committing via TX handle should NOT disassociated TX from the current thread.",
59                        tx);
60          assertEquals("TX status should have been COMMITTED.", Status.STATUS_COMMITTED, tx.getStatus());
61  
62          // Remove the TX-thread association. The only public API to achieve it is suspend(),
63          // technically we never resume the same transaction (TX forget).
64          Transaction suspended = tm.suspend();
65          assertTrue("Wrong TX suspended?.", suspended.equals(tx));
66          assertNull("TX should've been disassociated from the thread.", tm.getTransaction());
67  
68          // should be no-op and never fail
69          tm.resume(null);
70  
71          // ensure we don't have any TX-Thread association lurking around a main thread
72          assertNull(tm.getTransaction());
73      }
74  
75      public void testTxManagerCommitDissassociatesThread() throws Exception
76      {
77          // don't wait for ages, has to be set before TX is begun
78          tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
79          tm.begin();
80  
81          Transaction tx = tm.getTransaction();
82          assertNotNull("Transaction should have started.", tx);
83          assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus());
84  
85          tm.commit();
86  
87          assertNull("Committing via TX Manager should have disassociated TX from the current thread.",
88                     tm.getTransaction());
89      }
90  
91      public void testTxManagerRollbackDissassociatesThread() throws Exception
92      {
93          // don't wait for ages, has to be set before TX is begun
94          tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
95          tm.begin();
96  
97          Transaction tx = tm.getTransaction();
98          assertNotNull("Transaction should have started.", tx);
99          assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus());
100 
101         tm.rollback();
102 
103         assertNull("Committing via TX Manager should have disassociated TX from the current thread.",
104                    tm.getTransaction());
105     }
106 
107     /**
108      * AlwaysBegin action suspends current transaction and begins a new one.
109      *
110      * @throws Exception if any error
111      */
112     public void testAlwaysBeginXaTransactionSuspendResume() throws Exception
113     {
114         muleContext.setTransactionManager(tm);
115         assertNull("There sould be no current transaction associated.", tm.getTransaction());
116 
117         // don't wait for ages, has to be set before TX is begun
118         tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
119 
120         // this is one component with a TX always begin
121         TransactionConfig config = new MuleTransactionConfig();
122         config.setFactory(new XaTransactionFactory());
123         config.setAction(TransactionConfig.ACTION_ALWAYS_BEGIN);
124         TransactionTemplate template = new TransactionTemplate(config, new DefaultExceptionStrategy(),muleContext);
125 
126         // and the callee component which should begin new transaction, current must be suspended
127         final TransactionConfig nestedConfig = new MuleTransactionConfig();
128         nestedConfig.setFactory(new XaTransactionFactory());
129         nestedConfig.setAction(TransactionConfig.ACTION_ALWAYS_BEGIN);
130 
131         // start the call chain
132         template.execute(new TransactionCallback()
133         {
134             public Object doInTransaction() throws Exception
135             {
136                 // the callee executes within its own TX template, but uses the same global XA transaction,
137                 // bound to the current thread of execution via a ThreadLocal
138                 TransactionTemplate nestedTemplate =
139                         new TransactionTemplate(nestedConfig, new DefaultExceptionStrategy(),muleContext);
140                 final Transaction firstTx = tm.getTransaction();
141                 assertNotNull(firstTx);
142                 assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
143                 return nestedTemplate.execute(new TransactionCallback()
144                 {
145                     public Object doInTransaction() throws Exception
146                     {
147                         Transaction secondTx = tm.getTransaction();
148                         assertNotNull(secondTx);
149                         assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
150                         assertEquals(secondTx.getStatus(), Status.STATUS_ACTIVE);
151                         try
152                         {
153                             tm.resume(firstTx);
154                             fail("Second transaction must be active");
155                         }
156                         catch (java.lang.IllegalStateException e)
157                         {
158                             // expected
159 
160                             //Thrown if the thread is already associated with another transaction.
161                             //Second tx is associated with the current thread
162                         }
163                         try
164                         {
165                             Transaction currentTx = tm.suspend();
166                             assertTrue(currentTx.equals(secondTx));
167                             tm.resume(firstTx);
168                             assertEquals(firstTx, tm.getTransaction());
169                             assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
170                             assertEquals(secondTx.getStatus(), Status.STATUS_ACTIVE);
171                             Transaction a = tm.suspend();
172                             assertTrue(a.equals(firstTx));
173                             tm.resume(secondTx);
174                         }
175                         catch (Exception e)
176                         {
177                             fail("Error: " + e);
178                         }
179 
180                         // do not care about the return really
181                         return null;
182                     }
183                 });
184             }
185         });
186         assertNull("Committing via TX Manager should have disassociated TX from the current thread.",
187                    tm.getTransaction());
188     }
189 
190     /**
191      * NONE action suspends current transaction and begins a new one.
192      *
193      * @throws Exception if any error
194      */
195     public void testNoneXaTransactionSuspendResume() throws Exception
196     {
197         muleContext.setTransactionManager(tm);
198         assertNull("There sould be no current transaction associated.", tm.getTransaction());
199 
200         // don't wait for ages, has to be set before TX is begun
201         tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
202 
203         // this is one component with a TX always begin
204         TransactionConfig config = new MuleTransactionConfig();
205         config.setFactory(new XaTransactionFactory());
206         config.setAction(TransactionConfig.ACTION_ALWAYS_BEGIN);
207         TransactionTemplate template = new TransactionTemplate(config, new DefaultExceptionStrategy(), muleContext);
208 
209         // and the callee component which should begin new transaction, current must be suspended
210         final TransactionConfig nestedConfig = new MuleTransactionConfig();
211         nestedConfig.setFactory(new XaTransactionFactory());
212         nestedConfig.setAction(TransactionConfig.ACTION_NONE);
213 
214         // start the call chain
215         template.execute(new TransactionCallback()
216         {
217             public Object doInTransaction() throws Exception
218             {
219                 // the callee executes within its own TX template, but uses the same global XA transaction,
220                 // bound to the current thread of execution via a ThreadLocal
221                 TransactionTemplate nestedTemplate =
222                         new TransactionTemplate(nestedConfig, new DefaultExceptionStrategy(), muleContext);
223                 final Transaction firstTx = tm.getTransaction();
224                 assertNotNull(firstTx);
225                 assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
226                 return nestedTemplate.execute(new TransactionCallback()
227                 {
228                     public Object doInTransaction() throws Exception
229                     {
230                         Transaction secondTx = tm.getTransaction();
231                         assertNull(secondTx);
232                         assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
233                         try
234                         {
235                             tm.resume(firstTx);
236                             assertEquals(firstTx, tm.getTransaction());
237                             assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
238                             Transaction a = tm.suspend();
239                             assertTrue(a.equals(firstTx));
240                         }
241                         catch (Exception e)
242                         {
243                             fail("Error: " + e);
244                         }
245 
246                         // do not care about the return really
247                         return null;
248                     }
249                 });
250             }
251         });
252         assertNull("Committing via TX Manager should have disassociated TX from the current thread.",
253                    tm.getTransaction());
254     }
255     
256     /**
257      * This is a former XaTransactionTestCase.
258      *
259      * @throws Exception in case of any error
260      */
261     public void testXaTransactionTermination() throws Exception
262     {
263         muleContext.setTransactionManager(tm);
264         assertNull("There sould be no current transaction associated.", tm.getTransaction());
265 
266         // don't wait for ages, has to be set before TX is begun
267         tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
268 
269         XaTransaction muleTx = new XaTransaction(tm);
270         assertFalse(muleTx.isBegun());
271         assertEquals(Status.STATUS_NO_TRANSACTION, muleTx.getStatus());
272         muleTx.begin();
273 
274         assertTrue(muleTx.isBegun());
275 
276         muleTx.commit();
277 
278         Transaction jtaTx = tm.getTransaction();
279         assertNull("Committing via TX Manager should have disassociated TX from the current thread.", jtaTx);
280         assertEquals(Status.STATUS_NO_TRANSACTION, muleTx.getStatus());
281     }
282 
283     /**
284      * This is a former TransactionTemplateTestCase.
285      * http://mule.mulesource.org/jira/browse/MULE-1494
286      *
287      * @throws Exception in case of any error
288      */
289     public void testNoNestedTxStarted() throws Exception
290     {
291         muleContext.setTransactionManager(tm);
292         assertNull("There sould be no current transaction associated.", tm.getTransaction());
293 
294         // don't wait for ages, has to be set before TX is begun
295         tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
296 
297         // this is one service with a TX always begin
298         TransactionConfig config = new MuleTransactionConfig();
299         config.setFactory(new XaTransactionFactory());
300         config.setAction(TransactionConfig.ACTION_ALWAYS_BEGIN);
301         TransactionTemplate template = new TransactionTemplate(config, new DefaultExceptionStrategy(), muleContext);
302 
303         // and the callee service which should join the current XA transaction, not begin a nested one
304         final TransactionConfig nestedConfig = new MuleTransactionConfig();
305         nestedConfig.setFactory(new XaTransactionFactory());
306         nestedConfig.setAction(TransactionConfig.ACTION_BEGIN_OR_JOIN);
307 
308         // start the call chain
309         template.execute(new TransactionCallback()
310         {
311             public Object doInTransaction() throws Exception
312             {
313                 // the callee executes within its own TX template, but uses the same global XA transaction,
314                 // bound to the current thread of execution via a ThreadLocal
315                 TransactionTemplate nestedTemplate =
316                         new TransactionTemplate(nestedConfig, new DefaultExceptionStrategy(), muleContext);
317                 return nestedTemplate.execute(new TransactionCallback()
318                 {
319                     public Object doInTransaction() throws Exception
320                     {
321                         // do not care about the return really
322                         return null;
323                     }
324                 });
325             }
326         });
327     }
328 
329 
330     protected TransactionManager getTransactionManager()
331     {
332         return tm;
333     }
334 
335     protected abstract TransactionManagerFactory getTransactionManagerFactory();
336 
337 }