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/02/25 14:36:31 UTC

svn commit: r1732302 - in /aries/trunk/tx-control: tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/ tx-control-service-local/src/test/java/org/apa...

Author: timothyjward
Date: Thu Feb 25 13:36:31 2016
New Revision: 1732302

URL: http://svn.apache.org/viewvc?rev=1732302&view=rev
Log:
[tx-control] Throw the correct Exception types on different kinds of error

Modified:
    aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java

Modified: aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java?rev=1732302&r1=1732301&r2=1732302&view=diff
==============================================================================
--- aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java (original)
+++ aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java Thu Feb 25 13:36:31 2016
@@ -39,7 +39,6 @@ import javax.inject.Inject;
 import org.apache.aries.itest.AbstractIntegrationTest;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.Configuration;
@@ -49,6 +48,7 @@ import org.ops4j.pax.exam.junit.PaxExam;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerClass;
 import org.osgi.service.jdbc.DataSourceFactory;
+import org.osgi.service.transaction.control.ScopedWorkException;
 import org.osgi.service.transaction.control.TransactionControl;
 import org.osgi.service.transaction.control.TransactionRolledBackException;
 import org.osgi.service.transaction.control.jdbc.JDBCConnectionProviderFactory;
@@ -107,8 +107,8 @@ public class ExceptionManagementTransact
 						throw toThrow;
 					});
 			fail("An exception should occur!");
-		} catch (TransactionRolledBackException tre) {
-			assertSame(toThrow, tre.getCause());
+		} catch (ScopedWorkException swe) {
+			assertSame(toThrow, swe.getCause());
 		}
 
 		assertRollback();
@@ -127,8 +127,8 @@ public class ExceptionManagementTransact
 			fail("An exception should occur!");
 			// We have to catch Exception as the compiler complains
 			// otherwise
-		} catch (TransactionRolledBackException tre) {
-			assertSame(toThrow, tre.getCause());
+		} catch (ScopedWorkException swe) {
+			assertSame(toThrow, swe.getCause());
 		}
 
 		assertRollback();
@@ -137,7 +137,6 @@ public class ExceptionManagementTransact
 	//This test currently fails - the local implementation should probably
 	//use the coordinator a little differently
 	@Test
