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/16 16:35:02 UTC

svn commit: r1730701 - in /aries/trunk/tx-control: ./ tx-control-service-local/ tx-control-service-local/src/ tx-control-service-local/src/main/ tx-control-service-local/src/main/java/ tx-control-service-local/src/main/java/org/ tx-control-service-loca...

Author: timothyjward
Date: Tue Feb 16 15:35:02 2016
New Revision: 1730701

URL: http://svn.apache.org/viewvc?rev=1730701&view=rev
Log:
[tx-control] Add a basic TransactionControl service implementation

* Local only
* Coordinator based
* Currently no support for rollbackFor/noRollbackFor

Added:
    aries/trunk/tx-control/tx-control-service-local/
    aries/trunk/tx-control/tx-control-service-local/pom.xml
    aries/trunk/tx-control/tx-control-service-local/src/
    aries/trunk/tx-control/tx-control-service-local/src/main/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/
    aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/
    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/Activator.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/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/
    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
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java
    aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java
Modified:
    aries/trunk/tx-control/pom.xml

Modified: aries/trunk/tx-control/pom.xml
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/pom.xml?rev=1730701&r1=1730700&r2=1730701&view=diff
==============================================================================
--- aries/trunk/tx-control/pom.xml (original)
+++ aries/trunk/tx-control/pom.xml Tue Feb 16 15:35:02 2016
@@ -35,5 +35,6 @@
     </scm>
     <modules>
         <module>tx-control-api</module>
+        <module>tx-control-service-local</module>
     </modules>
 </project>
\ No newline at end of file

Added: aries/trunk/tx-control/tx-control-service-local/pom.xml
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/pom.xml?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/pom.xml (added)
+++ aries/trunk/tx-control/tx-control-service-local/pom.xml Tue Feb 16 15:35:02 2016
@@ -0,0 +1,113 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>org.apache.aries</groupId>
+		<artifactId>parent</artifactId>
+		<version>2.0.1</version>
+		<relativePath>../../parent/pom.xml</relativePath>
+	</parent>
+	<groupId>org.apache.aries.tx-control</groupId>
+	<artifactId>tx-control-service-local</artifactId>
+	<packaging>bundle</packaging>
+	<name>OSGi Transaction Control Service - Local Transactions</name>
+	<version>0.0.1-SNAPSHOT</version>
+
+	<description>
+        This bundle contains a Coordinator based OSGi Transaction Control Service implementation suitable for local resources.
+    </description>
+
+	<scm>
+		<connection>
+            scm:svn:http://svn.apache.org/repos/asf/aries/trunk/tx-control/tx-control-api
+        </connection>
+		<developerConnection>
+            scm:svn:https://svn.apache.org/repos/asf/aries/trunk/tx-control/tx-control-api
+        </developerConnection>
+		<url>
+            http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-api
+        </url>
+	</scm>
+
+	<properties>
+		<aries.osgi.activator>
+		   org.apache.aries.tx.control.service.local.impl.Activator
+		</aries.osgi.activator>
+		<aries.osgi.export.pkg>
+			org.osgi.service.transaction.control
+		</aries.osgi.export.pkg>
+        <aries.osgi.private.pkg>
+            org.apache.aries.tx.control.service.local.*
+        </aries.osgi.private.pkg>
+		<aries.osgi.import.pkg>
+			org.osgi.service.transaction.control;version="[0.0.1,0.0.1]",
+			*
+		</aries.osgi.import.pkg>
+	</properties>
+
+	<dependencies>
+	    <dependency>
+	        <groupId>org.slf4j</groupId>
+	        <artifactId>slf4j-api</artifactId>
+	    </dependency>
+		<dependency>
+			<groupId>org.apache.aries.tx-control</groupId>
+			<artifactId>tx-control-api</artifactId>
+			<version>0.0.1-SNAPSHOT</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.service.coordinator</artifactId>
+			<version>1.0.2</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.util.tracker</artifactId>
+			<version>1.5.1</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.osgi</groupId>
+			<artifactId>org.osgi.core</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.8</source>
+					<target>1.8</target>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.aries.versioning</groupId>
+				<artifactId>org.apache.aries.versioning.plugin</artifactId>
+				<executions>
+					<execution>
+						<id>default-verify</id>
+						<phase>verify</phase>
+						<goals>
+							<goal>version-check</goal>
+						</goals>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+</project>
\ No newline at end of file

