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