Added: aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java?rev=1734243&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java (added) +++ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java Wed Mar 9 14:38:15 2016 @@ -0,0 +1,436 @@ +package org.apache.aries.tx.control.service.xa.impl; + +import static java.util.Optional.ofNullable; +import static javax.transaction.xa.XAException.XA_HEURMIX; +import static javax.transaction.xa.XAException.XA_RBOTHER; +import static javax.transaction.xa.XAException.XA_RBPROTO; +import static org.osgi.service.transaction.control.TransactionStatus.ACTIVE; +import static org.osgi.service.transaction.control.TransactionStatus.COMMITTED; +import static org.osgi.service.transaction.control.TransactionStatus.COMMITTING; +import static org.osgi.service.transaction.control.TransactionStatus.MARKED_ROLLBACK; +import static org.osgi.service.transaction.control.TransactionStatus.PREPARED; +import static org.osgi.service.transaction.control.TransactionStatus.PREPARING; +import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK; +import static org.osgi.service.transaction.control.TransactionStatus.ROLLING_BACK; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +import javax.transaction.Status; +import javax.transaction.Synchronization; +import javax.transaction.SystemException; +import javax.transaction.Transaction; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +import org.apache.aries.tx.control.service.common.impl.AbstractTransactionContextImpl; +import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.osgi.service.coordinator.Coordination; +import org.osgi.service.transaction.control.LocalResource; +import org.osgi.service.transaction.control.TransactionContext; +import org.osgi.service.transaction.control.TransactionException; +import org.osgi.service.transaction.control.TransactionStatus; + +public class TransactionContextImpl extends AbstractTransactionContextImpl implements TransactionContext { + + final List<LocalResource> resources = new ArrayList<>(); + + private final Transaction oldTran; + + private final Transaction currentTransaction; + + private final AtomicReference<TransactionStatus> completionState = new AtomicReference<>(); + + private final GeronimoTransactionManager transactionManager; + + private final Object key; + + public TransactionContextImpl(GeronimoTransactionManager transactionManager, Coordination coordination) { + super(coordination); + this.transactionManager = transactionManager; + Transaction tmp = null; + try { + tmp = transactionManager.suspend(); + transactionManager.begin(); + } catch (Exception e) { + if(tmp != null) { + try { + transactionManager.resume(tmp); + } catch (Exception e1) { + e.addSuppressed(e1); + } + } + throw new TransactionException("There was a serious error creating a transaction"); + } + oldTran = tmp; + currentTransaction = transactionManager.getTransaction(); + key = transactionManager.getTransactionKey(); + } + + @Override + public Object getTransactionKey() { + return key; + } + + @Override + public boolean getRollbackOnly() throws IllegalStateException { + switch (getTransactionStatus()) { + case MARKED_ROLLBACK: + case ROLLING_BACK: + case ROLLED_BACK: + return true; + default: + return false; + } + } + + @Override + public void setRollbackOnly() throws IllegalStateException { + TransactionStatus status = getTransactionStatus(); + switch (status) { + case ACTIVE: + case MARKED_ROLLBACK: + try { + currentTransaction.setRollbackOnly(); + } catch (Exception e) { + throw new TransactionException("Unable to set rollback for the transaction", e); + } + break; + case COMMITTING: + // TODO something here? If it's the first resource then it might + // be ok to roll back? + throw new IllegalStateException("The transaction is already being committed"); + case COMMITTED: + throw new IllegalStateException("The transaction is already committed"); + + case ROLLING_BACK: + case ROLLED_BACK: + // A no op + break; + default: + throw new IllegalStateException("The transaction is in an unkown state"); + } + } + + @Override + protected void safeSetRollbackOnly() { + TransactionStatus status = getTransactionStatus(); + switch (status) { + case ACTIVE: + case MARKED_ROLLBACK: + try { + currentTransaction.setRollbackOnly(); + } catch (Exception e) { + throw new TransactionException("Unable to set rollback for the transaction", e); + } + break; + default: + break; + } + } + + @Override + public TransactionStatus getTransactionStatus() { + return ofNullable(completionState.get()) + .orElseGet(this::getStatusFromTransaction); + } + + private TransactionStatus getStatusFromTransaction() { + int status; + try { + status = currentTransaction.getStatus(); + } catch (SystemException e) { + throw new TransactionException("Unable to determine the state of the transaction.", e); + } + + switch (status) { + case Status.STATUS_ACTIVE: + return ACTIVE; + case Status.STATUS_MARKED_ROLLBACK: + return MARKED_ROLLBACK; + case Status.STATUS_PREPARING: + return PREPARING; + case Status.STATUS_PREPARED: + return PREPARED; + case Status.STATUS_COMMITTING: + return COMMITTING; + case Status.STATUS_COMMITTED: + return COMMITTED; + case Status.STATUS_ROLLING_BACK: + return ROLLING_BACK; + case Status.STATUS_ROLLEDBACK: + return ROLLED_BACK; + default: + throw new TransactionException("Unable to determine the state of the transaction: " + status); + } + } + + @Override + public void preCompletion(Runnable job) throws IllegalStateException { + TransactionStatus status = getTransactionStatus(); + if (status.compareTo(MARKED_ROLLBACK) > 0) { + throw new IllegalStateException("The current transaction is in state " + status); + } + + preCompletion.add(job); + } + + @Override + public void postCompletion(Consumer<TransactionStatus> job) throws IllegalStateException { + TransactionStatus status = getTransactionStatus(); + if (status == COMMITTED || status == ROLLED_BACK) { + throw new IllegalStateException("The current transaction is in state " + status); + } + + postCompletion.add(job); + } + + @Override + public void registerXAResource(XAResource resource) { + TransactionStatus status = getTransactionStatus(); + if (status.compareTo(MARKED_ROLLBACK) > 0) { + throw new IllegalStateException("The current transaction is in state " + status); + } + try { + currentTransaction.enlistResource(resource); + } catch (Exception e) { + throw new TransactionException("The transaction was unable to enlist a resource", e); + } + } + + @Override + public void registerLocalResource(LocalResource resource) { + TransactionStatus status = getTransactionStatus(); + if (status.compareTo(MARKED_ROLLBACK) > 0) { + throw new IllegalStateException("The current transaction is in state " + status); + } + resources.add(resource); + } + + @Override + public boolean supportsXA() { + return true; + } + + @Override + public boolean supportsLocal() { + return true; + } + + @Override + protected boolean isAlive() { + TransactionStatus status = getTransactionStatus(); + return status != COMMITTED && status != ROLLED_BACK; + } + + @Override + public void finish() { + + if(!resources.isEmpty()) { + XAResource localResource = new LocalXAResourceImpl(); + try { + currentTransaction.enlistResource(localResource); + } catch (Exception e) { + safeSetRollbackOnly(); + recordFailure(e); + try { + localResource.rollback(null); + } catch (XAException e1) { + recordFailure(e1); + } + } + } + + TxListener listener; + boolean manualCallListener; + if(!preCompletion.isEmpty() || !postCompletion.isEmpty()) { + listener = new TxListener(); + try { + transactionManager.registerInterposedSynchronization(listener); + manualCallListener = false; + } catch (Exception e) { + manualCallListener = true; + recordFailure(e); + safeSetRollbackOnly(); + } + } else { + listener = null; + manualCallListener = false; + } + + + try { + int status; + try { + if (getRollbackOnly()) { + // GERONIMO-4449 says that we get no beforeCompletion + // callback for rollback :( + if(listener != null) { + listener.beforeCompletion(); + } + transactionManager.rollback(); + status = Status.STATUS_ROLLEDBACK; + completionState.set(ROLLED_BACK); + } else { + if(manualCallListener) { + listener.beforeCompletion(); + } + transactionManager.commit(); + status = Status.STATUS_COMMITTED; + completionState.set(COMMITTED); + } + } catch (Exception e) { + recordFailure(e); + status = Status.STATUS_ROLLEDBACK; + completionState.set(ROLLED_BACK); + } + if(manualCallListener) { + listener.afterCompletion(status); + } + } finally { + try { + transactionManager.resume(oldTran); + } catch (Exception e) { + recordFailure(e); + } + } + } + + private class LocalXAResourceImpl implements XAResource { + + private final AtomicBoolean finished = new AtomicBoolean(); + + @Override + public void commit(Xid xid, boolean onePhase) throws XAException { + if(!finished.compareAndSet(false, true)) { + return; + } + doCommit(); + } + + private void doCommit() throws XAException { + AtomicBoolean commit = new AtomicBoolean(true); + + List<LocalResource> committed = new ArrayList<>(resources.size()); + List<LocalResource> rolledback = new ArrayList<>(0); + + resources.stream().forEach(lr -> { + try { + if (commit.get()) { + lr.commit(); + committed.add(lr); + } else { + lr.rollback(); + rolledback.add(lr); + } + } catch (Exception e) { + recordFailure(e); + if (committed.isEmpty()) { + commit.set(false); + // This is needed to override the status from the + // Transaction, which thinks that we're committing + // until we throw an XAException from this commit. + completionState.set(ROLLING_BACK); + } + rolledback.add(lr); + } + }); + + if(!rolledback.isEmpty()) { + if(committed.isEmpty()) { + throw (XAException) new XAException(XA_RBOTHER) + .initCause(firstUnexpectedException.get()); + } else { + throw (XAException) new XAException(XA_HEURMIX) + .initCause(firstUnexpectedException.get()); + } + } + } + + @Override + public void end(Xid xid, int flags) throws XAException { + //Nothing to do here + } + + @Override + public void forget(Xid xid) throws XAException { + //Nothing to do here + } + + @Override + public int getTransactionTimeout() throws XAException { + return 3600; + } + + @Override + public boolean isSameRM(XAResource xares) throws XAException { + return this == xares; + } + + @Override + public int prepare(Xid xid) throws XAException { + if(!finished.compareAndSet(false, true)) { + switch(getTransactionStatus()) { + case COMMITTING: + return XA_OK; + case ROLLING_BACK: + throw new XAException(XA_RBOTHER); + default: + throw new XAException(XA_RBPROTO); + } + } + completionState.set(COMMITTING); + doCommit(); + return XA_OK; + } + + @Override + public Xid[] recover(int flag) throws XAException { + return new Xid[0]; + } + + @Override + public void rollback(Xid xid) throws XAException { + if(!finished.compareAndSet(false, true)) { + return; + } + resources.stream().forEach(lr -> { + try { + lr.rollback(); + } catch (Exception e) { + // TODO log this + recordFailure(e); + } + }); + } + + @Override + public boolean setTransactionTimeout(int seconds) throws XAException { + return false; + } + + @Override + public void start(Xid xid, int flags) throws XAException { + // Nothing to do here + } + + } + + private class TxListener implements Synchronization { + + @Override + public void beforeCompletion() { + TransactionContextImpl.this.beforeCompletion(() -> safeSetRollbackOnly()); + } + + @Override + public void afterCompletion(int status) { + TransactionContextImpl.this.afterCompletion(status == Status.STATUS_COMMITTED ? COMMITTED : ROLLED_BACK); + } + + } +}
Added: aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java?rev=1734243&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java (added) +++ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java Wed Mar 9 14:38:15 2016 @@ -0,0 +1,23 @@ +package org.apache.aries.tx.control.service.xa.impl; + +import org.apache.aries.tx.control.service.common.impl.AbstractTransactionContextImpl; +import org.apache.aries.tx.control.service.common.impl.AbstractTransactionControlImpl; +import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.osgi.service.coordinator.Coordination; +import org.osgi.service.coordinator.Coordinator; + +public class TransactionControlImpl extends AbstractTransactionControlImpl { + + GeronimoTransactionManager transactionManager; + + public TransactionControlImpl(GeronimoTransactionManager tm, Coordinator c) { + super(c); + this.transactionManager = tm; + } + + @Override + protected AbstractTransactionContextImpl startTransaction(Coordination currentCoord) { + return new TransactionContextImpl(transactionManager, currentCoord); + } + +} Copied: aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java (from r1733320, aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java) URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java?p2=aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java&p1=aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java&r1=1733320&r2=1734243&rev=1734243&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java (original) +++ aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java Wed Mar 9 14:38:15 2016 @@ -1,4 +1,4 @@ -package org.apache.aries.tx.control.service.local.impl; +package org.apache.aries.tx.control.service.xa.impl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -17,12 +17,18 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import org.apache.aries.tx.control.service.common.impl.AbstractTransactionContextImpl; +import org.apache.aries.tx.control.service.xa.impl.TransactionContextImpl; +import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; @@ -46,8 +52,8 @@ public class TransactionContextTest { AbstractTransactionContextImpl ctx; @Before - public void setUp() { - ctx = new TransactionContextImpl(coordination); + public void setUp() throws XAException { + ctx = new TransactionContextImpl(new GeronimoTransactionManager(), coordination); variables = new HashMap<>(); Mockito.when(coordination.getVariables()).thenReturn(variables); } @@ -86,10 +92,10 @@ public class TransactionContextTest { @Test public void testXAResourceSupport() { - assertFalse(ctx.supportsXA()); + assertTrue(ctx.supportsXA()); } - @Test(expected=IllegalStateException.class) + @Test public void testXAResourceRegistration() { ctx.registerXAResource(xaResource); } @@ -285,9 +291,9 @@ public class TransactionContextTest { ctx.registerLocalResource(localResource); Mockito.doAnswer(i -> { - assertEquals(COMMITTING, ctx.getTransactionStatus()); + assertEquals(ROLLING_BACK, ctx.getTransactionStatus()); return null; - }).when(localResource).commit(); + }).when(localResource).rollback(); getParticipant().ended(coordination); @@ -386,4 +392,218 @@ public class TransactionContextTest { Mockito.verify(localResource2).rollback(); } + @Test + public void testSingleXAResource() throws Exception { + ctx.registerXAResource(xaResource); + + Mockito.doAnswer(i -> { + assertEquals(COMMITTING, ctx.getTransactionStatus()); + return null; + }).when(xaResource).commit(Mockito.any(Xid.class), Mockito.eq(true)); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMSUCCESS)); + inOrder.verify(xaResource).commit(Mockito.eq(captor.getValue()), Mockito.eq(true)); + + Mockito.verifyNoMoreInteractions(xaResource); + } + + @Test + public void testXAResourceEarlyEnd() throws Exception { + ctx.registerXAResource(xaResource); + + Mockito.doAnswer(i -> { + assertEquals(ROLLING_BACK, ctx.getTransactionStatus()); + return null; + }).when(xaResource).rollback(Mockito.any(Xid.class)); + + getParticipant().ended(coordination); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMFAIL)); + inOrder.verify(xaResource).rollback(Mockito.eq(captor.getValue())); + + Mockito.verifyNoMoreInteractions(xaResource); + } + + @Test + public void testXAResourceRollbackOnly() throws Exception { + ctx.registerXAResource(xaResource); + ctx.setRollbackOnly(); + + Mockito.doAnswer(i -> { + assertEquals(ROLLING_BACK, ctx.getTransactionStatus()); + return null; + }).when(xaResource).rollback(Mockito.any(Xid.class)); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMFAIL)); + inOrder.verify(xaResource).rollback(Mockito.eq(captor.getValue())); + + Mockito.verifyNoMoreInteractions(xaResource); + } + + @Test + public void testXAResourceFail() throws Exception { + ctx.registerXAResource(xaResource); + + Mockito.doAnswer(i -> { + assertEquals(ROLLING_BACK, ctx.getTransactionStatus()); + return null; + }).when(xaResource).rollback(Mockito.any(Xid.class)); + + getParticipant().failed(coordination); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMFAIL)); + inOrder.verify(xaResource).rollback(Mockito.eq(captor.getValue())); + + Mockito.verifyNoMoreInteractions(xaResource); + } + + @Test + public void testXAResourcePreCommitException() throws Exception { + ctx.registerXAResource(xaResource); + + Mockito.doAnswer(i -> { + assertEquals(ROLLING_BACK, ctx.getTransactionStatus()); + return null; + }).when(xaResource).rollback(Mockito.any(Xid.class)); + + ctx.preCompletion(() -> { throw new IllegalArgumentException(); }); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMFAIL)); + inOrder.verify(xaResource).rollback(Mockito.eq(captor.getValue())); + + Mockito.verifyNoMoreInteractions(xaResource); + } + + @Test + public void testXAResourcePostCommitException() throws Exception { + ctx.registerXAResource(xaResource); + + Mockito.doAnswer(i -> { + assertEquals(COMMITTING, ctx.getTransactionStatus()); + return null; + }).when(xaResource).commit(Mockito.any(Xid.class), Mockito.eq(true)); + + ctx.postCompletion(i -> { + assertEquals(COMMITTED, ctx.getTransactionStatus()); + throw new IllegalArgumentException(); + }); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMSUCCESS)); + inOrder.verify(xaResource).commit(Mockito.eq(captor.getValue()), Mockito.eq(true)); + + Mockito.verifyNoMoreInteractions(xaResource); + } + + @Test + public void testLastParticipantSuccessSoCommit() throws Exception { + + ctx.registerLocalResource(localResource); + ctx.registerXAResource(xaResource); + + Mockito.doAnswer(i -> { + assertEquals(COMMITTING, ctx.getTransactionStatus()); + return null; + }).when(localResource).commit(); + + Mockito.doAnswer(i -> { + assertEquals(COMMITTING, ctx.getTransactionStatus()); + return null; + }).when(xaResource).commit(Mockito.any(Xid.class), Mockito.eq(false)); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource, localResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMSUCCESS)); + inOrder.verify(xaResource).prepare(captor.getValue()); + inOrder.verify(localResource).commit(); + inOrder.verify(xaResource).commit(Mockito.eq(captor.getValue()), Mockito.eq(false)); + + Mockito.verifyNoMoreInteractions(xaResource, localResource); + } + + @Test + public void testLastParticipantFailsSoRollback() throws Exception { + + ctx.registerLocalResource(localResource); + ctx.registerXAResource(xaResource); + + Mockito.doAnswer(i -> { + assertEquals(COMMITTING, ctx.getTransactionStatus()); + throw new TransactionException("Unable to commit"); + }).when(localResource).commit(); + + Mockito.doAnswer(i -> { + assertEquals(ROLLING_BACK, ctx.getTransactionStatus()); + return null; + }).when(xaResource).rollback(Mockito.any(Xid.class)); + + ctx.finish(); + + ArgumentCaptor<Xid> captor = ArgumentCaptor.forClass(Xid.class); + + InOrder inOrder = Mockito.inOrder(xaResource, localResource); + + inOrder.verify(xaResource).start(captor.capture(), Mockito.eq(XAResource.TMNOFLAGS)); + inOrder.verify(xaResource).setTransactionTimeout(Mockito.anyInt()); + inOrder.verify(xaResource).end(Mockito.eq(captor.getValue()), Mockito.eq(XAResource.TMSUCCESS)); + inOrder.verify(xaResource).prepare(captor.getValue()); + inOrder.verify(localResource).commit(); + inOrder.verify(xaResource).rollback(Mockito.eq(captor.getValue())); + + Mockito.verifyNoMoreInteractions(xaResource, localResource); + } + } Added: aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlRunningTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlRunningTest.java?rev=1734243&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlRunningTest.java (added) +++ aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlRunningTest.java Wed Mar 9 14:38:15 2016 @@ -0,0 +1,455 @@ +package org.apache.aries.tx.control.service.xa.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.osgi.service.transaction.control.TransactionStatus.COMMITTED; +import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK; + +import java.net.BindException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import javax.transaction.xa.XAException; + +import org.apache.aries.tx.control.service.xa.impl.TransactionControlImpl; +import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.service.coordinator.Coordination; +import org.osgi.service.coordinator.Coordinator; +import org.osgi.service.coordinator.Participant; +import org.osgi.service.transaction.control.LocalResource; +import org.osgi.service.transaction.control.ResourceProvider; +import org.osgi.service.transaction.control.ScopedWorkException; +import org.osgi.service.transaction.control.TransactionStatus; + +@RunWith(MockitoJUnitRunner.class) +public class TransactionControlRunningTest { + + @Mock + Coordinator coordinator; + @Mock + Coordination coordination1; + @Mock + Coordination coordination2; + + @Mock + ResourceProvider<Object> testProvider; + @Mock + LocalResource testResource; + + TransactionControlImpl txControl; + + Map<Class<?>, Object> variables1; + Map<Class<?>, Object> variables2; + + @Before + public void setUp() throws XAException { + variables1 = new HashMap<>(); + variables2 = new HashMap<>(); + + setupCoordinations(); + + txControl = new TransactionControlImpl(new GeronimoTransactionManager(), coordinator); + } + + /** + * Allow up to two Coordinations to be happening + */ + private void setupCoordinations() { + Mockito.when(coordinator.begin(Mockito.anyString(), Mockito.anyLong())).then(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + return coordination1; + }).then(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination2); + return coordination2; + }).thenThrow(new IllegalStateException("Only two coordinations at a time in the test")); + + Mockito.when(coordination1.getVariables()).thenReturn(variables1); + Mockito.when(coordination1.getId()).thenReturn(42L); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(null); + ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class); + Mockito.verify(coordination1, Mockito.atLeast(1)).addParticipant(captor.capture()); + + for(Participant p : captor.getAllValues()) { + p.ended(coordination1); + } + return null; + }).when(coordination1).end(); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(null); + ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class); + Mockito.verify(coordination1, Mockito.atLeast(1)).addParticipant(captor.capture()); + + for(Participant p : captor.getAllValues()) { + p.failed(coordination1); + } + return null; + }).when(coordination1).fail(Mockito.any(Throwable.class)); + + Mockito.when(coordination2.getVariables()).thenReturn(variables2); + Mockito.when(coordination2.getId()).thenReturn(43L); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class); + Mockito.verify(coordination2, Mockito.atLeast(1)).addParticipant(captor.capture()); + + for(Participant p : captor.getAllValues()) { + p.ended(coordination2); + } + return null; + }).when(coordination2).end(); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class); + Mockito.verify(coordination2, Mockito.atLeast(1)).addParticipant(captor.capture()); + + for(Participant p : captor.getAllValues()) { + p.failed(coordination2); + } + return null; + }).when(coordination2).fail(Mockito.any(Throwable.class)); + } + + @Test + public void testRequired() { + + AtomicReference<TransactionStatus> finalStatus = new AtomicReference<>(); + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + txControl.getCurrentContext().postCompletion(finalStatus::set); + return null; + }); + + assertEquals(COMMITTED, finalStatus.get()); + + } + + @Test + public void testRequiredMarkedRollback() { + + AtomicReference<TransactionStatus> finalStatus = new AtomicReference<>(); + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + txControl.getCurrentContext().postCompletion(finalStatus::set); + + txControl.setRollbackOnly(); + return null; + }); + + assertEquals(ROLLED_BACK, finalStatus.get()); + } + + @Test + public void testRequiredUserException() { + + AtomicReference<TransactionStatus> finalStatus = new AtomicReference<>(); + + Exception userEx = new Exception("Bang!"); + + try { + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + txControl.getCurrentContext().postCompletion(finalStatus::set); + + throw userEx; + }); + fail("Should not be reached"); + } catch (ScopedWorkException swe) { + assertSame(userEx, swe.getCause()); + } + + assertEquals(ROLLED_BACK, finalStatus.get()); + } + + @Test + public void testRequiredNoRollbackException() { + + AtomicReference<TransactionStatus> finalStatus = new AtomicReference<>(); + + Exception userEx = new BindException("Bang!"); + + try { + txControl.build() + .noRollbackFor(BindException.class) + .required(() -> { + + assertTrue(txControl.activeTransaction()); + + txControl.getCurrentContext().postCompletion(finalStatus::set); + + throw userEx; + }); + fail("Should not be reached"); + } catch (ScopedWorkException swe) { + assertSame(userEx, swe.getCause()); + } + + assertEquals(COMMITTED, finalStatus.get()); + } + + @Test + public void testTwoRequiredsNested() { + + AtomicReference<TransactionStatus> finalStatusOuter = new AtomicReference<>(); + AtomicReference<TransactionStatus> finalStatusInner = new AtomicReference<>(); + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + + txControl.getCurrentContext().postCompletion(finalStatusOuter::set); + + txControl.requiresNew(() -> { + assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey())); + + txControl.getCurrentContext().postCompletion(finalStatusInner::set); + return null; + }); + + return null; + }); + + assertEquals(COMMITTED, finalStatusOuter.get()); + assertEquals(COMMITTED, finalStatusInner.get()); + + } + + @Test + public void testTwoRequiredsNestedOuterMarkedRollback() { + + AtomicReference<TransactionStatus> finalStatusOuter = new AtomicReference<>(); + AtomicReference<TransactionStatus> finalStatusInner = new AtomicReference<>(); + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + + txControl.getCurrentContext().postCompletion(finalStatusOuter::set); + + txControl.setRollbackOnly(); + + txControl.requiresNew(() -> { + assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey())); + + txControl.getCurrentContext().postCompletion(finalStatusInner::set); + return null; + }); + + return null; + }); + + assertEquals(ROLLED_BACK, finalStatusOuter.get()); + assertEquals(COMMITTED, finalStatusInner.get()); + + } + + @Test + public void testTwoRequiredsNestedInnerMarkedRollback() { + + AtomicReference<TransactionStatus> finalStatusOuter = new AtomicReference<>(); + AtomicReference<TransactionStatus> finalStatusInner = new AtomicReference<>(); + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + + txControl.getCurrentContext().postCompletion(finalStatusOuter::set); + + txControl.requiresNew(() -> { + assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey())); + + txControl.getCurrentContext().postCompletion(finalStatusInner::set); + + txControl.setRollbackOnly(); + + return null; + }); + + return null; + }); + + assertEquals(COMMITTED, finalStatusOuter.get()); + assertEquals(ROLLED_BACK, finalStatusInner.get()); + + } + + @Test + public void testTwoRequiredsNestedBothMarkedRollback() { + + AtomicReference<TransactionStatus> finalStatusOuter = new AtomicReference<>(); + AtomicReference<TransactionStatus> finalStatusInner = new AtomicReference<>(); + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + + txControl.getCurrentContext().postCompletion(finalStatusOuter::set); + + txControl.setRollbackOnly(); + + txControl.requiresNew(() -> { + assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey())); + + txControl.getCurrentContext().postCompletion(finalStatusInner::set); + + txControl.setRollbackOnly(); + + return null; + }); + + return null; + }); + + assertEquals(ROLLED_BACK, finalStatusOuter.get()); + assertEquals(ROLLED_BACK, finalStatusInner.get()); + + } + + @Test + public void testTwoRequiredsNestedOuterThrowsException() { + + AtomicReference<TransactionStatus> finalStatusOuter = new AtomicReference<>(); + AtomicReference<TransactionStatus> finalStatusInner = new AtomicReference<>(); + + Exception userEx = new Exception("Bang!"); + + try { + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + + txControl.getCurrentContext().postCompletion(finalStatusOuter::set); + + txControl.setRollbackOnly(); + + txControl.requiresNew(() -> { + assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey())); + + txControl.getCurrentContext().postCompletion(finalStatusInner::set); + return null; + }); + + throw userEx; + }); + fail("Should not be reached"); + } catch (ScopedWorkException swe) { + assertSame(userEx, swe.getCause()); + } + + assertEquals(ROLLED_BACK, finalStatusOuter.get()); + assertEquals(COMMITTED, finalStatusInner.get()); + + } + + @Test + public void testTwoRequiredsNestedInnerThrowsException() { + + AtomicReference<TransactionStatus> finalStatusOuter = new AtomicReference<>(); + AtomicReference<TransactionStatus> finalStatusInner = new AtomicReference<>(); + + Exception userEx = new Exception("Bang!"); + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + + txControl.getCurrentContext().postCompletion(finalStatusOuter::set); + + try { + txControl.requiresNew(() -> { + assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey())); + + txControl.getCurrentContext().postCompletion(finalStatusInner::set); + + txControl.setRollbackOnly(); + + throw userEx; + }); + fail("Should not be reached!"); + } catch (ScopedWorkException swe) { + assertSame(userEx, swe.getCause()); + } + return null; + }); + + assertEquals(COMMITTED, finalStatusOuter.get()); + assertEquals(ROLLED_BACK, finalStatusInner.get()); + + } + + @Test + public void testTwoRequiredsNestedNoRollbackForInnerException() { + + AtomicReference<TransactionStatus> finalStatusOuter = new AtomicReference<>(); + AtomicReference<TransactionStatus> finalStatusInner = new AtomicReference<>(); + + Exception userEx = new BindException("Bang!"); + + try { + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + + txControl.getCurrentContext().postCompletion(finalStatusOuter::set); + + try { + txControl.build() + .noRollbackFor(BindException.class) + .requiresNew(() -> { + assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey())); + + txControl.getCurrentContext().postCompletion(finalStatusInner::set); + + throw userEx; + }); + fail("Should not be reached!"); + } catch (ScopedWorkException swe) { + throw swe.as(BindException.class); + } + + return null; + }); + fail("Should not be reached!"); + } catch (ScopedWorkException swe) { + assertSame(userEx, swe.getCause()); + } + + assertEquals(ROLLED_BACK, finalStatusOuter.get()); + assertEquals(COMMITTED, finalStatusInner.get()); + + } + +} Added: aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlStatusTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlStatusTest.java?rev=1734243&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlStatusTest.java (added) +++ aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlStatusTest.java Wed Mar 9 14:38:15 2016 @@ -0,0 +1,324 @@ +package org.apache.aries.tx.control.service.xa.impl; + +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 static org.osgi.service.transaction.control.TransactionStatus.ACTIVE; +import static org.osgi.service.transaction.control.TransactionStatus.MARKED_ROLLBACK; +import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION; + +import java.util.HashMap; +import java.util.Map; + +import javax.transaction.xa.XAException; + +import org.apache.aries.tx.control.service.xa.impl.TransactionControlImpl; +import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.service.coordinator.Coordination; +import org.osgi.service.coordinator.Coordinator; +import org.osgi.service.coordinator.Participant; +import org.osgi.service.transaction.control.LocalResource; +import org.osgi.service.transaction.control.ResourceProvider; + +@RunWith(MockitoJUnitRunner.class) +public class TransactionControlStatusTest { + + @Mock + Coordinator coordinator; + @Mock + Coordination coordination1; + @Mock + Coordination coordination2; + + @Mock + ResourceProvider<Object> testProvider; + @Mock + LocalResource testResource; + + TransactionControlImpl txControl; + + Object resource = new Object(); + Map<Class<?>, Object> variables1; + Map<Class<?>, Object> variables2; + + @Before + public void setUp() throws XAException { + + resource = new Object(); + variables1 = new HashMap<>(); + variables2 = new HashMap<>(); + + Mockito.when(coordination1.getVariables()).thenReturn(variables1); + Mockito.when(coordination2.getVariables()).thenReturn(variables2); + + txControl = new TransactionControlImpl(new GeronimoTransactionManager(), coordinator); + } + + @Test + public void testGetRollbackOnlyUnscopedNoCoord() { + try { + txControl.getRollbackOnly(); + fail("Should not be able to get rollback only"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testSetRollbackOnlyUnscopedNoCoord() { + try { + txControl.setRollbackOnly(); + fail("Should not be able to set rollback only"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testTranChecksUnscopedNoCoord() { + assertFalse(txControl.activeTransaction()); + assertFalse(txControl.activeScope()); + assertNull(txControl.getCurrentContext()); + } + + private void setupExistingCoordination() { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + Mockito.when(coordination1.getVariables()).thenReturn(variables1); + } + + @Test + public void testGetRollbackOnlyUnscopedWithCoordination() { + setupExistingCoordination(); + + try { + txControl.getRollbackOnly(); + fail("Should not be able to get rollback only"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testSetRollbackOnlyUnscopedWithCoordination() { + setupExistingCoordination(); + + + try { + txControl.setRollbackOnly(); + fail("Should not be able to set rollback only"); + } catch (IllegalStateException e) { + + } + } + + @Test + public void testTranChecksUnscopedWithCoordination() { + + setupExistingCoordination(); + + assertFalse(txControl.activeTransaction()); + assertFalse(txControl.activeScope()); + assertNull(txControl.getCurrentContext()); + } + + private void setupCoordinatorForSingleTransaction() { + setupCoordinatorForSingleTransaction(null); + } + + private void setupCoordinatorForSingleTransaction(Coordination existing) { + + Mockito.when(coordinator.peek()).thenReturn(existing); + + Mockito.when(coordinator.begin(Mockito.anyString(), Mockito.anyLong())) + .then(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + return coordination1; + }); + + + Mockito.doAnswer(i -> Mockito.when(coordinator.peek()).thenReturn(existing)) + .when(coordination1).end(); + Mockito.doAnswer(i -> Mockito.when(coordinator.peek()).thenReturn(existing) != null) + .when(coordination1).fail(Mockito.any(Throwable.class)); + + Mockito.when(coordination1.getVariables()).thenReturn(variables1); + } + + @Test + public void testGetRollbackOnlyScoped() { + setupCoordinatorForSingleTransaction(); + txControl.notSupported(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + try { + txControl.getRollbackOnly(); + fail("Should not be able to get or set rollback when there is no transaction"); + } catch (IllegalStateException ise) { + } + return null; + }); + } + + @Test + public void testSetRollbackOnlyScoped() { + setupCoordinatorForSingleTransaction(); + + txControl.notSupported(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + try { + txControl.setRollbackOnly(); + fail("Should not be able to get or set rollback when there is no transaction"); + } catch (IllegalStateException ise) { + } + return null; + }); + } + + @Test + public void testTranChecksScoped() { + + setupCoordinatorForSingleTransaction(); + txControl.notSupported(() -> { + assertFalse(txControl.activeTransaction()); + assertTrue(txControl.activeScope()); + assertNotNull(txControl.getCurrentContext()); + assertEquals(NO_TRANSACTION, txControl.getCurrentContext().getTransactionStatus()); + + return null; + }); + } + + @Test + public void testGetRollbackOnlyScopedExistingCoordination() { + setupCoordinatorForSingleTransaction(coordination2); + txControl.notSupported(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + try { + txControl.getRollbackOnly(); + fail("Should not be able to get or set rollback when there is no transaction"); + } catch (IllegalStateException ise) { + } + return null; + }); + } + + @Test + public void testSetRollbackOnlyScopedExistingCoordination() { + setupCoordinatorForSingleTransaction(coordination2); + + txControl.notSupported(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + try { + txControl.setRollbackOnly(); + fail("Should not be able to get or set rollback when there is no transaction"); + } catch (IllegalStateException ise) { + } + return null; + }); + } + + @Test + public void testTranChecksScopedExistingCoordination() { + + setupCoordinatorForSingleTransaction(coordination2); + txControl.notSupported(() -> { + assertFalse(txControl.activeTransaction()); + assertTrue(txControl.activeScope()); + assertNotNull(txControl.getCurrentContext()); + assertEquals(NO_TRANSACTION, txControl.getCurrentContext().getTransactionStatus()); + + return null; + }); + } + + @Test + public void testGetRollbackOnlyActive() { + setupCoordinatorForSingleTransaction(); + txControl.required(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + assertFalse(txControl.getRollbackOnly()); + return null; + }); + } + + @Test + public void testSetRollbackOnlyActive() { + setupCoordinatorForSingleTransaction(); + + txControl.required(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + assertFalse(txControl.getRollbackOnly()); + txControl.setRollbackOnly(); + assertTrue(txControl.getRollbackOnly()); + + return null; + }); + } + + @Test + public void testTranChecksActive() { + + setupCoordinatorForSingleTransaction(); + txControl.required(() -> { + assertTrue(txControl.activeTransaction()); + assertTrue(txControl.activeScope()); + assertNotNull(txControl.getCurrentContext()); + assertEquals(ACTIVE, txControl.getCurrentContext().getTransactionStatus()); + + txControl.setRollbackOnly(); + assertEquals(MARKED_ROLLBACK, txControl.getCurrentContext().getTransactionStatus()); + + return null; + }); + } + + @Test + public void testGetRollbackOnlyActiveExistingCoordination() { + setupCoordinatorForSingleTransaction(coordination2); + txControl.required(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + assertFalse(txControl.getRollbackOnly()); + return null; + }); + } + + @Test + public void testSetRollbackOnlyActiveExistingCoordination() { + setupCoordinatorForSingleTransaction(coordination2); + + txControl.required(() -> { + Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class)); + assertFalse(txControl.getRollbackOnly()); + txControl.setRollbackOnly(); + assertTrue(txControl.getRollbackOnly()); + + return null; + }); + } + + @Test + public void testTranChecksActiveExistingCoordination() { + + setupCoordinatorForSingleTransaction(coordination2); + txControl.required(() -> { + assertTrue(txControl.activeTransaction()); + assertTrue(txControl.activeScope()); + assertNotNull(txControl.getCurrentContext()); + assertEquals(ACTIVE, txControl.getCurrentContext().getTransactionStatus()); + + txControl.setRollbackOnly(); + assertEquals(MARKED_ROLLBACK, txControl.getCurrentContext().getTransactionStatus()); + + return null; + }); + } + +} Added: aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLifecycleTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLifecycleTest.java?rev=1734243&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLifecycleTest.java (added) +++ aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLifecycleTest.java Wed Mar 9 14:38:15 2016 @@ -0,0 +1,307 @@ +package org.apache.aries.tx.control.service.xa.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import javax.transaction.xa.XAException; + +import org.apache.aries.tx.control.service.xa.impl.TransactionControlImpl; +import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.service.coordinator.Coordination; +import org.osgi.service.coordinator.Coordinator; +import org.osgi.service.transaction.control.LocalResource; +import org.osgi.service.transaction.control.ResourceProvider; + +@RunWith(MockitoJUnitRunner.class) +public class TransactionLifecycleTest { + + @Mock + Coordinator coordinator; + @Mock + Coordination coordination1; + @Mock + Coordination coordination2; + + @Mock + ResourceProvider<Object> testProvider; + @Mock + LocalResource testResource; + + TransactionControlImpl txControl; + + Map<Class<?>, Object> variables1; + Map<Class<?>, Object> variables2; + + @Before + public void setUp() throws XAException { + variables1 = new HashMap<>(); + variables2 = new HashMap<>(); + + setupCoordinations(); + + txControl = new TransactionControlImpl(new GeronimoTransactionManager(), coordinator); + } + + /** + * Allow up to two Coordinations to be happening + */ + private void setupCoordinations() { + Mockito.when(coordinator.begin(Mockito.anyString(), Mockito.anyLong())).then(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + return coordination1; + }).then(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination2); + return coordination2; + }).thenThrow(new IllegalStateException("Only two coordinations at a time in the test")); + + Mockito.when(coordination1.getVariables()).thenReturn(variables1); + Mockito.when(coordination1.getId()).thenReturn(42L); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(null); + return null; + }).when(coordination1).end(); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(null); + return null; + }).when(coordination1).fail(Mockito.any(Throwable.class)); + + Mockito.when(coordination2.getVariables()).thenReturn(variables2); + Mockito.when(coordination2.getId()).thenReturn(43L); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + return null; + }).when(coordination2).end(); + Mockito.doAnswer(i -> { + Mockito.when(coordinator.peek()).thenReturn(coordination1); + return null; + }).when(coordination2).fail(Mockito.any(Throwable.class)); + } + + @Test + public void testRequired() { + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + return null; + }); + + } + + @Test + public void testNestedRequired() { + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE); + + txControl.required(() -> { + assertEquals(key, txControl.getCurrentContext().getTransactionKey()); + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible")); + txControl.getCurrentContext().putScopedValue("visible", Boolean.FALSE); + return null; + }); + + assertEquals(key, txControl.getCurrentContext().getTransactionKey()); + assertEquals(Boolean.FALSE, txControl.getCurrentContext().getScopedValue("visible")); + + return null; + }); + + } + + @Test + public void testNestedRequiredFromNoTran() { + + txControl.supports(() -> { + + assertFalse(txControl.activeTransaction()); + + txControl.getCurrentContext().putScopedValue("invisible", Boolean.TRUE); + + txControl.required(() -> { + assertTrue(txControl.activeTransaction()); + assertNull(txControl.getCurrentContext().getScopedValue("invisible")); + txControl.getCurrentContext().putScopedValue("invisible", Boolean.FALSE); + return null; + }); + + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("invisible")); + + return null; + }); + + } + + @Test + public void testRequiresNew() { + + txControl.requiresNew(() -> { + + assertTrue(txControl.activeTransaction()); + + return null; + }); + + } + + @Test + public void testNestedRequiresNew() { + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + txControl.getCurrentContext().putScopedValue("invisible", Boolean.TRUE); + + txControl.requiresNew(() -> { + assertFalse("Parent key " + key + " Child Key " + txControl.getCurrentContext().getTransactionKey(), + key.equals(txControl.getCurrentContext().getTransactionKey())); + assertNull(txControl.getCurrentContext().getScopedValue("invisible")); + txControl.getCurrentContext().putScopedValue("invisible", Boolean.FALSE); + return null; + }); + + assertEquals(key, txControl.getCurrentContext().getTransactionKey()); + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("invisible")); + + return null; + }); + + } + + @Test + public void testSupports() { + + txControl.supports(() -> { + + assertFalse(txControl.activeTransaction()); + + return null; + }); + + } + + @Test + public void testNestedSupports() { + + txControl.supports(() -> { + + assertFalse(txControl.activeTransaction()); + + txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE); + + txControl.supports(() -> { + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible")); + txControl.getCurrentContext().putScopedValue("visible", Boolean.FALSE); + return null; + }); + + assertEquals(Boolean.FALSE, txControl.getCurrentContext().getScopedValue("visible")); + + return null; + }); + + } + + @Test + public void testNestedSupportsInActiveTran() { + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE); + + txControl.supports(() -> { + assertEquals(key, txControl.getCurrentContext().getTransactionKey()); + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible")); + txControl.getCurrentContext().putScopedValue("visible", Boolean.FALSE); + return null; + }); + + assertEquals(key, txControl.getCurrentContext().getTransactionKey()); + assertEquals(Boolean.FALSE, txControl.getCurrentContext().getScopedValue("visible")); + + return null; + }); + + } + + @Test + public void testNotSupported() { + + txControl.notSupported(() -> { + + assertFalse(txControl.activeTransaction()); + + return null; + }); + + } + + @Test + public void testNestedNotSupported() { + + txControl.notSupported(() -> { + + assertFalse(txControl.activeTransaction()); + + txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE); + + txControl.notSupported(() -> { + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible")); + return null; + }); + + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible")); + + return null; + }); + + } + + @Test + public void testNestedNotSupportedInActiveTran() { + + txControl.required(() -> { + + assertTrue(txControl.activeTransaction()); + + Object key = txControl.getCurrentContext().getTransactionKey(); + txControl.getCurrentContext().putScopedValue("invisible", Boolean.TRUE); + + txControl.notSupported(() -> { + assertFalse(txControl.activeTransaction()); + assertNull(txControl.getCurrentContext().getScopedValue("invisible")); + txControl.getCurrentContext().putScopedValue("invisible", Boolean.FALSE); + + return null; + }); + + assertEquals(key, txControl.getCurrentContext().getTransactionKey()); + assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("invisible")); + + return null; + }); + + } + +}