Added: 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=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,36 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.transaction.control.TransactionContext;
+
+public abstract class AbstractTransactionContextImpl
+		implements TransactionContext {
+
+	protected static class TransactionVariablesKey {}
+
+	AtomicReference<Exception> unexpectedException = new AtomicReference<>();
+	
+	protected final Coordination coordination;
+	
+	public AbstractTransactionContextImpl(Coordination coordination) {
+		this.coordination = coordination;
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public Object getScopedValue(Object key) {
+		return ((HashMap<Object,Object>) coordination.getVariables()
+				.getOrDefault(AbstractTransactionContextImpl.TransactionVariablesKey.class, new HashMap<>()))
+						.get(key);
+	}
+
+	@SuppressWarnings("unchecked")
+	@Override
+	public void putScopedValue(Object key, Object value) {
+		((HashMap<Object,Object>) coordination.getVariables().computeIfAbsent(
+				AbstractTransactionContextImpl.TransactionVariablesKey.class, k -> new HashMap<>())).put(key, value);
+	}
+}

Added: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.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/Activator.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,112 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Optional;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.transaction.control.TransactionControl;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Activator implements BundleActivator, ServiceTrackerCustomizer<Coordinator, Coordinator> {
+
+	private static final Logger logger = LoggerFactory.getLogger(Activator.class);
+	
+	private BundleContext context;
+	
+	private ServiceTracker<Coordinator, Coordinator> tracker;
+	
+	private Coordinator inUse;
+	private ServiceRegistration<TransactionControl> reg;
+	
+	private Object lock = new Object();
+	
+	@Override
+	public void start(BundleContext context) throws Exception {
+		this.context = context;
+		tracker = new ServiceTracker<>(context, Coordinator.class, this);
+		tracker.open();
+	}
+
+	@Override
+	public void stop(BundleContext context) throws Exception {
+		tracker.close();
+	}
+
+	@Override
+	public Coordinator addingService(ServiceReference<Coordinator> reference) {
+		Coordinator c = context.getService(reference);
+		checkAndRegister(c);
+		return c;
+	}
+
+	private void checkAndRegister(Coordinator c) {
+		boolean register = false;
+		synchronized (lock) {
+			if(inUse == null) {
+				inUse = c;
+				register = true;
+			}
+		}
+		
+		if(register) {
+			logger.info("Registering a new local-only TransactionControl service");
+			ServiceRegistration<TransactionControl> reg = context.registerService(
+					TransactionControl.class, new TransactionControlImpl(c), getProperties());
+			synchronized (lock) {
+				this.reg = reg;
+			}
+		}
+	}
+
+	private Dictionary<String, Object> getProperties() {
+		Dictionary<String, Object> props = new Hashtable<>();
+		props.put("osgi.local.enabled", Boolean.TRUE);
+		return props;
+	}
+
+	@Override
+	public void modifiedService(ServiceReference<Coordinator> reference, Coordinator service) {
+	}
+
+	@Override
+	public void removedService(ServiceReference<Coordinator> reference, Coordinator service) {
+		ServiceRegistration<TransactionControl> toUnregister = null;
+		synchronized (lock) {
+			if(inUse == service) {
+				inUse = null;
+				toUnregister = reg;
+				reg = null;
+			}
+		}
+		
+		if(toUnregister != null) {
+			try {
+				toUnregister.unregister();
+			} catch (IllegalStateException ise) {
+				logger.debug("An exception occurred when unregistering the Transaction Control service", ise);
+			}
+			
+			Optional<?> check = tracker.getTracked().values().stream()
+				.filter(c -> {
+					checkAndRegister(c);
+					synchronized (lock) {
+						return reg != null;
+					}
+				}).findFirst();
+			
+			if(!check.isPresent()) {
+				logger.info("No replacement Coordinator service was available. The Transaction Control service will remain unavailable until a new Coordinator can be found");
+			}
+		}
+		context.ungetService(reference);
+	}
+
+}

Added: 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=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,128 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+
+import java.util.ArrayList;
+import java.util.List;
+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 NoTransactionContextImpl extends AbstractTransactionContextImpl
+		implements TransactionContext {
+
+	final List<Runnable>					preCompletion		= new ArrayList<>();
+	final List<Consumer<TransactionStatus>>	postCompletion		= new ArrayList<>();
+
+	volatile boolean						finished			= false;
+
+	public NoTransactionContextImpl(Coordination coordination) {
+		super(coordination);
+
+		coordination.addParticipant(new Participant() {
+
+			@Override
+			public void failed(Coordination coordination) throws Exception {
+
+				beforeCompletion();
+
+				afterCompletion();
+			}
+
+			private void beforeCompletion() {
+				preCompletion.stream().forEach(r -> {
+					try {
+						r.run();
+					} catch (Exception e) {
+						unexpectedException.compareAndSet(null, e);
+						// TODO log this
+					}
+				});
+			}
+
+			private void afterCompletion() {
+				postCompletion.stream().forEach(c -> {
+					try {
+						c.accept(NO_TRANSACTION);
+					} catch (Exception e) {
+						unexpectedException.compareAndSet(null, e);
+						// TODO log this
+					}
+				});
+			}
+
+			@Override
+			public void ended(Coordination coordination) throws Exception {
+				beforeCompletion();
+
+				afterCompletion();
+			}
+		});
+	}
+
+	@Override
+	public Object getTransactionKey() {
+		return null;
+	}
+
+	@Override
+	public boolean getRollbackOnly() throws IllegalStateException {
+		throw new IllegalStateException("No transaction is active");
+	}
+
+	@Override
+	public void setRollbackOnly() throws IllegalStateException {
+		throw new IllegalStateException("No transaction is active");
+	}
+
+	@Override
+	public TransactionStatus getTransactionStatus() {
+		return NO_TRANSACTION;
+	}
+
+	@Override
+	public void preCompletion(Runnable job) throws IllegalStateException {
+		if (coordination.isTerminated()) {
+			throw new IllegalStateException(
+					"The transaction context has finished");
+		}
+		preCompletion.add(job);
+	}
+
+	@Override
+	public void postCompletion(Consumer<TransactionStatus> job)
+			throws IllegalStateException {
+		if (coordination.isTerminated()) {
+			throw new IllegalStateException(
+					"The transaction context has finished");
+		}
+
+		postCompletion.add(job);
+	}
+
+	@Override
+	public void registerXAResource(XAResource resource) {
+		throw new IllegalStateException("No transaction is active");
+	}
+
+	@Override
+	public void registerLocalResource(LocalResource resource) {
+		throw new IllegalStateException("No transaction is active");
+	}
+
+	@Override
+	public boolean supportsXA() {
+		return false;
+	}
+
+	@Override
+	public boolean supportsLocal() {
+		return false;
+	}
+}

Added: 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=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,217 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+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.ROLLED_BACK;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLING_BACK;
+
+import java.util.ArrayList;
+import java.util.List;
+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 {
+
+	final List<LocalResource>				resources			= new ArrayList<>();
+
+	final List<Runnable>					preCompletion		= new ArrayList<>();
+	final List<Consumer<TransactionStatus>>	postCompletion		= new ArrayList<>();
+
+	private volatile TransactionStatus		tranStatus;
+
+	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() {
+				preCompletion.stream().forEach(r -> {
+					try {
+						r.run();
+					} catch (Exception e) {
+						unexpectedException.compareAndSet(null, e);
+						setRollbackOnly();
+						// TODO log this
+					}
+				});
+			}
+
+			private void afterCompletion() {
+				postCompletion.stream().forEach(c -> {
+					try {
+						c.accept(tranStatus);
+					} catch (Exception e) {
+						unexpectedException.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) {
+							unexpectedException.compareAndSet(null, e);
+							if (committed.isEmpty()) {
+								tranStatus = ROLLING_BACK;
+							}
+							rolledback.add(lr);
+						}
+					});
+					tranStatus = tranStatus == ROLLING_BACK ? ROLLED_BACK
+							: COMMITTED;
+				}
+				afterCompletion();
+			}
+		});
+	}
+
+	@Override
+	public Object getTransactionKey() {
+		return coordination.getId();
+	}
+
+	@Override
+	public boolean getRollbackOnly() throws IllegalStateException {
+		switch (tranStatus) {
+			case MARKED_ROLLBACK :
+			case ROLLING_BACK :
+			case ROLLED_BACK :
+				return true;
+			default :
+				return false;
+		}
+	}
+
+	@Override
+	public void setRollbackOnly() throws IllegalStateException {
+		switch (tranStatus) {
+			case ACTIVE :
+			case MARKED_ROLLBACK :
+				tranStatus = MARKED_ROLLBACK;
+				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
+	public TransactionStatus getTransactionStatus() {
+		return tranStatus;
+	}
+
+	@Override
+	public void preCompletion(Runnable job) throws IllegalStateException {
+		if (tranStatus.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);
+		}
+
+		postCompletion.add(job);
+	}
+
+	@Override
+	public void registerXAResource(XAResource resource) {
+		throw new IllegalStateException("Not an XA manager");
+	}
+
+	@Override
+	public void registerLocalResource(LocalResource resource) {
+		if (tranStatus.compareTo(MARKED_ROLLBACK) > 0) {
+			throw new IllegalStateException(
+					"The current transaction is in state " + tranStatus);
+		}
+		resources.add(resource);
+	}
+
+	@Override
+	public boolean supportsXA() {
+		return false;
+	}
+
+	@Override
+	public boolean supportsLocal() {
+		return true;
+	}
+}

