View Javadoc

1   /*
2    * $Id: AbstractTxThreadAssociationTestCase.java 22654 2011-08-12 07:32:57Z mike.schilling $
3    * --------------------------------------------------------------------------------------
4    * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.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.api.transaction.TransactionCallback;
14  import org.mule.api.transaction.TransactionConfig;
15  import org.mule.api.transaction.TransactionManagerFactory;
16  import org.mule.tck.junit4.AbstractMuleContextTestCase;
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  import org.junit.Test;
27  
28  import static org.junit.Assert.assertEquals;
29  import static org.junit.Assert.assertFalse;
30  import static org.junit.Assert.assertNotNull;
31  import static org.junit.Assert.assertNull;
32  import static org.junit.Assert.assertTrue;
33  import static org.junit.Assert.fail;
34  
35  /**
36   * Validate certain expectations when working with JTA API. It is called to catch discrepancies in TM implementations
37   * and alert early. Subclasses are supposed to plug in specific transaction managers for tests.
38   */
39  public abstract class AbstractTxThreadAssociationTestCase extends AbstractMuleContextTestCase
40  {
41      /* To allow access from the dead TX threads we spawn. */
42      private TransactionManager tm;
43      protected static final int TRANSACTION_TIMEOUT_SECONDS = 3;
44  
45      protected void doSetUp() throws Exception
46      {
47          super.doSetUp();
48          TransactionManagerFactory factory = getTransactionManagerFactory();
49          tm = factory.create(muleContext.getConfiguration());
50          assertNotNull("Transaction Manager should be available.", tm);
51          assertNull("There should be no current transaction associated.", tm.getTransaction());
52      }
53  
54      @Test
55      public void testTxHandleCommitKeepsThreadAssociation() throws Exception
56      {
57          // don't wait for ages, has to be set before TX is begun
58          tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
59          tm.begin();
60  
61          Transaction tx = tm.getTransaction();
62          assertNotNull("Transaction should have started.", tx);
63          assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus());
64  
65          tx.commit();
66  
67          tx = tm.getTransaction();
68          assertNotNull("Committing via TX handle should NOT disassociated TX from the current thread.",
69                        tx);
70          assertEquals("TX status should have been COMMITTED.", Status.STATUS_COMMITTED, tx.getStatus());
71  
72          // Remove the TX-thread association. The only public API to achieve it is suspend(),
73          // technically we never resume the same transaction (TX forget).
74          Transaction suspended = tm.suspend();
75          assertTrue("Wrong TX suspended?.", suspended.equals(tx));
76          assertNull("TX should've been disassociated from the thread.", tm.getTransaction());
77  
78          // should be no-op and never fail
79          tm.resume(null);
80  
81          // ensure we don't have any TX-Thread association lurking around a main thread
82          assertNull(tm.getTransaction());
83      }
84  
85      @Test
86      public void testTxManagerCommitDissassociatesThread() throws Exception
87      {
88          // don't wait for ages, has to be set before TX is begun
89          tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
90          tm.begin();
91  
92          Transaction tx = tm.getTransaction();
93          assertNotNull("Transaction should have started.", tx);
94          assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus());
95  
96          tm.commit();
97  
98          assertNull("Committing via TX Manager should have disassociated TX from the current thread.",
99                     tm.getTransaction());
100     }
101 
102     @Test
103     public void testTxManagerRollbackDissassociatesThread() throws Exception
104     {
105         // don't wait for ages, has to be set before TX is begun
106         tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
107         tm.begin();
108 
109         Transaction tx = tm.getTransaction();
110         assertNotNull("Transaction should have started.", tx);
111         assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus());
112 
113         tm.rollback();
114 
115         assertNull("Committing via TX Manager should have disassociated TX from the current thread.",
116                    tm.getTransaction());
117     }
118 
119     /**
120      * AlwaysBegin action suspends current transaction and begins a new one.
121      *
122      * @throws Exception if any error
123      */
124     @Test
125     public void testAlwaysBeginXaTransactionSuspendResume() throws Exception
126     {
127         muleContext.setTransactionManager(tm);
128         assertNull("There should be no current transaction associated.", tm.getTransaction());
129 
130         // don't wait for ages, has to be set before TX is begun
131         tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
132 
133         // this is one component with a TX always begin
134         TransactionConfig config = new MuleTransactionConfig(TransactionConfig.ACTION_ALWAYS_BEGIN);
135         config.setFactory(new XaTransactionFactory());
136         TransactionTemplate template = new TransactionTemplate(config, muleContext);
137 
138         // and the callee component which should begin new transaction, current must be suspended
139         final TransactionConfig nestedConfig = new MuleTransactionConfig(TransactionConfig.ACTION_ALWAYS_BEGIN);
140         nestedConfig.setFactory(new XaTransactionFactory());
141 
142         // start the call chain
143         template.execute(new TransactionCallback<Void>()
144         {
145             public Void doInTransaction() throws Exception
146             {
147                 // the callee executes within its own TX template, but uses the same global XA transaction,
148                 // bound to the current thread of execution via a ThreadLocal
149                 TransactionTemplate<Void> nestedTemplate =
150                         new TransactionTemplate<Void>(nestedConfig, muleContext);
151                 final Transaction firstTx = tm.getTransaction();
152                 assertNotNull(firstTx);
153                 assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
154                 return nestedTemplate.execute(new TransactionCallback<Void>()
155                 {
156                     public Void doInTransaction() throws Exception
157                     {
158                         Transaction secondTx = tm.getTransaction();
159                         assertNotNull(secondTx);
160                         assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
161                         assertEquals(secondTx.getStatus(), Status.STATUS_ACTIVE);
162                         try
163                         {
164                             tm.resume(firstTx);
165                             fail("Second transaction must be active");
166                         }
167                         catch (java.lang.IllegalStateException e)
168                         {
169                             // expected
170 
171                             //Thrown if the thread is already associated with another transaction.
172                             //Second tx is associated with the current thread
173                         }
174                         try
175                         {
176                             Transaction currentTx = tm.suspend();
177                             assertTrue(currentTx.equals(secondTx));
178                             tm.resume(firstTx);
179                             assertEquals(firstTx, tm.getTransaction());
180                             assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE);
181                             assertEquals(secondTx.getStatus(), Status.STATUS_ACTIVE);
182                             Transaction a = tm.suspend();
183                             assertTrue(a.equals(firstTx));
184                             tm.resume(secondTx);
185                         }
186                         catch (Exception e)
187                         {
188                             fail("Error: " + e);
189                         }
190 
191                         // do not care about the return really
192                         return null;
193                     }
194                 });
195             }
196         });
197         assertNull("Committing via TX Manager should have disassociated TX from the current thread.",
198                    tm.getTransaction());
199     }
200 
201     /**
202      * NONE action suspends current transaction and begins a new one.
203      *
204      * @throws Exception if any error
205      */
206     @Test
207     public void testNoneXaTransactionSuspendResume() throws Exception
208     {
209         muleContext.setTransactionManager(tm);
210         assertNull("There should be no current transaction associated.", tm.getTransaction());
211 
212         // don't wait for ages, has to be set before TX is begun
213         tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS);
214 
215         // this is one component with a TX always begin
216         TransactionConfig config = new MuleTransactionConfig(TransactionConfig.ACTION_ALWAYS_BEGIN);
217         config.setFactory(new XaTransactionFactory());
218         TransactionTemplate<Void> template = new TransactionTemplate<Void>(config, muleContext);
219 
220         // and the callee component which should begin new transaction, current must be suspended
221         final TransactionConfig nestedConfig = new MuleTransactionConfig(TransactionConfig.ACTION_NONE);
222         nestedConfig.setFactory(new XaTransactionFactory());
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 should 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 should 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(TransactionConfig.ACTION_ALWAYS_BEGIN);
311         config.setFactory(new XaTransactionFactory());
312         TransactionTemplate template = new TransactionTemplate(config, muleContext);
313 
314         // and the callee service which should join the current XA transaction, not begin a nested one
315         final TransactionConfig nestedConfig = new MuleTransactionConfig(TransactionConfig.ACTION_BEGIN_OR_JOIN);
316         nestedConfig.setFactory(new XaTransactionFactory());
317 
318         // start the call chain
319         template.execute(new TransactionCallback<Void>()
320         {
321             public Void doInTransaction() throws Exception
322             {
323                 // the callee executes within its own TX template, but uses the same global XA transaction,
324                 // bound to the current thread of execution via a ThreadLocal
325                 TransactionTemplate<Void> nestedTemplate =
326                         new TransactionTemplate<Void>(nestedConfig, muleContext);
327                 return nestedTemplate.execute(new TransactionCallback<Void>()
328                 {
329                     public Void doInTransaction() throws Exception
330                     {
331                         // do not care about the return really
332                         return null;
333                     }
334                 });
335             }
336         });
337     }
338 
339 
340     protected TransactionManager getTransactionManager()
341     {
342         return tm;
343     }
344 
345     protected abstract TransactionManagerFactory getTransactionManagerFactory();
346 
347 }