package org.infinispan.tx;
import static org.testng.AssertJUnit.assertNull;
import java.util.Arrays;
import java.util.Collection;
import javax.transaction.HeuristicMixedException;
import javax.transaction.RollbackException;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.test.SingleCacheManagerTest;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.transaction.lookup.EmbeddedTransactionManagerLookup;
import org.infinispan.transaction.tm.EmbeddedTransactionManager;
import org.testng.AssertJUnit;
import org.testng.annotations.Test;
/**
* Set of tests for the DummyTransaction.
*
* @author Pedro Ruivo
* @since 7.2
*/
@Test(groups = "functional", testName = "tx.EmbeddedTransactionTest")
public class EmbeddedTransactionTest extends SingleCacheManagerTest {
private static final String SYNC_CACHE_NAME = "sync-cache";
private static final String XA_CACHE_NAME = "xa-cache";
private static final String KEY = "key";
private static final String VALUE = "value";
public void testFailBeforeWithMarkRollbackFirstSync() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterFailSynchronization(FailMode.BEFORE_MARK_ROLLBACK),
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testFailBeforeWithExceptionFirstSync() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterFailSynchronization(FailMode.BEFORE_THROW_EXCEPTION),
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testFailBeforeWithMarkRollbackSecondSync() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterFailSynchronization(FailMode.BEFORE_MARK_ROLLBACK),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testFailBeforeWithExceptionSecondSync() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterFailSynchronization(FailMode.BEFORE_THROW_EXCEPTION),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testEndFailFirstXa() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_END),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testEndFailSecondXa() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_END)
));
}
public void testPrepareFailFirstXa() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_PREPARE),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testPrepareFailSecondXa() throws Exception {
doCommitWithRollbackExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_PREPARE)
));
}
public void testCommitFailFirstXa() throws Exception {
doCommitWithHeuristicExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_COMMIT),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testCommitFailSecondXa() throws Exception {
doCommitWithHeuristicExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_COMMIT)
));
}
public void testRollbackFailFirstXa() throws Exception {
doRollbackWithHeuristicExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_ROLLBACK),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testRollbackFailSecondXa() throws Exception {
doRollbackWithHeuristicExceptionTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME)),
new RegisterFailXaResource(FailMode.XA_ROLLBACK)
));
}
public void testFailAfterFirstSync() throws Exception {
doAfterCompletionFailTest(Arrays.asList(
new RegisterFailSynchronization(FailMode.AFTER_THROW_EXCEPTION),
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testFailAfterSecondSync() throws Exception {
doAfterCompletionFailTest(Arrays.asList(
new RegisterCacheTransaction(cacheManager.getCache(SYNC_CACHE_NAME)),
new RegisterFailSynchronization(FailMode.AFTER_THROW_EXCEPTION),
new RegisterCacheTransaction(cacheManager.getCache(XA_CACHE_NAME))
));
}
public void testReadOnlyResource() throws Exception {
//test for ISPN-2813
EmbeddedTransactionManager transactionManager = EmbeddedTransactionManager.getInstance();
transactionManager.begin();
cacheManager.<String, String>getCache(SYNC_CACHE_NAME).put(KEY, VALUE);
cacheManager.<String, String>getCache(XA_CACHE_NAME).put(KEY, VALUE);
transactionManager.getTransaction().enlistResource(new ReadOnlyXaResource());
transactionManager.commit();
assertData();
assertNoTxInAllCaches();
assertNull(transactionManager.getTransaction());
}
@Override
protected EmbeddedCacheManager createCacheManager() throws Exception {
EmbeddedCacheManager cacheManager = TestCacheManagerFactory.createCacheManager(getDefaultStandaloneCacheConfig(true));
ConfigurationBuilder builder = getDefaultStandaloneCacheConfig(true);
builder.transaction().useSynchronization(true);
builder.transaction().transactionManagerLookup(new EmbeddedTransactionManagerLookup());
cacheManager.defineConfiguration(SYNC_CACHE_NAME, builder.build());
builder = getDefaultStandaloneCacheConfig(true);
builder.transaction().useSynchronization(false);
builder.transaction().transactionManagerLookup(new EmbeddedTransactionManagerLookup());
cacheManager.defineConfiguration(XA_CACHE_NAME, builder.build());
return cacheManager;
}
private void doCommitWithRollbackExceptionTest(Collection<RegisterTransaction> registerTransactionCollection) throws Exception {
EmbeddedTransactionManager transactionManager = EmbeddedTransactionManager.getInstance();
transactionManager.begin();
for (RegisterTransaction registerTransaction : registerTransactionCollection) {
registerTransaction.register(transactionManager);
}
try {
transactionManager.commit();
AssertJUnit.fail("RollbackException expected!");
} catch (RollbackException e) {
//expected
}
assertEmpty();
assertNoTxInAllCaches();
assertNull(transactionManager.getTransaction());
}
private void doCommitWithHeuristicExceptionTest(Collection<RegisterTransaction> registerTransactionCollection) throws Exception {
EmbeddedTransactionManager transactionManager = EmbeddedTransactionManager.getInstance();
transactionManager.begin();
for (RegisterTransaction registerTransaction : registerTransactionCollection) {
registerTransaction.register(transactionManager);
}
try {
transactionManager.commit();
AssertJUnit.fail("HeuristicMixedException expected!");
} catch (HeuristicMixedException e) {
//expected
}
assertData();
assertNoTxInAllCaches();
assertNull(transactionManager.getTransaction());
}
private void doRollbackWithHeuristicExceptionTest(Collection<RegisterTransaction> registerTransactionCollection) throws Exception {
EmbeddedTransactionManager transactionManager = EmbeddedTransactionManager.getInstance();
transactionManager.begin();
for (RegisterTransaction registerTransaction : registerTransactionCollection) {
registerTransaction.register(transactionManager);
}
try {
transactionManager.rollback();
AssertJUnit.fail("SystemException expected!");
} catch (SystemException e) {
//expected
}
assertEmpty();
assertNoTxInAllCaches();
assertNull(transactionManager.getTransaction());
}
private void doAfterCompletionFailTest(Collection<RegisterTransaction> registerTransactionCollection) throws Exception {
EmbeddedTransactionManager transactionManager = EmbeddedTransactionManager.getInstance();
transactionManager.begin();
for (RegisterTransaction registerTransaction : registerTransactionCollection) {
registerTransaction.register(transactionManager);
}
transactionManager.commit();
assertData();
assertNoTxInAllCaches();
assertNull(transactionManager.getTransaction());
}
private void assertEmpty() {
AssertJUnit.assertTrue(cacheManager.getCache(SYNC_CACHE_NAME).isEmpty());
AssertJUnit.assertTrue(cacheManager.getCache(XA_CACHE_NAME).isEmpty());
}
private void assertData() {
AssertJUnit.assertEquals(VALUE, cacheManager.getCache(SYNC_CACHE_NAME).get(KEY));
AssertJUnit.assertEquals(VALUE, cacheManager.getCache(XA_CACHE_NAME).get(KEY));
}
private void assertNoTxInAllCaches() {
assertNoTransactions(cacheManager.getCache(XA_CACHE_NAME));
assertNoTransactions(cacheManager.getCache(SYNC_CACHE_NAME));
}
private enum FailMode {
BEFORE_MARK_ROLLBACK, BEFORE_THROW_EXCEPTION, AFTER_THROW_EXCEPTION,
XA_PREPARE, XA_COMMIT, XA_ROLLBACK, XA_END
}
private interface RegisterTransaction {
void register(TransactionManager transactionManager) throws Exception;
}
//a little hacky, but it is just to avoid implementing everything
private static class ReadOnlyXaResource extends FailXaResource {
private boolean finished;
private ReadOnlyXaResource() {
super(null);
}
@Override
public int prepare(Xid xid) throws XAException {
finished = true;
return XA_OK;
}
@Override
public void commit(Xid xid, boolean b) throws XAException {
if (finished) {
throw new XAException(XAException.XAER_NOTA);
}
}
@Override
public void rollback(Xid xid) throws XAException {
if (finished) {
throw new XAException(XAException.XAER_NOTA);
}
}
}
private static class FailXaResource implements XAResource {
private final FailMode failMode;
private FailXaResource(FailMode failMode) {
this.failMode = failMode;
}
@Override
public void commit(Xid xid, boolean b) throws XAException {
if (failMode == FailMode.XA_COMMIT) {
throw new XAException(XAException.XA_HEURCOM);
}
}
@Override
public void end(Xid xid, int i) throws XAException {
if (failMode == FailMode.XA_END) {
throw new XAException();
}
}
@Override
public void forget(Xid xid) throws XAException {/*no-op*/}
@Override
public int getTransactionTimeout() throws XAException {
return 0;
}
@Override
public boolean isSameRM(XAResource xaResource) throws XAException {
return xaResource instanceof FailSynchronization && ((FailSynchronization) xaResource).failMode == failMode;
}
@Override
public int prepare(Xid xid) throws XAException {
if (failMode == FailMode.XA_PREPARE) {
throw new XAException();
}
return XA_OK;
}
@Override
public Xid[] recover(int i) throws XAException {
return new Xid[0];
}
@Override
public void rollback(Xid xid) throws XAException {
if (failMode == FailMode.XA_ROLLBACK) {
throw new XAException();
}
}
@Override
public boolean setTransactionTimeout(int i) throws XAException {
return false;
}
@Override
public void start(Xid xid, int i) throws XAException {/*no-op*/}
}
private static class FailSynchronization implements Synchronization {
private final Transaction transaction;
private final FailMode failMode;
private FailSynchronization(Transaction transaction, FailMode failMode) {
this.transaction = transaction;
this.failMode = failMode;
}
@Override
public void beforeCompletion() {
switch (failMode) {
case BEFORE_MARK_ROLLBACK:
try {
transaction.setRollbackOnly();
} catch (SystemException e) {
/* ignored */
}
break;
case BEFORE_THROW_EXCEPTION:
throw new RuntimeException("induced!");
}
}
@Override
public void afterCompletion(int status) {
if (failMode == FailMode.AFTER_THROW_EXCEPTION) {
throw new RuntimeException("induced!");
}
}
}
private static class RegisterCacheTransaction implements RegisterTransaction {
private final Cache<String, String> cache;
private RegisterCacheTransaction(Cache<String, String> cache) {
this.cache = cache;
}
@Override
public void register(TransactionManager transactionManager) throws Exception {
cache.put(KEY, VALUE);
}
}
private static class RegisterFailSynchronization implements RegisterTransaction {
private final FailMode failMode;
private RegisterFailSynchronization(FailMode failMode) {
this.failMode = failMode;
}
@Override
public void register(TransactionManager transactionManager) throws Exception {
Transaction transaction = transactionManager.getTransaction();
FailSynchronization failSynchronization = new FailSynchronization(transaction, failMode);
transaction.registerSynchronization(failSynchronization);
}
}
private static class RegisterFailXaResource implements RegisterTransaction {
private final FailMode failMode;
private RegisterFailXaResource(FailMode failMode) {
this.failMode = failMode;
}
@Override
public void register(TransactionManager transactionManager) throws Exception {
Transaction transaction = transactionManager.getTransaction();
transaction.enlistResource(new FailXaResource(failMode));
}
}
}