-	@Ignore
 	public void testPreCompletionException() {
 		RuntimeException toThrow = new RuntimeException("Bang!");
 		

Modified: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java?rev=1732302&r1=1732301&r2=1732302&view=diff
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java (original)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java Thu Feb 25 13:36:31 2016
@@ -7,50 +7,70 @@ import java.util.concurrent.atomic.Atomi
 import java.util.function.Consumer;
 
 import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Participant;
 import org.osgi.service.transaction.control.TransactionContext;
 import org.osgi.service.transaction.control.TransactionStatus;
 
-public abstract class AbstractTransactionContextImpl
-		implements TransactionContext {
+public abstract class AbstractTransactionContextImpl implements TransactionContext {
 
-	protected static class TransactionVariablesKey {}
+	protected static class TransactionVariablesKey {
+	}
+
+	protected final AtomicReference<Throwable> firstUnexpectedException = new AtomicReference<>();
+
+	protected final List<Throwable> subsequentExceptions = new ArrayList<>();
 
-	protected final AtomicReference<Exception> firstUnexpectedException = new AtomicReference<>();
-	
-	protected final List<Exception> subsequentExceptions = new ArrayList<>();
-	
 	protected final Coordination coordination;
-	
+
 	protected final List<Runnable> preCompletion = new ArrayList<>();
-	
+
 	protected final List<Consumer<TransactionStatus>> postCompletion = new ArrayList<>();
-	
+
 	public AbstractTransactionContextImpl(Coordination coordination) {
 		this.coordination = coordination;
+
+		coordination.addParticipant(new Participant() {
+
+			@Override
+			public void failed(Coordination coordination) throws Exception {
+				Throwable failure = coordination.getFailure();
+				recordFailure(failure);
+				safeSetRollbackOnly();
+			}
+
+			@Override
+			public void ended(Coordination coordination) throws Exception {
+				if (isAlive()) {
+					// TODO log this
+					recordFailure(new IllegalStateException(
+							"The surrounding coordination ended before the transaction completed"));
+					safeSetRollbackOnly();
+
+				}
+			}
+		});
 	}
 
 	@SuppressWarnings("unchecked")
 	@Override
 	public Object getScopedValue(Object key) {
-		return ((HashMap<Object,Object>) coordination.getVariables()
-				.getOrDefault(TransactionVariablesKey.class, new HashMap<>()))
-						.get(key);
+		return ((HashMap<Object, Object>) coordination.getVariables().getOrDefault(TransactionVariablesKey.class,
+				new HashMap<>())).get(key);
 	}
 
 	@SuppressWarnings("unchecked")
 	@Override
 	public void putScopedValue(Object key, Object value) {
-		((HashMap<Object,Object>) coordination.getVariables().computeIfAbsent(
-				TransactionVariablesKey.class, k -> new HashMap<>())).put(key, value);
+		((HashMap<Object, Object>) coordination.getVariables().computeIfAbsent(TransactionVariablesKey.class,
+				k -> new HashMap<>())).put(key, value);
 	}
-	
-	
+
 	protected void beforeCompletion(Runnable onFirstError) {
 		preCompletion.stream().forEach(r -> {
 			try {
 				r.run();
 			} catch (Exception e) {
-				if(firstUnexpectedException.compareAndSet(null, e)) {
+				if (firstUnexpectedException.compareAndSet(null, e)) {
 					onFirstError.run();
 				} else {
 					subsequentExceptions.add(e);
@@ -59,4 +79,27 @@ public abstract class AbstractTransactio
 			}
 		});
 	}
+
+	protected void afterCompletion(TransactionStatus status) {
+		postCompletion.stream().forEach(c -> {
+			try {
+				c.accept(status);
+			} catch (Exception e) {
+				recordFailure(e);
+				// TODO log this
+			}
+		});
+	}
+
+	protected abstract boolean isAlive();
+
+	protected void recordFailure(Throwable failure) {
+		if (!firstUnexpectedException.compareAndSet(null, failure)) {
+			subsequentExceptions.add(failure);
+		}
+	}
+
+	protected abstract void safeSetRollbackOnly();
+
+	public abstract void finish();
 }

Modified: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java?rev=1732302&r1=1732301&r2=1732302&view=diff
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java (original)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java Thu Feb 25 13:36:31 2016
@@ -2,12 +2,12 @@ package org.apache.aries.tx.control.serv
 
 import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
 
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
 import javax.transaction.xa.XAResource;
 
 import org.osgi.service.coordinator.Coordination;
-import org.osgi.service.coordinator.Participant;
 import org.osgi.service.transaction.control.LocalResource;
 import org.osgi.service.transaction.control.TransactionContext;
 import org.osgi.service.transaction.control.TransactionStatus;
@@ -15,39 +15,10 @@ import org.osgi.service.transaction.cont
 public class NoTransactionContextImpl extends AbstractTransactionContextImpl
 		implements TransactionContext {
 
-	volatile boolean						finished			= false;
+	private final AtomicBoolean finished = new AtomicBoolean(false);
 
 	public NoTransactionContextImpl(Coordination coordination) {
 		super(coordination);
-
-		coordination.addParticipant(new Participant() {
-
-			@Override
-			public void failed(Coordination coordination) throws Exception {
-				finished();
-			}
-
-			@Override
-			public void ended(Coordination coordination) throws Exception {
-				finished();
-			}
-
-			private void finished() {
-				beforeCompletion(() -> {});
-				afterCompletion();
-			}
-
-			private void afterCompletion() {
-				postCompletion.stream().forEach(c -> {
-					try {
-						c.accept(NO_TRANSACTION);
-					} catch (Exception e) {
-						firstUnexpectedException.compareAndSet(null, e);
-						// TODO log this
-					}
-				});
-			}
-		});
 	}
 
 	@Override
@@ -76,6 +47,7 @@ public class NoTransactionContextImpl ex
 			throw new IllegalStateException(
 					"The transaction context has finished");
 		}
+		
 		preCompletion.add(job);
 	}
 
@@ -109,4 +81,21 @@ public class NoTransactionContextImpl ex
 	public boolean supportsLocal() {
 		return false;
 	}
+
+	@Override
+	protected boolean isAlive() {
+		return !finished.get();
+	}
+	
+	@Override
+	public void finish() {
+		if(finished.compareAndSet(false, true)) {
+			beforeCompletion(() -> {});
+			afterCompletion(NO_TRANSACTION);
+		}
+	}
+
+	@Override
+	protected void safeSetRollbackOnly() {
+	}
 }

Modified: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java?rev=1732302&r1=1732301&r2=1732302&view=diff
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java (original)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java Thu Feb 25 13:36:31 2016
@@ -1,5 +1,6 @@
 package org.apache.aries.tx.control.service.local.impl;
 
+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;
@@ -8,107 +9,24 @@ import static org.osgi.service.transacti
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 
 import javax.transaction.xa.XAResource;
 
 import org.osgi.service.coordinator.Coordination;
-import org.osgi.service.coordinator.Participant;
 import org.osgi.service.transaction.control.LocalResource;
 import org.osgi.service.transaction.control.TransactionContext;
 import org.osgi.service.transaction.control.TransactionStatus;
 
-public class TransactionContextImpl extends AbstractTransactionContextImpl
-		implements TransactionContext {
+public class TransactionContextImpl extends AbstractTransactionContextImpl implements TransactionContext {
 
-	final List<LocalResource>				resources			= new ArrayList<>();
+	final List<LocalResource> resources = new ArrayList<>();
 
-	private volatile TransactionStatus		tranStatus;
+	private AtomicReference<TransactionStatus> tranStatus = new AtomicReference<>(ACTIVE);
 
 	public TransactionContextImpl(Coordination coordination) {
 		super(coordination);
-
-		tranStatus = TransactionStatus.ACTIVE;
-
-		coordination.addParticipant(new Participant() {
-
-			@Override
-			public void failed(Coordination coordination) throws Exception {
-				setRollbackOnly();
-
-				beforeCompletion();
-
-				vanillaRollback();
-
-				afterCompletion();
-			}
-
-			private void vanillaRollback() {
-
-				tranStatus = ROLLING_BACK;
-
-				resources.stream().forEach(lr -> {
-					try {
-						lr.rollback();
-					} catch (Exception e) {
-						// TODO log this
-					}
-				});
-
-				tranStatus = ROLLED_BACK;
-			}
-
-			private void beforeCompletion() {
-				TransactionContextImpl.this.beforeCompletion(() -> setRollbackOnly());
-			}
-
-			private void afterCompletion() {
-				postCompletion.stream().forEach(c -> {
-					try {
-						c.accept(tranStatus);
-					} catch (Exception e) {
-						firstUnexpectedException.compareAndSet(null, e);
-						// TODO log this
-					}
-				});
-			}
-
-			@Override
-			public void ended(Coordination coordination) throws Exception {
-				beforeCompletion();
-
-				if (getRollbackOnly()) {
-					vanillaRollback();
-				} else {
-					tranStatus = COMMITTING;
-
-					List<LocalResource> committed = new ArrayList<>(
-							resources.size());
-					List<LocalResource> rolledback = new ArrayList<>(0);
-
-					resources.stream().forEach(lr -> {
-						try {
-							if (getRollbackOnly()) {
-								lr.rollback();
-								rolledback.add(lr);
-							} else {
-								lr.commit();
-								committed.add(lr);
-							}
-						} catch (Exception e) {
-							firstUnexpectedException.compareAndSet(null, e);
-							if (committed.isEmpty()) {
-								tranStatus = ROLLING_BACK;
-							}
-							rolledback.add(lr);
-						}
-					});
-					tranStatus = tranStatus == ROLLING_BACK ? ROLLED_BACK
-							: COMMITTED;
-				}
-				afterCompletion();
-			}
-		});
 	}
 
 	@Override
@@ -118,63 +36,74 @@ public class TransactionContextImpl exte
 
 	@Override
 	public boolean getRollbackOnly() throws IllegalStateException {
-		switch (tranStatus) {
-			case MARKED_ROLLBACK :
-			case ROLLING_BACK :
-			case ROLLED_BACK :
+		switch (tranStatus.get()) {
+			case MARKED_ROLLBACK:
+			case ROLLING_BACK:
+			case ROLLED_BACK:
 				return true;
-			default :
+			default:
 				return false;
 		}
 	}
 
 	@Override
 	public void setRollbackOnly() throws IllegalStateException {
-		switch (tranStatus) {
-			case ACTIVE :
-			case MARKED_ROLLBACK :
-				tranStatus = MARKED_ROLLBACK;
+		TransactionStatus status = tranStatus.get();
+		switch (status) {
+			case ACTIVE:
+			case MARKED_ROLLBACK:
+				if(!tranStatus.compareAndSet(status, MARKED_ROLLBACK))
+					setRollbackOnly();
 				break;
-			case COMMITTING :
+			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 :
+				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");
+			default:
+				throw new IllegalStateException("The transaction is in an unkown state");
+		}
+	}
+	
+	@Override
+	protected void safeSetRollbackOnly() {
+		TransactionStatus status = tranStatus.get();
+		switch (status) {
+			case ACTIVE:
+			case MARKED_ROLLBACK:
+				if(!tranStatus.compareAndSet(status, MARKED_ROLLBACK))
+					safeSetRollbackOnly();
+				break;
+			default:
+				break;
 		}
 	}
 
 	@Override
 	public TransactionStatus getTransactionStatus() {
-		return tranStatus;
+		return tranStatus.get();
 	}
 
 	@Override
 	public void preCompletion(Runnable job) throws IllegalStateException {
-		if (tranStatus.compareTo(MARKED_ROLLBACK) > 0) {
-			throw new IllegalStateException(
-					"The current transaction is in state " + tranStatus);
+		if (tranStatus.get().compareTo(MARKED_ROLLBACK) > 0) {
+			throw new IllegalStateException("The current transaction is in state " + tranStatus);
 		}
 
 		preCompletion.add(job);
 	}
 
 	@Override
-	public void postCompletion(Consumer<TransactionStatus> job)
-			throws IllegalStateException {
-		if (tranStatus == COMMITTED || tranStatus == ROLLED_BACK) {
-			throw new IllegalStateException(
-					"The current transaction is in state " + tranStatus);
+	public void postCompletion(Consumer<TransactionStatus> job) throws IllegalStateException {
+		TransactionStatus status = tranStatus.get();
+		if (status == COMMITTED || status == ROLLED_BACK) {
+			throw new IllegalStateException("The current transaction is in state " + tranStatus);
 		}
 
 		postCompletion.add(job);
@@ -187,9 +116,8 @@ public class TransactionContextImpl exte
 
 	@Override
 	public void registerLocalResource(LocalResource resource) {
-		if (tranStatus.compareTo(MARKED_ROLLBACK) > 0) {
-			throw new IllegalStateException(
-					"The current transaction is in state " + tranStatus);
+		if (tranStatus.get().compareTo(MARKED_ROLLBACK) > 0) {
+			throw new IllegalStateException("The current transaction is in state " + tranStatus);
 		}
 		resources.add(resource);
 	}
@@ -203,4 +131,64 @@ public class TransactionContextImpl exte
 	public boolean supportsLocal() {
 		return true;
 	}
+
+	@Override
+	protected boolean isAlive() {
+		TransactionStatus status = tranStatus.get();
+		return status != COMMITTED && status != ROLLED_BACK;
+	}
+
+	@Override
+	public void finish() {
+		
+		beforeCompletion(() -> setRollbackOnly());
+
+		TransactionStatus status;
+
+		if (getRollbackOnly()) {
+			vanillaRollback();
+			status = ROLLED_BACK;
+		} else {
+			tranStatus.set(COMMITTING);
+
+			List<LocalResource> committed = new ArrayList<>(resources.size());
+			List<LocalResource> rolledback = new ArrayList<>(0);
+
+			resources.stream().forEach(lr -> {
+				try {
+					if (getRollbackOnly()) {
+						lr.rollback();
+						rolledback.add(lr);
+					} else {
+						lr.commit();
+						committed.add(lr);
+					}
+				} catch (Exception e) {
+					firstUnexpectedException.compareAndSet(null, e);
+					if (committed.isEmpty()) {
+						tranStatus.set(ROLLING_BACK);
+					}
+					rolledback.add(lr);
+				}
+			});
+			status = tranStatus.updateAndGet(ts -> ts == ROLLING_BACK ? ROLLED_BACK : COMMITTED);
+		}
+		afterCompletion(status);
+	}
+	
+	private void vanillaRollback() {
+		
+		tranStatus.set(ROLLING_BACK);
+	
+		resources.stream().forEach(lr -> {
+				try {
+					lr.rollback();
+				} catch (Exception e) {
+					// TODO log this
+					recordFailure(e);
+				}
+			});
+		
+		tranStatus.set(ROLLED_BACK);
+	}
 }

Modified: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java?rev=1732302&r1=1732301&r2=1732302&view=diff
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java (original)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java Thu Feb 25 13:36:31 2016
@@ -2,12 +2,14 @@ package org.apache.aries.tx.control.serv
 
 import static java.util.Optional.ofNullable;
 import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK;
 
 import java.util.concurrent.Callable;
 
 import org.osgi.service.coordinator.Coordination;
 import org.osgi.service.coordinator.CoordinationException;
 import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.transaction.control.ScopedWorkException;
 import org.osgi.service.transaction.control.TransactionBuilder;
 import org.osgi.service.transaction.control.TransactionContext;
 import org.osgi.service.transaction.control.TransactionControl;
@@ -50,18 +52,19 @@ public class TransactionControlImpl impl
 				throw re;
 			}
 
-			return doWork(work, currentCoord, endCoordination);
+			return doWork(work, currentTran, currentCoord, endCoordination);
 		}
 
 		@Override
 		public <T> T requiresNew(Callable<T> work)
 				throws TransactionException, TransactionRolledBackException {
 			Coordination currentCoord = null;
+			AbstractTransactionContextImpl currentTran;
 			try {
 				currentCoord = coordinator.begin(
 						"Resource-Local-Transaction.REQUIRES_NEW", 30000);
 
-				AbstractTransactionContextImpl currentTran = new TransactionContextImpl(
+				currentTran = new TransactionContextImpl(
 						currentCoord);
 				currentCoord.getVariables().put(TransactionContextKey.class,
 						currentTran);
@@ -71,7 +74,7 @@ public class TransactionControlImpl impl
 				throw re;
 			}
 
-			return doWork(work, currentCoord, true);
+			return doWork(work, currentTran, currentCoord, true);
 		}
 
 		@Override
@@ -101,7 +104,7 @@ public class TransactionControlImpl impl
 				throw re;
 			}
 
-			return doWork(work, currentCoord, endCoordination);
+			return doWork(work, currentTran, currentCoord, endCoordination);
 		}
 
 		@Override
@@ -133,36 +136,71 @@ public class TransactionControlImpl impl
 				}
 				throw re;
 			}
-			return doWork(work, currentCoord, endCoordination);
+			return doWork(work, currentTran, currentCoord, endCoordination);
 		}
 
 		private <R> R doWork(Callable<R> transactionalWork,
-				Coordination currentCoord, boolean endCoordination) {
+				AbstractTransactionContextImpl currentTran, Coordination currentCoord, boolean endCoordination) {
+			R result;
 			try {
-				R result = transactionalWork.call();
+				result = transactionalWork.call();
 
-				if (endCoordination) {
-					currentCoord.end();
-				}
-				return result;
 			} catch (Throwable t) {
 				//TODO handle noRollbackFor
 				currentCoord.fail(t);
+				try {
+					currentTran.finish();
+				} catch (Exception e) {
+					currentTran.recordFailure(e);
+				}
 				if (endCoordination) {
 					try {
 						currentCoord.end();
 					} catch (CoordinationException ce) {
-						if(ce.getType() == CoordinationException.FAILED) {
-							throw new TransactionRolledBackException("The transaction was rolled back due to a failure", ce.getCause());
-						} else {
-							throw ce;
+						if(ce.getType() != CoordinationException.FAILED) {
+							currentTran.recordFailure(ce);
 						}
 					}
 				}
-				TransactionControlImpl.<RuntimeException> throwException(t);
+				ScopedWorkException workException = new ScopedWorkException("The scoped work threw an exception", t, 
+						endCoordination ? null : currentTran);
+				Throwable throwable = currentTran.firstUnexpectedException.get();
+				if(throwable != null) {
+					workException.addSuppressed(throwable);
+				}
+				currentTran.subsequentExceptions.stream().forEach(workException::addSuppressed);
+				
+				throw workException;
+			}
+			
+			try {
+				currentTran.finish();
+			} catch (Exception e) {
+				currentTran.recordFailure(e);
+				currentCoord.fail(e);
+			}
+			try {
+				if (endCoordination) {
+					currentCoord.end();
+				}
+			} catch (CoordinationException ce) {
+				if(ce.getType() != CoordinationException.FAILED) {
+					currentTran.recordFailure(ce);
+				}
+			}
+			
+			Throwable throwable = currentTran.firstUnexpectedException.get();
+			if(throwable != null) {
+				TransactionException te = currentTran.getTransactionStatus() == ROLLED_BACK ?
+						new TransactionRolledBackException("The transaction rolled back due to a failure", throwable) :
+						new TransactionException("There was an error in the Transaction completion.", throwable);
+				
+				currentTran.subsequentExceptions.stream().forEach(te::addSuppressed);
+				
+				throw te;
 			}
-			throw new TransactionException(
-					"The code here should never be reached");
+			
+			return result;
 		}
 	}
 
@@ -244,21 +282,6 @@ public class TransactionControlImpl impl
 		return toUse;
 	}
 
-	/**
-	 * Borrowed from the netty project as a way to avoid wrapping checked
-	 * exceptions Viewable at https://github.com/netty/netty/
-	 * netty/common/src/main/java/io/netty/util/internal/PlatformDependent.java
-	 * 
-	 * @param t
-	 * @return
-	 * @throws T
-	 */
-	@SuppressWarnings("unchecked")
-	private static <T extends Throwable> T throwException(Throwable t)
-			throws T {
-		throw (T) t;
-	}
-
 	@Override
 	public void ignoreException(Throwable t) throws IllegalStateException {
 		// TODO Auto-generated method stub

Modified: aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java?rev=1732302&r1=1732301&r2=1732302&view=diff
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java (original)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java Thu Feb 25 13:36:31 2016
@@ -22,7 +22,6 @@ import org.mockito.runners.MockitoJUnitR
 import org.osgi.service.coordinator.Coordination;
 import org.osgi.service.coordinator.Participant;
 import org.osgi.service.transaction.control.LocalResource;
-import org.osgi.service.transaction.control.TransactionContext;
 
 @RunWith(MockitoJUnitRunner.class)
 public class NoTransactionContextTest {
@@ -36,7 +35,7 @@ public class NoTransactionContextTest {
 	
 	Map<Class<?>, Object> variables;
 	
-	TransactionContext ctx;
+	AbstractTransactionContextImpl ctx;
 	
 	@Before
 	public void setUp() {
@@ -97,7 +96,7 @@ public class NoTransactionContextTest {
 	}
 	
 	@Test
-	public void testPreCompletionEnd() throws Exception {
+	public void testPreCompletion() throws Exception {
 		
 		AtomicInteger value = new AtomicInteger(0);
 		
@@ -105,8 +104,7 @@ public class NoTransactionContextTest {
 		
 		assertEquals(0, value.getAndSet(1));
 		
-		Participant participant = getParticipant();
-		participant.ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}
@@ -123,11 +121,13 @@ public class NoTransactionContextTest {
 		Participant participant = getParticipant();
 		participant.failed(coordination);
 		
+		ctx.finish();
+		
 		assertEquals(5, value.get());
 	}
 
 	@Test
-	public void testPostCompletionEnd() throws Exception {
+	public void testPostCompletion() throws Exception {
 		
 		AtomicInteger value = new AtomicInteger(0);
 		
@@ -138,8 +138,7 @@ public class NoTransactionContextTest {
 		
 		assertEquals(0, value.getAndSet(1));
 		
-		Participant participant = getParticipant();
-		participant.ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}
@@ -159,11 +158,13 @@ public class NoTransactionContextTest {
 		Participant participant = getParticipant();
 		participant.failed(coordination);
 		
+		ctx.finish();
+		
 		assertEquals(5, value.get());
 	}
 
 	@Test
-	public void testPostCompletionIsAfterPreCompletionEnd() throws Exception {
+	public void testPostCompletionIsAfterPreCompletion() throws Exception {
 		
 		AtomicInteger value = new AtomicInteger(0);
 		
@@ -173,8 +174,7 @@ public class NoTransactionContextTest {
 			value.compareAndSet(3, 5);
 		});
 		
-		Participant participant = getParticipant();
-		participant.ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}
@@ -193,6 +193,8 @@ public class NoTransactionContextTest {
 		Participant participant = getParticipant();
 		participant.failed(coordination);
 		
+		ctx.finish();
+		
 		assertEquals(5, value.get());
 	}
 
@@ -210,8 +212,7 @@ public class NoTransactionContextTest {
 			value.compareAndSet(3, 5);
 		});
 		
-		Participant participant = getParticipant();
-		participant.ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}

Modified: 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-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java?rev=1732302&r1=1732301&r2=1732302&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-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java Thu Feb 25 13:36:31 2016
@@ -29,7 +29,6 @@ import org.mockito.runners.MockitoJUnitR
 import org.osgi.service.coordinator.Coordination;
 import org.osgi.service.coordinator.Participant;
 import org.osgi.service.transaction.control.LocalResource;
-import org.osgi.service.transaction.control.TransactionContext;
 import org.osgi.service.transaction.control.TransactionException;
 
 @RunWith(MockitoJUnitRunner.class)
@@ -44,7 +43,7 @@ public class TransactionContextTest {
 	
 	Map<Class<?>, Object> variables;
 	
-	TransactionContext ctx;
+	AbstractTransactionContextImpl ctx;
 	
 	@Before
 	public void setUp() {
@@ -107,7 +106,7 @@ public class TransactionContextTest {
 	}
 	
 	@Test
-	public void testPreCompletionEnd() throws Exception {
+	public void testPreCompletion() throws Exception {
 		
 		AtomicInteger value = new AtomicInteger(0);
 		
@@ -118,10 +117,7 @@ public class TransactionContextTest {
 		
 		assertEquals(0, value.getAndSet(1));
 		
-		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
-		Mockito.verify(coordination).addParticipant(captor.capture());
-		
-		captor.getValue().ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}
@@ -138,16 +134,19 @@ public class TransactionContextTest {
 		
 		assertEquals(0, value.getAndSet(1));
 		
+		
 		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
 		Mockito.verify(coordination).addParticipant(captor.capture());
 		
 		captor.getValue().failed(coordination);
 		
+		ctx.finish();
+
 		assertEquals(5, value.get());
 	}
 
 	@Test
-	public void testPostCompletionEnd() throws Exception {
+	public void testPostCompletion() throws Exception {
 		
 		AtomicInteger value = new AtomicInteger(0);
 		
@@ -158,10 +157,7 @@ public class TransactionContextTest {
 		
 		assertEquals(0, value.getAndSet(1));
 		
-		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
-		Mockito.verify(coordination).addParticipant(captor.capture());
-		
-		captor.getValue().ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}
@@ -183,6 +179,8 @@ public class TransactionContextTest {
 		
 		captor.getValue().failed(coordination);
 		
+		ctx.finish();
+		
 		assertEquals(5, value.get());
 	}
 
@@ -200,10 +198,7 @@ public class TransactionContextTest {
 			value.compareAndSet(3, 5);
 		});
 		
-		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
-		Mockito.verify(coordination).addParticipant(captor.capture());
-		
-		captor.getValue().ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}
@@ -222,10 +217,7 @@ public class TransactionContextTest {
 			value.compareAndSet(3, 5);
 		});
 		
-		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
-		Mockito.verify(coordination).addParticipant(captor.capture());
-		
-		captor.getValue().ended(coordination);
+		ctx.finish();
 		
 		assertEquals(5, value.get());
 	}
@@ -241,9 +233,8 @@ public class TransactionContextTest {
 	@Test(expected=IllegalStateException.class)
 	public void testPreCompletionAfterEnd() throws Exception {
 		
-		getParticipant().ended(coordination);
+		ctx.finish();
 		
-		Mockito.when(coordination.isTerminated()).thenReturn(true);
 		ctx.preCompletion(() -> {});
 	}
 
@@ -252,16 +243,16 @@ public class TransactionContextTest {
 		
 		getParticipant().failed(coordination);
 		
-		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.finish();
+		
 		ctx.preCompletion(() -> {});
 	}
 
 	@Test(expected=IllegalStateException.class)
 	public void testPostCompletionAfterEnd() throws Exception {
 		
-		getParticipant().ended(coordination);
+		ctx.finish();
 		
-		Mockito.when(coordination.isTerminated()).thenReturn(true);
 		ctx.postCompletion(x -> {});
 	}
 
@@ -270,12 +261,13 @@ public class TransactionContextTest {
 		
 		getParticipant().failed(coordination);
 		
-		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.finish();
+		
 		ctx.postCompletion(x -> {});
 	}
 
 	@Test
-	public void testLocalResourceEnd() throws Exception {
+	public void testLocalResource() throws Exception {
 		ctx.registerLocalResource(localResource);
 		
 		Mockito.doAnswer(i -> {
@@ -283,13 +275,29 @@ public class TransactionContextTest {
 			return null;
 		}).when(localResource).commit();
 		
-		getParticipant().ended(coordination);
+		ctx.finish();
 		
 		Mockito.verify(localResource).commit();
 	}
+	
+	@Test
+	public void testLocalResourceEarlyEnd() throws Exception {
+		ctx.registerLocalResource(localResource);
+		
+		Mockito.doAnswer(i -> {
+			assertEquals(COMMITTING, ctx.getTransactionStatus());
+			return null;
+		}).when(localResource).commit();
+		
+		getParticipant().ended(coordination);
+		
+		ctx.finish();
+		
+		Mockito.verify(localResource).rollback();
+	}
 
 	@Test
-	public void testLocalResourceEndRollbackOnly() throws Exception {
+	public void testLocalResourceRollbackOnly() throws Exception {
 		ctx.registerLocalResource(localResource);
 		ctx.setRollbackOnly();
 		
@@ -298,7 +306,7 @@ public class TransactionContextTest {
 			return null;
 		}).when(localResource).rollback();
 		
-		getParticipant().ended(coordination);
+		ctx.finish();
 		
 		Mockito.verify(localResource).rollback();
 	}
@@ -314,11 +322,13 @@ public class TransactionContextTest {
 		
 		getParticipant().failed(coordination);
 		
+		ctx.finish();
+		
 		Mockito.verify(localResource).rollback();
 	}
 	
 	@Test
-	public void testLocalResourceEndPreCommitException() throws Exception {
+	public void testLocalResourcePreCommitException() throws Exception {
 		ctx.registerLocalResource(localResource);
 		
 		Mockito.doAnswer(i -> {
@@ -328,13 +338,13 @@ public class TransactionContextTest {
 		
 		ctx.preCompletion(() -> { throw new IllegalArgumentException(); });
 		
-		getParticipant().ended(coordination);
+		ctx.finish();
 		
 		Mockito.verify(localResource).rollback();
 	}
 
 	@Test
-	public void testLocalResourceEndPostCommitException() throws Exception {
+	public void testLocalResourcePostCommitException() throws Exception {
 		ctx.registerLocalResource(localResource);
 		
 		Mockito.doAnswer(i -> {
@@ -347,7 +357,7 @@ public class TransactionContextTest {
 				throw new IllegalArgumentException(); 
 			});
 		
-		getParticipant().ended(coordination);
+		ctx.finish();
 		
 		Mockito.verify(localResource).commit();
 	}
@@ -370,7 +380,7 @@ public class TransactionContextTest {
 			return null;
 		}).when(localResource2).rollback();
 		
-		getParticipant().ended(coordination);
+		ctx.finish();
 		
 		Mockito.verify(localResource).commit();
 		Mockito.verify(localResource2).rollback();