Added: 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=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,258 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static java.util.Optional.ofNullable;
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+
+import java.util.concurrent.Callable;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.transaction.control.TransactionBuilder;
+import org.osgi.service.transaction.control.TransactionContext;
+import org.osgi.service.transaction.control.TransactionControl;
+import org.osgi.service.transaction.control.TransactionException;
+import org.osgi.service.transaction.control.TransactionRolledBackException;
+
+public class TransactionControlImpl implements TransactionControl {
+
+	private final class TransactionBuilderImpl extends TransactionBuilder {
+
+
+		@Override
+		public <T> T required(Callable<T> work)
+				throws TransactionException, TransactionRolledBackException {
+
+			Coordination currentCoord = coordinator.peek();
+			boolean endCoordination = false;
+
+			AbstractTransactionContextImpl currentTran = ofNullable(
+					currentCoord).map(c -> (AbstractTransactionContextImpl) c
+							.getVariables().get(TransactionContextKey.class))
+							.filter(atc -> atc
+									.getTransactionStatus() != NO_TRANSACTION)
+							.orElse(null);
+			try {
+				if (currentTran == null) {
+					// We must create a new coordination to scope our new
+					// transaction
+					currentCoord = coordinator.begin(
+							"Resource-Local-Transaction.REQUIRED", 30000);
+					endCoordination = true;
+					currentTran = new TransactionContextImpl(currentCoord);
+					currentCoord.getVariables().put(TransactionContextKey.class,
+							currentTran);
+				}
+			} catch (RuntimeException re) {
+				if (endCoordination) {
+					currentCoord.end();
+				}
+				throw re;
+			}
+
+			return doWork(work, currentCoord, endCoordination);
+		}
+
+		@Override
+		public <T> T requiresNew(Callable<T> work)
+				throws TransactionException, TransactionRolledBackException {
+			Coordination currentCoord = null;
+			try {
+				currentCoord = coordinator.begin(
+						"Resource-Local-Transaction.REQUIRES_NEW", 30000);
+
+				AbstractTransactionContextImpl currentTran = new TransactionContextImpl(
+						currentCoord);
+				currentCoord.getVariables().put(TransactionContextKey.class,
+						currentTran);
+			} catch (RuntimeException re) {
+				if (currentCoord != null)
+					currentCoord.end();
+				throw re;
+			}
+
+			return doWork(work, currentCoord, true);
+		}
+
+		@Override
+		public <T> T supports(Callable<T> work) throws TransactionException {
+			Coordination currentCoord = coordinator.peek();
+			boolean endCoordination = false;
+
+			AbstractTransactionContextImpl currentTran = ofNullable(
+					currentCoord).map(c -> (AbstractTransactionContextImpl) c
+							.getVariables().get(TransactionContextKey.class))
+							.orElse(null);
+			try {
+				if (currentTran == null) {
+					// We must create a new coordination to scope our new
+					// transaction
+					currentCoord = coordinator.begin(
+							"Resource-Local-Transaction.SUPPORTS", 30000);
+					endCoordination = true;
+					currentTran = new NoTransactionContextImpl(currentCoord);
+					currentCoord.getVariables().put(TransactionContextKey.class,
+							currentTran);
+				}
+			} catch (RuntimeException re) {
+				if (endCoordination) {
+					currentCoord.end();
+				}
+				throw re;
+			}
+
+			return doWork(work, currentCoord, endCoordination);
+		}
+
+		@Override
+		public <T> T notSupported(Callable<T> work)
+				throws TransactionException {
+			Coordination currentCoord = coordinator.peek();
+			boolean endCoordination = false;
+
+			AbstractTransactionContextImpl currentTran = ofNullable(
+					currentCoord).map(c -> (AbstractTransactionContextImpl) c
+							.getVariables().get(TransactionContextKey.class))
+							.filter(atc -> atc
+									.getTransactionStatus() == NO_TRANSACTION)
+							.orElse(null);
+			try {
+				if (currentTran == null) {
+					// We must create a new coordination to scope our new
+					// transaction
+					currentCoord = coordinator.begin(
+							"Resource-Local-Transaction.NOT_SUPPORTED", 30000);
+					endCoordination = true;
+					currentTran = new NoTransactionContextImpl(currentCoord);
+					currentCoord.getVariables().put(TransactionContextKey.class,
+							currentTran);
+				}
+			} catch (RuntimeException re) {
+				if (endCoordination) {
+					currentCoord.end();
+				}
+				throw re;
+			}
+			return doWork(work, currentCoord, endCoordination);
+		}
+
+		private <R> R doWork(Callable<R> transactionalWork,
+				Coordination currentCoord, boolean endCoordination) {
+			try {
+				R result = transactionalWork.call();
+
+				if (endCoordination) {
+					currentCoord.end();
+				}
+				return result;
+			} catch (Throwable t) {
+				if (endCoordination) {
+					//TODO handle noRollbackFor
+					currentCoord.fail(t);
+				}
+				TransactionControlImpl.<RuntimeException> throwException(t);
+			}
+			throw new TransactionException(
+					"The code here should never be reached");
+		}
+	}
+
+	private static class TransactionContextKey {}
+
+	private final Coordinator coordinator;
+
+	public TransactionControlImpl(Coordinator c) {
+		coordinator = c;
+	}
+
+	@Override
+	public TransactionBuilder build() {
+		return new TransactionBuilderImpl();
+	}
+
+	@Override
+	public boolean getRollbackOnly() throws IllegalStateException {
+		return getCurrentTranContextChecked().getRollbackOnly();
+	}
+
+	@Override
+	public void setRollbackOnly() throws IllegalStateException {
+		getCurrentTranContextChecked().setRollbackOnly();
+	}
+
+	@Override
+	public <T> T required(Callable<T> work)
+			throws TransactionException, TransactionRolledBackException {
+		return build().required(work);
+	}
+
+	@Override
+	public <T> T requiresNew(Callable<T> work)
+			throws TransactionException, TransactionRolledBackException {
+		return build().requiresNew(work);
+	}
+
+	@Override
+	public <T> T notSupported(Callable<T> work) throws TransactionException {
+		return build().notSupported(work);
+	}
+
+	@Override
+	public <T> T supports(Callable<T> work) throws TransactionException {
+		return build().supports(work);
+	}
+
+	@Override
+	public boolean activeTransaction() {
+		TransactionContext context = getCurrentContext();
+		return context != null
+				&& context.getTransactionStatus() != NO_TRANSACTION;
+	}
+
+	@Override
+	public boolean activeScope() {
+		return getCurrentContext() != null;
+	}
+
+	private TransactionContext getCurrentTranContextChecked() {
+		TransactionContext toUse = getCurrentContext();
+		if (toUse == null) {
+			throw new IllegalStateException(
+					"There is no applicable transaction context");
+		}
+		return toUse;
+	}
+
+	@Override
+	public TransactionContext getCurrentContext() {
+		TransactionContext toUse = null;
+
+		Coordination peek = coordinator.peek();
+		if (peek != null) {
+			toUse = (TransactionContext) peek.getVariables()
+					.get(TransactionContextKey.class);
+		}
+		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
+
+	}
+
+}

