You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by ti...@apache.org on 2016/03/09 15:38:16 UTC

svn commit: r1734243 [2/2] - in /aries/trunk/tx-control: ./ tx-control-itests/ tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ tx-control-service-common/ tx-control-service-common/src/ tx-control-service-common/src/main/ tx-control-...

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;
+		});
+
+	}
+
+}