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


Reply via email to