Added: 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=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,240 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.transaction.xa.XAResource;
+
+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.Participant;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.TransactionContext;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NoTransactionContextTest {
+
+	@Mock
+	Coordination coordination;
+	@Mock
+	XAResource xaResource;
+	@Mock
+	LocalResource localResource;
+	
+	Map<Class<?>, Object> variables;
+	
+	TransactionContext ctx;
+	
+	@Before
+	public void setUp() {
+		ctx = new NoTransactionContextImpl(coordination);
+		variables = new HashMap<>();
+		Mockito.when(coordination.getVariables()).thenReturn(variables);
+	}
+	
+	@Test(expected=IllegalStateException.class)
+	public void testGetRollbackOnly() {
+		ctx.getRollbackOnly();
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testSetRollbackOnly() {
+		ctx.setRollbackOnly();
+	}
+
+	@Test
+	public void testTransactionKey() {
+		assertNull(ctx.getTransactionKey());
+	}
+	
+	@Test
+	public void testTransactionStatus() {
+		assertEquals(NO_TRANSACTION, ctx.getTransactionStatus());
+	}
+
+	@Test
+	public void testLocalResourceSupport() {
+		assertFalse(ctx.supportsLocal());
+	}
+
+	@Test
+	public void testXAResourceSupport() {
+		assertFalse(ctx.supportsXA());
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testLocalResourceRegistration() {
+		ctx.registerLocalResource(localResource);
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testXAResourceRegistration() {
+		ctx.registerXAResource(xaResource);
+	}
+
+	@Test
+	public void testScopedValues() {
+		assertNull(ctx.getScopedValue("foo"));
+		
+		Object value = new Object();
+		
+		ctx.putScopedValue("foo", value);
+		
+		assertSame(value, ctx.getScopedValue("foo"));
+	}
+	
+	@Test
+	public void testPreCompletionEnd() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> value.compareAndSet(1, 5));
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		Participant participant = getParticipant();
+		participant.ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPreCompletionFail() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> value.compareAndSet(1, 5));
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		Participant participant = getParticipant();
+		participant.failed(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionEnd() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.postCompletion(status -> {
+				assertEquals(NO_TRANSACTION, status);
+				value.compareAndSet(1, 5);
+			});
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		Participant participant = getParticipant();
+		participant.ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionFail() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.postCompletion(status -> {
+			assertEquals(NO_TRANSACTION, status);
+			value.compareAndSet(1, 5);
+		});
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		Participant participant = getParticipant();
+		participant.failed(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionIsAfterPreCompletionEnd() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> value.compareAndSet(0, 3));
+		ctx.postCompletion(status -> {
+			assertEquals(NO_TRANSACTION, status);
+			value.compareAndSet(3, 5);
+		});
+		
+		Participant participant = getParticipant();
+		participant.ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionIsAfterPreCompletionFail() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> value.compareAndSet(0, 3));
+		ctx.postCompletion(status -> {
+			assertEquals(NO_TRANSACTION, status);
+			value.compareAndSet(3, 5);
+		});
+		
+		Participant participant = getParticipant();
+		participant.failed(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionIsStillCalledAfterPreCompletionException() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> {
+				value.compareAndSet(0, 3);
+				throw new RuntimeException("Boom!");
+			});
+		ctx.postCompletion(status -> {
+			assertEquals(NO_TRANSACTION, status);
+			value.compareAndSet(3, 5);
+		});
+		
+		Participant participant = getParticipant();
+		participant.ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	private Participant getParticipant() {
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		Participant participant = captor.getValue();
+		return participant;
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testPreCompletionAfterEnd() throws Exception {
+		
+		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.preCompletion(() -> {});
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testPostCompletionAfterEnd() throws Exception {
+		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.postCompletion(x -> {});
+	}
+	
+}

Added: 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=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,379 @@
+package org.apache.aries.tx.control.service.local.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.assertSame;
+import static org.junit.Assert.assertTrue;
+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.ROLLED_BACK;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLING_BACK;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.transaction.xa.XAResource;
+
+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.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)
+public class TransactionContextTest {
+
+	@Mock
+	Coordination coordination;
+	@Mock
+	XAResource xaResource;
+	@Mock
+	LocalResource localResource;
+	
+	Map<Class<?>, Object> variables;
+	
+	TransactionContext ctx;
+	
+	@Before
+	public void setUp() {
+		ctx = new TransactionContextImpl(coordination);
+		variables = new HashMap<>();
+		Mockito.when(coordination.getVariables()).thenReturn(variables);
+	}
+	
+	@Test
+	public void testGetRollbackOnly() {
+		assertFalse(ctx.getRollbackOnly());
+	}
+
+	@Test
+	public void testSetRollbackOnly() {
+		ctx.setRollbackOnly();
+		assertTrue(ctx.getRollbackOnly());
+	}
+
+	@Test
+	public void testTransactionKey() {
+		Mockito.when(coordination.getId()).thenReturn(42L);
+		
+		assertNotNull(ctx.getTransactionKey());
+	}
+	
+	@Test
+	public void testTransactionStatus() {
+		assertEquals(ACTIVE, ctx.getTransactionStatus());
+		
+		ctx.setRollbackOnly();
+		
+		assertEquals(MARKED_ROLLBACK, ctx.getTransactionStatus());
+	}
+
+	@Test
+	public void testLocalResourceSupport() {
+		assertTrue(ctx.supportsLocal());
+	}
+
+	@Test
+	public void testXAResourceSupport() {
+		assertFalse(ctx.supportsXA());
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testXAResourceRegistration() {
+		ctx.registerXAResource(xaResource);
+	}
+
+	@Test
+	public void testScopedValues() {
+		assertNull(ctx.getScopedValue("foo"));
+		
+		Object value = new Object();
+		
+		ctx.putScopedValue("foo", value);
+		
+		assertSame(value, ctx.getScopedValue("foo"));
+	}
+	
+	@Test
+	public void testPreCompletionEnd() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> {
+			assertEquals(ACTIVE, ctx.getTransactionStatus());
+			value.compareAndSet(1, 5);
+		});
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		captor.getValue().ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPreCompletionFail() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> {
+			assertEquals(MARKED_ROLLBACK, ctx.getTransactionStatus());
+			value.compareAndSet(1, 5);
+		});
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		captor.getValue().failed(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionEnd() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.postCompletion(status -> {
+				assertEquals(COMMITTED, status);
+				value.compareAndSet(1, 5);
+			});
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		captor.getValue().ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionFail() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.postCompletion(status -> {
+			assertEquals(ROLLED_BACK, status);
+			value.compareAndSet(1, 5);
+		});
+		
+		assertEquals(0, value.getAndSet(1));
+		
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		captor.getValue().failed(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionIsAfterPreCompletion() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> {
+			assertEquals(ACTIVE, ctx.getTransactionStatus());
+			value.compareAndSet(0, 3);
+		});
+		ctx.postCompletion(status -> {
+			assertEquals(COMMITTED, status);
+			value.compareAndSet(3, 5);
+		});
+		
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		captor.getValue().ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	@Test
+	public void testPostCompletionIsStillCalledAfterPreCompletionException() throws Exception {
+		
+		AtomicInteger value = new AtomicInteger(0);
+		
+		ctx.preCompletion(() -> {
+				value.compareAndSet(0, 3);
+				throw new RuntimeException("Boom!");
+			});
+		ctx.postCompletion(status -> {
+			assertEquals(ROLLED_BACK, status);
+			value.compareAndSet(3, 5);
+		});
+		
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		captor.getValue().ended(coordination);
+		
+		assertEquals(5, value.get());
+	}
+
+	private Participant getParticipant() {
+		ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+		Mockito.verify(coordination).addParticipant(captor.capture());
+		
+		Participant participant = captor.getValue();
+		return participant;
+	}
+	
+	@Test(expected=IllegalStateException.class)
+	public void testPreCompletionAfterEnd() throws Exception {
+		
+		getParticipant().ended(coordination);
+		
+		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.preCompletion(() -> {});
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testPreCompletionAfterFail() throws Exception {
+		
+		getParticipant().failed(coordination);
+		
+		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.preCompletion(() -> {});
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testPostCompletionAfterEnd() throws Exception {
+		
+		getParticipant().ended(coordination);
+		
+		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.postCompletion(x -> {});
+	}
+
+	@Test(expected=IllegalStateException.class)
+	public void testPostCompletionAfterFail() throws Exception {
+		
+		getParticipant().failed(coordination);
+		
+		Mockito.when(coordination.isTerminated()).thenReturn(true);
+		ctx.postCompletion(x -> {});
+	}
+
+	@Test
+	public void testLocalResourceEnd() throws Exception {
+		ctx.registerLocalResource(localResource);
+		
+		Mockito.doAnswer(i -> {
+			assertEquals(COMMITTING, ctx.getTransactionStatus());
+			return null;
+		}).when(localResource).commit();
+		
+		getParticipant().ended(coordination);
+		
+		Mockito.verify(localResource).commit();
+	}
+
+	@Test
+	public void testLocalResourceEndRollbackOnly() throws Exception {
+		ctx.registerLocalResource(localResource);
+		ctx.setRollbackOnly();
+		
+		Mockito.doAnswer(i -> {
+			assertEquals(ROLLING_BACK, ctx.getTransactionStatus());
+			return null;
+		}).when(localResource).rollback();
+		
+		getParticipant().ended(coordination);
+		
+		Mockito.verify(localResource).rollback();
+	}
+
+	@Test
+	public void testLocalResourceFail() throws Exception {
+		ctx.registerLocalResource(localResource);
+		
+		Mockito.doAnswer(i -> {
+			assertEquals(ROLLING_BACK, ctx.getTransactionStatus());
+			return null;
+		}).when(localResource).rollback();
+		
+		getParticipant().failed(coordination);
+		
+		Mockito.verify(localResource).rollback();
+	}
+	
+	@Test
+	public void testLocalResourceEndPreCommitException() throws Exception {
+		ctx.registerLocalResource(localResource);
+		
+		Mockito.doAnswer(i -> {
+			assertEquals(ROLLING_BACK, ctx.getTransactionStatus());
+			return null;
+		}).when(localResource).rollback();
+		
+		ctx.preCompletion(() -> { throw new IllegalArgumentException(); });
+		
+		getParticipant().ended(coordination);
+		
+		Mockito.verify(localResource).rollback();
+	}
+
+	@Test
+	public void testLocalResourceEndPostCommitException() throws Exception {
+		ctx.registerLocalResource(localResource);
+		
+		Mockito.doAnswer(i -> {
+			assertEquals(COMMITTING, ctx.getTransactionStatus());
+			return null;
+		}).when(localResource).commit();
+		
+		ctx.postCompletion(i -> { 
+				assertEquals(COMMITTED, ctx.getTransactionStatus());
+				throw new IllegalArgumentException(); 
+			});
+		
+		getParticipant().ended(coordination);
+		
+		Mockito.verify(localResource).commit();
+	}
+
+	@Test
+	public void testLocalResourcesFirstFailsSoRollback() throws Exception {
+		
+		ctx.registerLocalResource(localResource);
+
+		LocalResource localResource2 = Mockito.mock(LocalResource.class);
+		ctx.registerLocalResource(localResource2);
+		
+		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(localResource2).rollback();
+		
+		getParticipant().ended(coordination);
+		
+		Mockito.verify(localResource).commit();
+		Mockito.verify(localResource2).rollback();
+	}
+	
+}

Added: aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.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/TransactionControlStatusTest.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,321 @@
+package org.apache.aries.tx.control.service.local.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 org.apache.aries.tx.control.service.local.impl.TransactionControlImpl;
+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() {
+
+		resource = new Object();
+		variables1 = new HashMap<>();
+		variables2 = new HashMap<>();
+
+		Mockito.when(coordination1.getVariables()).thenReturn(variables1);
+		Mockito.when(coordination2.getVariables()).thenReturn(variables2);
+		
+		txControl = new TransactionControlImpl(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-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.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/TransactionLifecycleTest.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,303 @@
+package org.apache.aries.tx.control.service.local.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 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() {
+		variables1 = new HashMap<>();
+		variables2 = new HashMap<>();
+
+		setupCoordinations();
+
+		txControl = new TransactionControlImpl(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;
+		});
+
+	}
+
+}