tx-control-service spec compliance
No Transaction scope should allow post-completion registrations from inside pre-completion callbacks
diff --git a/tx-control-services/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java b/tx-control-services/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java
index aa3d6d0..c345d64 100644
--- a/tx-control-services/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java
+++ b/tx-control-services/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java
@@ -20,7 +20,7 @@
 
 import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
 
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 import javax.transaction.xa.XAResource;
@@ -32,7 +32,11 @@
 public class NoTransactionContextImpl extends AbstractTransactionContextImpl
 		implements TransactionContext {
 
-	private final AtomicBoolean finished = new AtomicBoolean(false);
+	private enum Status {
+		WORKING, PRE, POST;
+	}
+	
+	private final AtomicReference<Status> status = new AtomicReference<>(Status.WORKING);
 
 	public NoTransactionContextImpl() {
 		super();
@@ -60,9 +64,9 @@
 
 	@Override
 	public void preCompletion(Runnable job) throws IllegalStateException {
-		if (finished.get()) {
+		if (status.get() != Status.WORKING) {
 			throw new IllegalStateException(
-					"The transaction context has finished");
+					"The scoped work has returned. No more pre-completion callbacks can be registered");
 		}
 		
 		preCompletion.add(job);
@@ -71,9 +75,9 @@
 	@Override
 	public void postCompletion(Consumer<TransactionStatus> job)
 			throws IllegalStateException {
-		if (finished.get()) {
+		if (status.get() == Status.POST) {
 			throw new IllegalStateException(
-					"The transaction context has finished");
+					"Post completion callbacks have begun. No more post-completion callbacks can be registered");
 		}
 
 		postCompletion.add(job);
@@ -106,13 +110,14 @@
 
 	@Override
 	protected boolean isAlive() {
-		return !finished.get();
+		return status.get() == Status.WORKING;
 	}
 	
 	@Override
 	public void finish() {
-		if(finished.compareAndSet(false, true)) {
+		if(status.compareAndSet(Status.WORKING, Status.PRE)) {
 			beforeCompletion(() -> {});
+			status.set(Status.POST);
 			afterCompletion(NO_TRANSACTION);
 		}
 	}
diff --git a/tx-control-services/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java b/tx-control-services/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java
index 4cf4c81..d1e1219 100644
--- a/tx-control-services/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java
+++ b/tx-control-services/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
+import static org.osgi.service.transaction.control.TransactionStatus.COMMITTED;
 import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
 
 import java.util.concurrent.atomic.AtomicInteger;
@@ -236,6 +237,20 @@
 		
 		assertEquals(5, value.get());
 	}
+	
+	@Test
+	public void testPreCompletionRegisterPostCompletion() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(2);
+		
+		ctx.preCompletion(() -> ctx.postCompletion(t -> value.compareAndSet(1, t.ordinal())));
+		
+		assertEquals(2, value.getAndSet(1));
+		
+		ctx.finish();
+		
+		assertEquals(NO_TRANSACTION.ordinal(), value.get());
+	}
 
 	@Test(expected=IllegalStateException.class)
 	public void testPreCompletionAfterEnd() throws Exception {