View Javadoc

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