/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.tck; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.mule.runtime.core.api.execution.ExecutionCallback; import org.mule.runtime.core.api.execution.ExecutionTemplate; import org.mule.runtime.core.api.transaction.TransactionConfig; import org.mule.runtime.core.api.transaction.TransactionManagerFactory; import org.mule.runtime.core.execution.TransactionalExecutionTemplate; import org.mule.tck.junit4.AbstractMuleContextTestCase; import org.mule.runtime.core.transaction.MuleTransactionConfig; import org.mule.runtime.core.transaction.XaTransaction; import org.mule.runtime.core.transaction.XaTransactionFactory; import javax.transaction.Status; import javax.transaction.Transaction; import javax.transaction.TransactionManager; import org.junit.Test; /** * Validate certain expectations when working with JTA API. It is called to catch discrepancies in TM implementations and alert * early. Subclasses are supposed to plug in specific transaction managers for tests. */ public abstract class AbstractTxThreadAssociationTestCase extends AbstractMuleContextTestCase { /* To allow access from the dead TX threads we spawn. */ private TransactionManager tm; protected static final int TRANSACTION_TIMEOUT_SECONDS = 3; @Override protected void doSetUp() throws Exception { super.doSetUp(); TransactionManagerFactory factory = getTransactionManagerFactory(); tm = factory.create(muleContext.getConfiguration()); assertNotNull("Transaction Manager should be available.", tm); assertNull("There should be no current transaction associated.", tm.getTransaction()); } @Test public void testTxHandleCommitKeepsThreadAssociation() throws Exception { // don't wait for ages, has to be set before TX is begun tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS); tm.begin(); Transaction tx = tm.getTransaction(); assertNotNull("Transaction should have started.", tx); assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus()); tx.commit(); tx = tm.getTransaction(); assertNotNull("Committing via TX handle should NOT disassociated TX from the current thread.", tx); assertEquals("TX status should have been COMMITTED.", Status.STATUS_COMMITTED, tx.getStatus()); // Remove the TX-thread association. The only public API to achieve it is suspend(), // technically we never resume the same transaction (TX forget). Transaction suspended = tm.suspend(); assertTrue("Wrong TX suspended?.", suspended.equals(tx)); assertNull("TX should've been disassociated from the thread.", tm.getTransaction()); // should be no-op and never fail tm.resume(null); // ensure we don't have any TX-Thread association lurking around a main thread assertNull(tm.getTransaction()); } @Test public void testTxManagerCommitDissassociatesThread() throws Exception { // don't wait for ages, has to be set before TX is begun tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS); tm.begin(); Transaction tx = tm.getTransaction(); assertNotNull("Transaction should have started.", tx); assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus()); tm.commit(); assertNull("Committing via TX Manager should have disassociated TX from the current thread.", tm.getTransaction()); } @Test public void testTxManagerRollbackDissassociatesThread() throws Exception { // don't wait for ages, has to be set before TX is begun tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS); tm.begin(); Transaction tx = tm.getTransaction(); assertNotNull("Transaction should have started.", tx); assertEquals("TX should have been active", Status.STATUS_ACTIVE, tx.getStatus()); tm.rollback(); assertNull("Committing via TX Manager should have disassociated TX from the current thread.", tm.getTransaction()); } /** * AlwaysBegin action suspends current transaction and begins a new one. * * @throws Exception if any error */ @Test public void testAlwaysBeginXaTransactionSuspendResume() throws Exception { muleContext.setTransactionManager(tm); assertNull("There should be no current transaction associated.", tm.getTransaction()); // don't wait for ages, has to be set before TX is begun tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS); // this is one component with a TX always begin TransactionConfig config = new MuleTransactionConfig(TransactionConfig.ACTION_ALWAYS_BEGIN); config.setFactory(new XaTransactionFactory()); ExecutionTemplate<Void> executionTemplate = TransactionalExecutionTemplate.createTransactionalExecutionTemplate(muleContext, config); // and the callee component which should begin new transaction, current must be suspended final TransactionConfig nestedConfig = new MuleTransactionConfig(TransactionConfig.ACTION_ALWAYS_BEGIN); nestedConfig.setFactory(new XaTransactionFactory()); // start the call chain executionTemplate.execute(new ExecutionCallback<Void>() { @Override public Void process() throws Exception { // the callee executes within its own TX template, but uses the same global XA transaction, // bound to the current thread of execution via a ThreadLocal ExecutionTemplate<Void> innerExecutionTemplate = TransactionalExecutionTemplate.createTransactionalExecutionTemplate(muleContext, nestedConfig); final Transaction firstTx = tm.getTransaction(); assertNotNull(firstTx); assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE); return innerExecutionTemplate.execute(new ExecutionCallback<Void>() { @Override public Void process() throws Exception { Transaction secondTx = tm.getTransaction(); assertNotNull(secondTx); assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE); assertEquals(secondTx.getStatus(), Status.STATUS_ACTIVE); try { tm.resume(firstTx); fail("Second transaction must be active"); } catch (java.lang.IllegalStateException e) { // expected // Thrown if the thread is already associated with another transaction. // Second tx is associated with the current thread } try { Transaction currentTx = tm.suspend(); assertTrue(currentTx.equals(secondTx)); tm.resume(firstTx); assertEquals(firstTx, tm.getTransaction()); assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE); assertEquals(secondTx.getStatus(), Status.STATUS_ACTIVE); Transaction a = tm.suspend(); assertTrue(a.equals(firstTx)); tm.resume(secondTx); } catch (Exception e) { fail("Error: " + e); } // do not care about the return really return null; } }); } }); assertNull("Committing via TX Manager should have disassociated TX from the current thread.", tm.getTransaction()); } /** * NONE action suspends current transaction and begins a new one. * * @throws Exception if any error */ @Test public void testNoneXaTransactionSuspendResume() throws Exception { muleContext.setTransactionManager(tm); assertNull("There should be no current transaction associated.", tm.getTransaction()); // don't wait for ages, has to be set before TX is begun tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS); // this is one component with a TX always begin TransactionConfig config = new MuleTransactionConfig(TransactionConfig.ACTION_ALWAYS_BEGIN); config.setFactory(new XaTransactionFactory()); ExecutionTemplate<Void> executionTemplate = TransactionalExecutionTemplate.createTransactionalExecutionTemplate(muleContext, config); // and the callee component which should begin new transaction, current must be suspended final TransactionConfig nestedConfig = new MuleTransactionConfig(TransactionConfig.ACTION_NONE); nestedConfig.setFactory(new XaTransactionFactory()); // start the call chain executionTemplate.execute(new ExecutionCallback<Void>() { @Override public Void process() throws Exception { // the callee executes within its own TX template, but uses the same global XA transaction, // bound to the current thread of execution via a ThreadLocal ExecutionTemplate<Void> nestedExecutionTemplate = TransactionalExecutionTemplate.createTransactionalExecutionTemplate(muleContext, nestedConfig); final Transaction firstTx = tm.getTransaction(); assertNotNull(firstTx); assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE); return nestedExecutionTemplate.execute(new ExecutionCallback<Void>() { @Override public Void process() throws Exception { Transaction secondTx = tm.getTransaction(); assertNull(secondTx); assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE); try { tm.resume(firstTx); assertEquals(firstTx, tm.getTransaction()); assertEquals(firstTx.getStatus(), Status.STATUS_ACTIVE); Transaction a = tm.suspend(); assertTrue(a.equals(firstTx)); } catch (Exception e) { fail("Error: " + e); } // do not care about the return really return null; } }); } }); assertNull("Committing via TX Manager should have disassociated TX from the current thread.", tm.getTransaction()); } /** * This is a former XaTransactionTestCase. * * @throws Exception in case of any error */ @Test public void testXaTransactionTermination() throws Exception { muleContext.setTransactionManager(tm); assertNull("There should be no current transaction associated.", tm.getTransaction()); // don't wait for ages, has to be set before TX is begun tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS); XaTransaction muleTx = new XaTransaction(muleContext); assertFalse(muleTx.isBegun()); assertEquals(Status.STATUS_NO_TRANSACTION, muleTx.getStatus()); muleTx.begin(); assertTrue(muleTx.isBegun()); muleTx.commit(); Transaction jtaTx = tm.getTransaction(); assertNull("Committing via TX Manager should have disassociated TX from the current thread.", jtaTx); assertEquals(Status.STATUS_NO_TRANSACTION, muleTx.getStatus()); } /** * This is a former TransactionTemplateTestCase. http://mule.mulesoft.org/jira/browse/MULE-1494 * * @throws Exception in case of any error */ @Test public void testNoNestedTxStarted() throws Exception { muleContext.setTransactionManager(tm); assertNull("There should be no current transaction associated.", tm.getTransaction()); // don't wait for ages, has to be set before TX is begun tm.setTransactionTimeout(TRANSACTION_TIMEOUT_SECONDS); // this is one service with a TX always begin TransactionConfig config = new MuleTransactionConfig(TransactionConfig.ACTION_ALWAYS_BEGIN); config.setFactory(new XaTransactionFactory()); ExecutionTemplate<Void> executionTemplate = TransactionalExecutionTemplate.createTransactionalExecutionTemplate(muleContext, config); // and the callee service which should join the current XA transaction, not begin a nested one final TransactionConfig nestedConfig = new MuleTransactionConfig(TransactionConfig.ACTION_BEGIN_OR_JOIN); nestedConfig.setFactory(new XaTransactionFactory()); // start the call chain executionTemplate.execute(new ExecutionCallback<Void>() { @Override public Void process() throws Exception { // the callee executes within its own TX template, but uses the same global XA transaction, // bound to the current thread of execution via a ThreadLocal ExecutionTemplate<Void> nestedExecutionTemplate = TransactionalExecutionTemplate.createTransactionalExecutionTemplate(muleContext, nestedConfig); return nestedExecutionTemplate.execute(new ExecutionCallback<Void>() { @Override public Void process() throws Exception { // do not care about the return really return null; } }); } }); } protected TransactionManager getTransactionManager() { return tm; } protected abstract TransactionManagerFactory getTransactionManagerFactory(); }