You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by ti...@apache.org on 2016/02/16 16:35:02 UTC
svn commit: r1730701 - in /aries/trunk/tx-control: ./
tx-control-service-local/ tx-control-service-local/src/
tx-control-service-local/src/main/ tx-control-service-local/src/main/java/
tx-control-service-local/src/main/java/org/ tx-control-service-loca...
Author: timothyjward
Date: Tue Feb 16 15:35:02 2016
New Revision: 1730701
URL: http://svn.apache.org/viewvc?rev=1730701&view=rev
Log:
[tx-control] Add a basic TransactionControl service implementation
* Local only
* Coordinator based
* Currently no support for rollbackFor/noRollbackFor
Added:
aries/trunk/tx-control/tx-control-service-local/
aries/trunk/tx-control/tx-control-service-local/pom.xml
aries/trunk/tx-control/tx-control-service-local/src/
aries/trunk/tx-control/tx-control-service-local/src/main/
aries/trunk/tx-control/tx-control-service-local/src/main/java/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.java
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
aries/trunk/tx-control/tx-control-service-local/src/test/
aries/trunk/tx-control/tx-control-service-local/src/test/java/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java
Modified:
aries/trunk/tx-control/pom.xml
Modified: aries/trunk/tx-control/pom.xml
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/pom.xml?rev=1730701&r1=1730700&r2=1730701&view=diff
==============================================================================
--- aries/trunk/tx-control/pom.xml (original)
+++ aries/trunk/tx-control/pom.xml Tue Feb 16 15:35:02 2016
@@ -35,5 +35,6 @@
</scm>
<modules>
<module>tx-control-api</module>
+ <module>tx-control-service-local</module>
</modules>
</project>
\ No newline at end of file
Added: aries/trunk/tx-control/tx-control-service-local/pom.xml
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/pom.xml?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/pom.xml (added)
+++ aries/trunk/tx-control/tx-control-service-local/pom.xml Tue Feb 16 15:35:02 2016
@@ -0,0 +1,113 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.aries</groupId>
+ <artifactId>parent</artifactId>
+ <version>2.0.1</version>
+ <relativePath>../../parent/pom.xml</relativePath>
+ </parent>
+ <groupId>org.apache.aries.tx-control</groupId>
+ <artifactId>tx-control-service-local</artifactId>
+ <packaging>bundle</packaging>
+ <name>OSGi Transaction Control Service - Local Transactions</name>
+ <version>0.0.1-SNAPSHOT</version>
+
+ <description>
+ This bundle contains a Coordinator based OSGi Transaction Control Service implementation suitable for local resources.
+ </description>
+
+ <scm>
+ <connection>
+ scm:svn:http://svn.apache.org/repos/asf/aries/trunk/tx-control/tx-control-api
+ </connection>
+ <developerConnection>
+ scm:svn:https://svn.apache.org/repos/asf/aries/trunk/tx-control/tx-control-api
+ </developerConnection>
+ <url>
+ http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-api
+ </url>
+ </scm>
+
+ <properties>
+ <aries.osgi.activator>
+ org.apache.aries.tx.control.service.local.impl.Activator
+ </aries.osgi.activator>
+ <aries.osgi.export.pkg>
+ org.osgi.service.transaction.control
+ </aries.osgi.export.pkg>
+ <aries.osgi.private.pkg>
+ org.apache.aries.tx.control.service.local.*
+ </aries.osgi.private.pkg>
+ <aries.osgi.import.pkg>
+ org.osgi.service.transaction.control;version="[0.0.1,0.0.1]",
+ *
+ </aries.osgi.import.pkg>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.aries.tx-control</groupId>
+ <artifactId>tx-control-api</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.service.coordinator</artifactId>
+ <version>1.0.2</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.util.tracker</artifactId>
+ <version>1.5.1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.9.5</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.aries.versioning</groupId>
+ <artifactId>org.apache.aries.versioning.plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-verify</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>version-check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
Added: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/AbstractTransactionContextImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,36 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.transaction.control.TransactionContext;
+
+public abstract class AbstractTransactionContextImpl
+ implements TransactionContext {
+
+ protected static class TransactionVariablesKey {}
+
+ AtomicReference<Exception> unexpectedException = new AtomicReference<>();
+
+ protected final Coordination coordination;
+
+ public AbstractTransactionContextImpl(Coordination coordination) {
+ this.coordination = coordination;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object getScopedValue(Object key) {
+ return ((HashMap<Object,Object>) coordination.getVariables()
+ .getOrDefault(AbstractTransactionContextImpl.TransactionVariablesKey.class, new HashMap<>()))
+ .get(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void putScopedValue(Object key, Object value) {
+ ((HashMap<Object,Object>) coordination.getVariables().computeIfAbsent(
+ AbstractTransactionContextImpl.TransactionVariablesKey.class, k -> new HashMap<>())).put(key, value);
+ }
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/Activator.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,112 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Optional;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.transaction.control.TransactionControl;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Activator implements BundleActivator, ServiceTrackerCustomizer<Coordinator, Coordinator> {
+
+ private static final Logger logger = LoggerFactory.getLogger(Activator.class);
+
+ private BundleContext context;
+
+ private ServiceTracker<Coordinator, Coordinator> tracker;
+
+ private Coordinator inUse;
+ private ServiceRegistration<TransactionControl> reg;
+
+ private Object lock = new Object();
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ this.context = context;
+ tracker = new ServiceTracker<>(context, Coordinator.class, this);
+ tracker.open();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ tracker.close();
+ }
+
+ @Override
+ public Coordinator addingService(ServiceReference<Coordinator> reference) {
+ Coordinator c = context.getService(reference);
+ checkAndRegister(c);
+ return c;
+ }
+
+ private void checkAndRegister(Coordinator c) {
+ boolean register = false;
+ synchronized (lock) {
+ if(inUse == null) {
+ inUse = c;
+ register = true;
+ }
+ }
+
+ if(register) {
+ logger.info("Registering a new local-only TransactionControl service");
+ ServiceRegistration<TransactionControl> reg = context.registerService(
+ TransactionControl.class, new TransactionControlImpl(c), getProperties());
+ synchronized (lock) {
+ this.reg = reg;
+ }
+ }
+ }
+
+ private Dictionary<String, Object> getProperties() {
+ Dictionary<String, Object> props = new Hashtable<>();
+ props.put("osgi.local.enabled", Boolean.TRUE);
+ return props;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference<Coordinator> reference, Coordinator service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference<Coordinator> reference, Coordinator service) {
+ ServiceRegistration<TransactionControl> toUnregister = null;
+ synchronized (lock) {
+ if(inUse == service) {
+ inUse = null;
+ toUnregister = reg;
+ reg = null;
+ }
+ }
+
+ if(toUnregister != null) {
+ try {
+ toUnregister.unregister();
+ } catch (IllegalStateException ise) {
+ logger.debug("An exception occurred when unregistering the Transaction Control service", ise);
+ }
+
+ Optional<?> check = tracker.getTracked().values().stream()
+ .filter(c -> {
+ checkAndRegister(c);
+ synchronized (lock) {
+ return reg != null;
+ }
+ }).findFirst();
+
+ if(!check.isPresent()) {
+ logger.info("No replacement Coordinator service was available. The Transaction Control service will remain unavailable until a new Coordinator can be found");
+ }
+ }
+ context.ungetService(reference);
+ }
+
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,128 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.transaction.xa.XAResource;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Participant;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.TransactionContext;
+import org.osgi.service.transaction.control.TransactionStatus;
+
+public class NoTransactionContextImpl extends AbstractTransactionContextImpl
+ implements TransactionContext {
+
+ final List<Runnable> preCompletion = new ArrayList<>();
+ final List<Consumer<TransactionStatus>> postCompletion = new ArrayList<>();
+
+ volatile boolean finished = false;
+
+ public NoTransactionContextImpl(Coordination coordination) {
+ super(coordination);
+
+ coordination.addParticipant(new Participant() {
+
+ @Override
+ public void failed(Coordination coordination) throws Exception {
+
+ beforeCompletion();
+
+ afterCompletion();
+ }
+
+ private void beforeCompletion() {
+ preCompletion.stream().forEach(r -> {
+ try {
+ r.run();
+ } catch (Exception e) {
+ unexpectedException.compareAndSet(null, e);
+ // TODO log this
+ }
+ });
+ }
+
+ private void afterCompletion() {
+ postCompletion.stream().forEach(c -> {
+ try {
+ c.accept(NO_TRANSACTION);
+ } catch (Exception e) {
+ unexpectedException.compareAndSet(null, e);
+ // TODO log this
+ }
+ });
+ }
+
+ @Override
+ public void ended(Coordination coordination) throws Exception {
+ beforeCompletion();
+
+ afterCompletion();
+ }
+ });
+ }
+
+ @Override
+ public Object getTransactionKey() {
+ return null;
+ }
+
+ @Override
+ public boolean getRollbackOnly() throws IllegalStateException {
+ throw new IllegalStateException("No transaction is active");
+ }
+
+ @Override
+ public void setRollbackOnly() throws IllegalStateException {
+ throw new IllegalStateException("No transaction is active");
+ }
+
+ @Override
+ public TransactionStatus getTransactionStatus() {
+ return NO_TRANSACTION;
+ }
+
+ @Override
+ public void preCompletion(Runnable job) throws IllegalStateException {
+ if (coordination.isTerminated()) {
+ throw new IllegalStateException(
+ "The transaction context has finished");
+ }
+ preCompletion.add(job);
+ }
+
+ @Override
+ public void postCompletion(Consumer<TransactionStatus> job)
+ throws IllegalStateException {
+ if (coordination.isTerminated()) {
+ throw new IllegalStateException(
+ "The transaction context has finished");
+ }
+
+ postCompletion.add(job);
+ }
+
+ @Override
+ public void registerXAResource(XAResource resource) {
+ throw new IllegalStateException("No transaction is active");
+ }
+
+ @Override
+ public void registerLocalResource(LocalResource resource) {
+ throw new IllegalStateException("No transaction is active");
+ }
+
+ @Override
+ public boolean supportsXA() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsLocal() {
+ return false;
+ }
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,217 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.osgi.service.transaction.control.TransactionStatus.COMMITTED;
+import static org.osgi.service.transaction.control.TransactionStatus.COMMITTING;
+import static org.osgi.service.transaction.control.TransactionStatus.MARKED_ROLLBACK;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLING_BACK;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.transaction.xa.XAResource;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Participant;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.TransactionContext;
+import org.osgi.service.transaction.control.TransactionStatus;
+
+public class TransactionContextImpl extends AbstractTransactionContextImpl
+ implements TransactionContext {
+
+ final List<LocalResource> resources = new ArrayList<>();
+
+ final List<Runnable> preCompletion = new ArrayList<>();
+ final List<Consumer<TransactionStatus>> postCompletion = new ArrayList<>();
+
+ private volatile TransactionStatus tranStatus;
+
+ public TransactionContextImpl(Coordination coordination) {
+ super(coordination);
+
+ tranStatus = TransactionStatus.ACTIVE;
+
+ coordination.addParticipant(new Participant() {
+
+ @Override
+ public void failed(Coordination coordination) throws Exception {
+ setRollbackOnly();
+
+ beforeCompletion();
+
+ vanillaRollback();
+
+ afterCompletion();
+ }
+
+ private void vanillaRollback() {
+
+ tranStatus = ROLLING_BACK;
+
+ resources.stream().forEach(lr -> {
+ try {
+ lr.rollback();
+ } catch (Exception e) {
+ // TODO log this
+ }
+ });
+
+ tranStatus = ROLLED_BACK;
+ }
+
+ private void beforeCompletion() {
+ preCompletion.stream().forEach(r -> {
+ try {
+ r.run();
+ } catch (Exception e) {
+ unexpectedException.compareAndSet(null, e);
+ setRollbackOnly();
+ // TODO log this
+ }
+ });
+ }
+
+ private void afterCompletion() {
+ postCompletion.stream().forEach(c -> {
+ try {
+ c.accept(tranStatus);
+ } catch (Exception e) {
+ unexpectedException.compareAndSet(null, e);
+ // TODO log this
+ }
+ });
+ }
+
+ @Override
+ public void ended(Coordination coordination) throws Exception {
+ beforeCompletion();
+
+ if (getRollbackOnly()) {
+ vanillaRollback();
+ } else {
+ tranStatus = COMMITTING;
+
+ List<LocalResource> committed = new ArrayList<>(
+ resources.size());
+ List<LocalResource> rolledback = new ArrayList<>(0);
+
+ resources.stream().forEach(lr -> {
+ try {
+ if (getRollbackOnly()) {
+ lr.rollback();
+ rolledback.add(lr);
+ } else {
+ lr.commit();
+ committed.add(lr);
+ }
+ } catch (Exception e) {
+ unexpectedException.compareAndSet(null, e);
+ if (committed.isEmpty()) {
+ tranStatus = ROLLING_BACK;
+ }
+ rolledback.add(lr);
+ }
+ });
+ tranStatus = tranStatus == ROLLING_BACK ? ROLLED_BACK
+ : COMMITTED;
+ }
+ afterCompletion();
+ }
+ });
+ }
+
+ @Override
+ public Object getTransactionKey() {
+ return coordination.getId();
+ }
+
+ @Override
+ public boolean getRollbackOnly() throws IllegalStateException {
+ switch (tranStatus) {
+ case MARKED_ROLLBACK :
+ case ROLLING_BACK :
+ case ROLLED_BACK :
+ return true;
+ default :
+ return false;
+ }
+ }
+
+ @Override
+ public void setRollbackOnly() throws IllegalStateException {
+ switch (tranStatus) {
+ case ACTIVE :
+ case MARKED_ROLLBACK :
+ tranStatus = MARKED_ROLLBACK;
+ break;
+ case COMMITTING :
+ // TODO something here? If it's the first resource then it might
+ // be ok to roll back?
+ throw new IllegalStateException(
+ "The transaction is already being committed");
+ case COMMITTED :
+ throw new IllegalStateException(
+ "The transaction is already committed");
+
+ case ROLLING_BACK :
+ case ROLLED_BACK :
+ // A no op
+ break;
+ default :
+ throw new IllegalStateException(
+ "The transaction is in an unkown state");
+ }
+ }
+
+ @Override
+ public TransactionStatus getTransactionStatus() {
+ return tranStatus;
+ }
+
+ @Override
+ public void preCompletion(Runnable job) throws IllegalStateException {
+ if (tranStatus.compareTo(MARKED_ROLLBACK) > 0) {
+ throw new IllegalStateException(
+ "The current transaction is in state " + tranStatus);
+ }
+
+ preCompletion.add(job);
+ }
+
+ @Override
+ public void postCompletion(Consumer<TransactionStatus> job)
+ throws IllegalStateException {
+ if (tranStatus == COMMITTED || tranStatus == ROLLED_BACK) {
+ throw new IllegalStateException(
+ "The current transaction is in state " + tranStatus);
+ }
+
+ postCompletion.add(job);
+ }
+
+ @Override
+ public void registerXAResource(XAResource resource) {
+ throw new IllegalStateException("Not an XA manager");
+ }
+
+ @Override
+ public void registerLocalResource(LocalResource resource) {
+ if (tranStatus.compareTo(MARKED_ROLLBACK) > 0) {
+ throw new IllegalStateException(
+ "The current transaction is in state " + tranStatus);
+ }
+ resources.add(resource);
+ }
+
+ @Override
+ public boolean supportsXA() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsLocal() {
+ return true;
+ }
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,258 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static java.util.Optional.ofNullable;
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+
+import java.util.concurrent.Callable;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.transaction.control.TransactionBuilder;
+import org.osgi.service.transaction.control.TransactionContext;
+import org.osgi.service.transaction.control.TransactionControl;
+import org.osgi.service.transaction.control.TransactionException;
+import org.osgi.service.transaction.control.TransactionRolledBackException;
+
+public class TransactionControlImpl implements TransactionControl {
+
+ private final class TransactionBuilderImpl extends TransactionBuilder {
+
+
+ @Override
+ public <T> T required(Callable<T> work)
+ throws TransactionException, TransactionRolledBackException {
+
+ Coordination currentCoord = coordinator.peek();
+ boolean endCoordination = false;
+
+ AbstractTransactionContextImpl currentTran = ofNullable(
+ currentCoord).map(c -> (AbstractTransactionContextImpl) c
+ .getVariables().get(TransactionContextKey.class))
+ .filter(atc -> atc
+ .getTransactionStatus() != NO_TRANSACTION)
+ .orElse(null);
+ try {
+ if (currentTran == null) {
+ // We must create a new coordination to scope our new
+ // transaction
+ currentCoord = coordinator.begin(
+ "Resource-Local-Transaction.REQUIRED", 30000);
+ endCoordination = true;
+ currentTran = new TransactionContextImpl(currentCoord);
+ currentCoord.getVariables().put(TransactionContextKey.class,
+ currentTran);
+ }
+ } catch (RuntimeException re) {
+ if (endCoordination) {
+ currentCoord.end();
+ }
+ throw re;
+ }
+
+ return doWork(work, currentCoord, endCoordination);
+ }
+
+ @Override
+ public <T> T requiresNew(Callable<T> work)
+ throws TransactionException, TransactionRolledBackException {
+ Coordination currentCoord = null;
+ try {
+ currentCoord = coordinator.begin(
+ "Resource-Local-Transaction.REQUIRES_NEW", 30000);
+
+ AbstractTransactionContextImpl currentTran = new TransactionContextImpl(
+ currentCoord);
+ currentCoord.getVariables().put(TransactionContextKey.class,
+ currentTran);
+ } catch (RuntimeException re) {
+ if (currentCoord != null)
+ currentCoord.end();
+ throw re;
+ }
+
+ return doWork(work, currentCoord, true);
+ }
+
+ @Override
+ public <T> T supports(Callable<T> work) throws TransactionException {
+ Coordination currentCoord = coordinator.peek();
+ boolean endCoordination = false;
+
+ AbstractTransactionContextImpl currentTran = ofNullable(
+ currentCoord).map(c -> (AbstractTransactionContextImpl) c
+ .getVariables().get(TransactionContextKey.class))
+ .orElse(null);
+ try {
+ if (currentTran == null) {
+ // We must create a new coordination to scope our new
+ // transaction
+ currentCoord = coordinator.begin(
+ "Resource-Local-Transaction.SUPPORTS", 30000);
+ endCoordination = true;
+ currentTran = new NoTransactionContextImpl(currentCoord);
+ currentCoord.getVariables().put(TransactionContextKey.class,
+ currentTran);
+ }
+ } catch (RuntimeException re) {
+ if (endCoordination) {
+ currentCoord.end();
+ }
+ throw re;
+ }
+
+ return doWork(work, currentCoord, endCoordination);
+ }
+
+ @Override
+ public <T> T notSupported(Callable<T> work)
+ throws TransactionException {
+ Coordination currentCoord = coordinator.peek();
+ boolean endCoordination = false;
+
+ AbstractTransactionContextImpl currentTran = ofNullable(
+ currentCoord).map(c -> (AbstractTransactionContextImpl) c
+ .getVariables().get(TransactionContextKey.class))
+ .filter(atc -> atc
+ .getTransactionStatus() == NO_TRANSACTION)
+ .orElse(null);
+ try {
+ if (currentTran == null) {
+ // We must create a new coordination to scope our new
+ // transaction
+ currentCoord = coordinator.begin(
+ "Resource-Local-Transaction.NOT_SUPPORTED", 30000);
+ endCoordination = true;
+ currentTran = new NoTransactionContextImpl(currentCoord);
+ currentCoord.getVariables().put(TransactionContextKey.class,
+ currentTran);
+ }
+ } catch (RuntimeException re) {
+ if (endCoordination) {
+ currentCoord.end();
+ }
+ throw re;
+ }
+ return doWork(work, currentCoord, endCoordination);
+ }
+
+ private <R> R doWork(Callable<R> transactionalWork,
+ Coordination currentCoord, boolean endCoordination) {
+ try {
+ R result = transactionalWork.call();
+
+ if (endCoordination) {
+ currentCoord.end();
+ }
+ return result;
+ } catch (Throwable t) {
+ if (endCoordination) {
+ //TODO handle noRollbackFor
+ currentCoord.fail(t);
+ }
+ TransactionControlImpl.<RuntimeException> throwException(t);
+ }
+ throw new TransactionException(
+ "The code here should never be reached");
+ }
+ }
+
+ private static class TransactionContextKey {}
+
+ private final Coordinator coordinator;
+
+ public TransactionControlImpl(Coordinator c) {
+ coordinator = c;
+ }
+
+ @Override
+ public TransactionBuilder build() {
+ return new TransactionBuilderImpl();
+ }
+
+ @Override
+ public boolean getRollbackOnly() throws IllegalStateException {
+ return getCurrentTranContextChecked().getRollbackOnly();
+ }
+
+ @Override
+ public void setRollbackOnly() throws IllegalStateException {
+ getCurrentTranContextChecked().setRollbackOnly();
+ }
+
+ @Override
+ public <T> T required(Callable<T> work)
+ throws TransactionException, TransactionRolledBackException {
+ return build().required(work);
+ }
+
+ @Override
+ public <T> T requiresNew(Callable<T> work)
+ throws TransactionException, TransactionRolledBackException {
+ return build().requiresNew(work);
+ }
+
+ @Override
+ public <T> T notSupported(Callable<T> work) throws TransactionException {
+ return build().notSupported(work);
+ }
+
+ @Override
+ public <T> T supports(Callable<T> work) throws TransactionException {
+ return build().supports(work);
+ }
+
+ @Override
+ public boolean activeTransaction() {
+ TransactionContext context = getCurrentContext();
+ return context != null
+ && context.getTransactionStatus() != NO_TRANSACTION;
+ }
+
+ @Override
+ public boolean activeScope() {
+ return getCurrentContext() != null;
+ }
+
+ private TransactionContext getCurrentTranContextChecked() {
+ TransactionContext toUse = getCurrentContext();
+ if (toUse == null) {
+ throw new IllegalStateException(
+ "There is no applicable transaction context");
+ }
+ return toUse;
+ }
+
+ @Override
+ public TransactionContext getCurrentContext() {
+ TransactionContext toUse = null;
+
+ Coordination peek = coordinator.peek();
+ if (peek != null) {
+ toUse = (TransactionContext) peek.getVariables()
+ .get(TransactionContextKey.class);
+ }
+ return toUse;
+ }
+
+ /**
+ * Borrowed from the netty project as a way to avoid wrapping checked
+ * exceptions Viewable at https://github.com/netty/netty/
+ * netty/common/src/main/java/io/netty/util/internal/PlatformDependent.java
+ *
+ * @param t
+ * @return
+ * @throws T
+ */
+ @SuppressWarnings("unchecked")
+ private static <T extends Throwable> T throwException(Throwable t)
+ throws T {
+ throw (T) t;
+ }
+
+ @Override
+ public void ignoreException(Throwable t) throws IllegalStateException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/NoTransactionContextTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,240 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.transaction.xa.XAResource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Participant;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.TransactionContext;
+
+@RunWith(MockitoJUnitRunner.class)
+public class NoTransactionContextTest {
+
+ @Mock
+ Coordination coordination;
+ @Mock
+ XAResource xaResource;
+ @Mock
+ LocalResource localResource;
+
+ Map<Class<?>, Object> variables;
+
+ TransactionContext ctx;
+
+ @Before
+ public void setUp() {
+ ctx = new NoTransactionContextImpl(coordination);
+ variables = new HashMap<>();
+ Mockito.when(coordination.getVariables()).thenReturn(variables);
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testGetRollbackOnly() {
+ ctx.getRollbackOnly();
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testSetRollbackOnly() {
+ ctx.setRollbackOnly();
+ }
+
+ @Test
+ public void testTransactionKey() {
+ assertNull(ctx.getTransactionKey());
+ }
+
+ @Test
+ public void testTransactionStatus() {
+ assertEquals(NO_TRANSACTION, ctx.getTransactionStatus());
+ }
+
+ @Test
+ public void testLocalResourceSupport() {
+ assertFalse(ctx.supportsLocal());
+ }
+
+ @Test
+ public void testXAResourceSupport() {
+ assertFalse(ctx.supportsXA());
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testLocalResourceRegistration() {
+ ctx.registerLocalResource(localResource);
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testXAResourceRegistration() {
+ ctx.registerXAResource(xaResource);
+ }
+
+ @Test
+ public void testScopedValues() {
+ assertNull(ctx.getScopedValue("foo"));
+
+ Object value = new Object();
+
+ ctx.putScopedValue("foo", value);
+
+ assertSame(value, ctx.getScopedValue("foo"));
+ }
+
+ @Test
+ public void testPreCompletionEnd() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> value.compareAndSet(1, 5));
+
+ assertEquals(0, value.getAndSet(1));
+
+ Participant participant = getParticipant();
+ participant.ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPreCompletionFail() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> value.compareAndSet(1, 5));
+
+ assertEquals(0, value.getAndSet(1));
+
+ Participant participant = getParticipant();
+ participant.failed(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionEnd() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.postCompletion(status -> {
+ assertEquals(NO_TRANSACTION, status);
+ value.compareAndSet(1, 5);
+ });
+
+ assertEquals(0, value.getAndSet(1));
+
+ Participant participant = getParticipant();
+ participant.ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionFail() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.postCompletion(status -> {
+ assertEquals(NO_TRANSACTION, status);
+ value.compareAndSet(1, 5);
+ });
+
+ assertEquals(0, value.getAndSet(1));
+
+ Participant participant = getParticipant();
+ participant.failed(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionIsAfterPreCompletionEnd() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> value.compareAndSet(0, 3));
+ ctx.postCompletion(status -> {
+ assertEquals(NO_TRANSACTION, status);
+ value.compareAndSet(3, 5);
+ });
+
+ Participant participant = getParticipant();
+ participant.ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionIsAfterPreCompletionFail() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> value.compareAndSet(0, 3));
+ ctx.postCompletion(status -> {
+ assertEquals(NO_TRANSACTION, status);
+ value.compareAndSet(3, 5);
+ });
+
+ Participant participant = getParticipant();
+ participant.failed(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionIsStillCalledAfterPreCompletionException() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> {
+ value.compareAndSet(0, 3);
+ throw new RuntimeException("Boom!");
+ });
+ ctx.postCompletion(status -> {
+ assertEquals(NO_TRANSACTION, status);
+ value.compareAndSet(3, 5);
+ });
+
+ Participant participant = getParticipant();
+ participant.ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ private Participant getParticipant() {
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ Participant participant = captor.getValue();
+ return participant;
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testPreCompletionAfterEnd() throws Exception {
+
+ Mockito.when(coordination.isTerminated()).thenReturn(true);
+ ctx.preCompletion(() -> {});
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testPostCompletionAfterEnd() throws Exception {
+ Mockito.when(coordination.isTerminated()).thenReturn(true);
+ ctx.postCompletion(x -> {});
+ }
+
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,379 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.osgi.service.transaction.control.TransactionStatus.ACTIVE;
+import static org.osgi.service.transaction.control.TransactionStatus.COMMITTED;
+import static org.osgi.service.transaction.control.TransactionStatus.COMMITTING;
+import static org.osgi.service.transaction.control.TransactionStatus.MARKED_ROLLBACK;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK;
+import static org.osgi.service.transaction.control.TransactionStatus.ROLLING_BACK;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.transaction.xa.XAResource;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Participant;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.TransactionContext;
+import org.osgi.service.transaction.control.TransactionException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TransactionContextTest {
+
+ @Mock
+ Coordination coordination;
+ @Mock
+ XAResource xaResource;
+ @Mock
+ LocalResource localResource;
+
+ Map<Class<?>, Object> variables;
+
+ TransactionContext ctx;
+
+ @Before
+ public void setUp() {
+ ctx = new TransactionContextImpl(coordination);
+ variables = new HashMap<>();
+ Mockito.when(coordination.getVariables()).thenReturn(variables);
+ }
+
+ @Test
+ public void testGetRollbackOnly() {
+ assertFalse(ctx.getRollbackOnly());
+ }
+
+ @Test
+ public void testSetRollbackOnly() {
+ ctx.setRollbackOnly();
+ assertTrue(ctx.getRollbackOnly());
+ }
+
+ @Test
+ public void testTransactionKey() {
+ Mockito.when(coordination.getId()).thenReturn(42L);
+
+ assertNotNull(ctx.getTransactionKey());
+ }
+
+ @Test
+ public void testTransactionStatus() {
+ assertEquals(ACTIVE, ctx.getTransactionStatus());
+
+ ctx.setRollbackOnly();
+
+ assertEquals(MARKED_ROLLBACK, ctx.getTransactionStatus());
+ }
+
+ @Test
+ public void testLocalResourceSupport() {
+ assertTrue(ctx.supportsLocal());
+ }
+
+ @Test
+ public void testXAResourceSupport() {
+ assertFalse(ctx.supportsXA());
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testXAResourceRegistration() {
+ ctx.registerXAResource(xaResource);
+ }
+
+ @Test
+ public void testScopedValues() {
+ assertNull(ctx.getScopedValue("foo"));
+
+ Object value = new Object();
+
+ ctx.putScopedValue("foo", value);
+
+ assertSame(value, ctx.getScopedValue("foo"));
+ }
+
+ @Test
+ public void testPreCompletionEnd() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> {
+ assertEquals(ACTIVE, ctx.getTransactionStatus());
+ value.compareAndSet(1, 5);
+ });
+
+ assertEquals(0, value.getAndSet(1));
+
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ captor.getValue().ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPreCompletionFail() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> {
+ assertEquals(MARKED_ROLLBACK, ctx.getTransactionStatus());
+ value.compareAndSet(1, 5);
+ });
+
+ assertEquals(0, value.getAndSet(1));
+
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ captor.getValue().failed(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionEnd() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.postCompletion(status -> {
+ assertEquals(COMMITTED, status);
+ value.compareAndSet(1, 5);
+ });
+
+ assertEquals(0, value.getAndSet(1));
+
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ captor.getValue().ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionFail() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.postCompletion(status -> {
+ assertEquals(ROLLED_BACK, status);
+ value.compareAndSet(1, 5);
+ });
+
+ assertEquals(0, value.getAndSet(1));
+
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ captor.getValue().failed(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionIsAfterPreCompletion() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> {
+ assertEquals(ACTIVE, ctx.getTransactionStatus());
+ value.compareAndSet(0, 3);
+ });
+ ctx.postCompletion(status -> {
+ assertEquals(COMMITTED, status);
+ value.compareAndSet(3, 5);
+ });
+
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ captor.getValue().ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ @Test
+ public void testPostCompletionIsStillCalledAfterPreCompletionException() throws Exception {
+
+ AtomicInteger value = new AtomicInteger(0);
+
+ ctx.preCompletion(() -> {
+ value.compareAndSet(0, 3);
+ throw new RuntimeException("Boom!");
+ });
+ ctx.postCompletion(status -> {
+ assertEquals(ROLLED_BACK, status);
+ value.compareAndSet(3, 5);
+ });
+
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ captor.getValue().ended(coordination);
+
+ assertEquals(5, value.get());
+ }
+
+ private Participant getParticipant() {
+ ArgumentCaptor<Participant> captor = ArgumentCaptor.forClass(Participant.class);
+ Mockito.verify(coordination).addParticipant(captor.capture());
+
+ Participant participant = captor.getValue();
+ return participant;
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testPreCompletionAfterEnd() throws Exception {
+
+ getParticipant().ended(coordination);
+
+ Mockito.when(coordination.isTerminated()).thenReturn(true);
+ ctx.preCompletion(() -> {});
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testPreCompletionAfterFail() throws Exception {
+
+ getParticipant().failed(coordination);
+
+ Mockito.when(coordination.isTerminated()).thenReturn(true);
+ ctx.preCompletion(() -> {});
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testPostCompletionAfterEnd() throws Exception {
+
+ getParticipant().ended(coordination);
+
+ Mockito.when(coordination.isTerminated()).thenReturn(true);
+ ctx.postCompletion(x -> {});
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testPostCompletionAfterFail() throws Exception {
+
+ getParticipant().failed(coordination);
+
+ Mockito.when(coordination.isTerminated()).thenReturn(true);
+ ctx.postCompletion(x -> {});
+ }
+
+ @Test
+ public void testLocalResourceEnd() throws Exception {
+ ctx.registerLocalResource(localResource);
+
+ Mockito.doAnswer(i -> {
+ assertEquals(COMMITTING, ctx.getTransactionStatus());
+ return null;
+ }).when(localResource).commit();
+
+ getParticipant().ended(coordination);
+
+ Mockito.verify(localResource).commit();
+ }
+
+ @Test
+ public void testLocalResourceEndRollbackOnly() throws Exception {
+ ctx.registerLocalResource(localResource);
+ ctx.setRollbackOnly();
+
+ Mockito.doAnswer(i -> {
+ assertEquals(ROLLING_BACK, ctx.getTransactionStatus());
+ return null;
+ }).when(localResource).rollback();
+
+ getParticipant().ended(coordination);
+
+ Mockito.verify(localResource).rollback();
+ }
+
+ @Test
+ public void testLocalResourceFail() throws Exception {
+ ctx.registerLocalResource(localResource);
+
+ Mockito.doAnswer(i -> {
+ assertEquals(ROLLING_BACK, ctx.getTransactionStatus());
+ return null;
+ }).when(localResource).rollback();
+
+ getParticipant().failed(coordination);
+
+ Mockito.verify(localResource).rollback();
+ }
+
+ @Test
+ public void testLocalResourceEndPreCommitException() throws Exception {
+ ctx.registerLocalResource(localResource);
+
+ Mockito.doAnswer(i -> {
+ assertEquals(ROLLING_BACK, ctx.getTransactionStatus());
+ return null;
+ }).when(localResource).rollback();
+
+ ctx.preCompletion(() -> { throw new IllegalArgumentException(); });
+
+ getParticipant().ended(coordination);
+
+ Mockito.verify(localResource).rollback();
+ }
+
+ @Test
+ public void testLocalResourceEndPostCommitException() throws Exception {
+ ctx.registerLocalResource(localResource);
+
+ Mockito.doAnswer(i -> {
+ assertEquals(COMMITTING, ctx.getTransactionStatus());
+ return null;
+ }).when(localResource).commit();
+
+ ctx.postCompletion(i -> {
+ assertEquals(COMMITTED, ctx.getTransactionStatus());
+ throw new IllegalArgumentException();
+ });
+
+ getParticipant().ended(coordination);
+
+ Mockito.verify(localResource).commit();
+ }
+
+ @Test
+ public void testLocalResourcesFirstFailsSoRollback() throws Exception {
+
+ ctx.registerLocalResource(localResource);
+
+ LocalResource localResource2 = Mockito.mock(LocalResource.class);
+ ctx.registerLocalResource(localResource2);
+
+ Mockito.doAnswer(i -> {
+ assertEquals(COMMITTING, ctx.getTransactionStatus());
+ throw new TransactionException("Unable to commit");
+ }).when(localResource).commit();
+
+ Mockito.doAnswer(i -> {
+ assertEquals(ROLLING_BACK, ctx.getTransactionStatus());
+ return null;
+ }).when(localResource2).rollback();
+
+ getParticipant().ended(coordination);
+
+ Mockito.verify(localResource).commit();
+ Mockito.verify(localResource2).rollback();
+ }
+
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlStatusTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,321 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.osgi.service.transaction.control.TransactionStatus.ACTIVE;
+import static org.osgi.service.transaction.control.TransactionStatus.MARKED_ROLLBACK;
+import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.aries.tx.control.service.local.impl.TransactionControlImpl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.coordinator.Participant;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.ResourceProvider;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TransactionControlStatusTest {
+
+ @Mock
+ Coordinator coordinator;
+ @Mock
+ Coordination coordination1;
+ @Mock
+ Coordination coordination2;
+
+ @Mock
+ ResourceProvider<Object> testProvider;
+ @Mock
+ LocalResource testResource;
+
+ TransactionControlImpl txControl;
+
+ Object resource = new Object();
+ Map<Class<?>, Object> variables1;
+ Map<Class<?>, Object> variables2;
+
+ @Before
+ public void setUp() {
+
+ resource = new Object();
+ variables1 = new HashMap<>();
+ variables2 = new HashMap<>();
+
+ Mockito.when(coordination1.getVariables()).thenReturn(variables1);
+ Mockito.when(coordination2.getVariables()).thenReturn(variables2);
+
+ txControl = new TransactionControlImpl(coordinator);
+ }
+
+ @Test
+ public void testGetRollbackOnlyUnscopedNoCoord() {
+ try {
+ txControl.getRollbackOnly();
+ fail("Should not be able to get rollback only");
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void testSetRollbackOnlyUnscopedNoCoord() {
+ try {
+ txControl.setRollbackOnly();
+ fail("Should not be able to set rollback only");
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void testTranChecksUnscopedNoCoord() {
+ assertFalse(txControl.activeTransaction());
+ assertFalse(txControl.activeScope());
+ assertNull(txControl.getCurrentContext());
+ }
+
+ private void setupExistingCoordination() {
+ Mockito.when(coordinator.peek()).thenReturn(coordination1);
+ Mockito.when(coordination1.getVariables()).thenReturn(variables1);
+ }
+
+ @Test
+ public void testGetRollbackOnlyUnscopedWithCoordination() {
+ setupExistingCoordination();
+
+ try {
+ txControl.getRollbackOnly();
+ fail("Should not be able to get rollback only");
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void testSetRollbackOnlyUnscopedWithCoordination() {
+ setupExistingCoordination();
+
+
+ try {
+ txControl.setRollbackOnly();
+ fail("Should not be able to set rollback only");
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void testTranChecksUnscopedWithCoordination() {
+
+ setupExistingCoordination();
+
+ assertFalse(txControl.activeTransaction());
+ assertFalse(txControl.activeScope());
+ assertNull(txControl.getCurrentContext());
+ }
+
+ private void setupCoordinatorForSingleTransaction() {
+ setupCoordinatorForSingleTransaction(null);
+ }
+
+ private void setupCoordinatorForSingleTransaction(Coordination existing) {
+
+ Mockito.when(coordinator.peek()).thenReturn(existing);
+
+ Mockito.when(coordinator.begin(Mockito.anyString(), Mockito.anyLong()))
+ .then(i -> {
+ Mockito.when(coordinator.peek()).thenReturn(coordination1);
+ return coordination1;
+ });
+
+
+ Mockito.doAnswer(i -> Mockito.when(coordinator.peek()).thenReturn(existing))
+ .when(coordination1).end();
+ Mockito.doAnswer(i -> Mockito.when(coordinator.peek()).thenReturn(existing) != null)
+ .when(coordination1).fail(Mockito.any(Throwable.class));
+
+ Mockito.when(coordination1.getVariables()).thenReturn(variables1);
+ }
+
+ @Test
+ public void testGetRollbackOnlyScoped() {
+ setupCoordinatorForSingleTransaction();
+ txControl.notSupported(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ try {
+ txControl.getRollbackOnly();
+ fail("Should not be able to get or set rollback when there is no transaction");
+ } catch (IllegalStateException ise) {
+ }
+ return null;
+ });
+ }
+
+ @Test
+ public void testSetRollbackOnlyScoped() {
+ setupCoordinatorForSingleTransaction();
+
+ txControl.notSupported(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ try {
+ txControl.setRollbackOnly();
+ fail("Should not be able to get or set rollback when there is no transaction");
+ } catch (IllegalStateException ise) {
+ }
+ return null;
+ });
+ }
+
+ @Test
+ public void testTranChecksScoped() {
+
+ setupCoordinatorForSingleTransaction();
+ txControl.notSupported(() -> {
+ assertFalse(txControl.activeTransaction());
+ assertTrue(txControl.activeScope());
+ assertNotNull(txControl.getCurrentContext());
+ assertEquals(NO_TRANSACTION, txControl.getCurrentContext().getTransactionStatus());
+
+ return null;
+ });
+ }
+
+ @Test
+ public void testGetRollbackOnlyScopedExistingCoordination() {
+ setupCoordinatorForSingleTransaction(coordination2);
+ txControl.notSupported(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ try {
+ txControl.getRollbackOnly();
+ fail("Should not be able to get or set rollback when there is no transaction");
+ } catch (IllegalStateException ise) {
+ }
+ return null;
+ });
+ }
+
+ @Test
+ public void testSetRollbackOnlyScopedExistingCoordination() {
+ setupCoordinatorForSingleTransaction(coordination2);
+
+ txControl.notSupported(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ try {
+ txControl.setRollbackOnly();
+ fail("Should not be able to get or set rollback when there is no transaction");
+ } catch (IllegalStateException ise) {
+ }
+ return null;
+ });
+ }
+
+ @Test
+ public void testTranChecksScopedExistingCoordination() {
+
+ setupCoordinatorForSingleTransaction(coordination2);
+ txControl.notSupported(() -> {
+ assertFalse(txControl.activeTransaction());
+ assertTrue(txControl.activeScope());
+ assertNotNull(txControl.getCurrentContext());
+ assertEquals(NO_TRANSACTION, txControl.getCurrentContext().getTransactionStatus());
+
+ return null;
+ });
+ }
+
+ @Test
+ public void testGetRollbackOnlyActive() {
+ setupCoordinatorForSingleTransaction();
+ txControl.required(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ assertFalse(txControl.getRollbackOnly());
+ return null;
+ });
+ }
+
+ @Test
+ public void testSetRollbackOnlyActive() {
+ setupCoordinatorForSingleTransaction();
+
+ txControl.required(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ assertFalse(txControl.getRollbackOnly());
+ txControl.setRollbackOnly();
+ assertTrue(txControl.getRollbackOnly());
+
+ return null;
+ });
+ }
+
+ @Test
+ public void testTranChecksActive() {
+
+ setupCoordinatorForSingleTransaction();
+ txControl.required(() -> {
+ assertTrue(txControl.activeTransaction());
+ assertTrue(txControl.activeScope());
+ assertNotNull(txControl.getCurrentContext());
+ assertEquals(ACTIVE, txControl.getCurrentContext().getTransactionStatus());
+
+ txControl.setRollbackOnly();
+ assertEquals(MARKED_ROLLBACK, txControl.getCurrentContext().getTransactionStatus());
+
+ return null;
+ });
+ }
+
+ @Test
+ public void testGetRollbackOnlyActiveExistingCoordination() {
+ setupCoordinatorForSingleTransaction(coordination2);
+ txControl.required(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ assertFalse(txControl.getRollbackOnly());
+ return null;
+ });
+ }
+
+ @Test
+ public void testSetRollbackOnlyActiveExistingCoordination() {
+ setupCoordinatorForSingleTransaction(coordination2);
+
+ txControl.required(() -> {
+ Mockito.verify(coordination1).addParticipant(Mockito.any(Participant.class));
+ assertFalse(txControl.getRollbackOnly());
+ txControl.setRollbackOnly();
+ assertTrue(txControl.getRollbackOnly());
+
+ return null;
+ });
+ }
+
+ @Test
+ public void testTranChecksActiveExistingCoordination() {
+
+ setupCoordinatorForSingleTransaction(coordination2);
+ txControl.required(() -> {
+ assertTrue(txControl.activeTransaction());
+ assertTrue(txControl.activeScope());
+ assertNotNull(txControl.getCurrentContext());
+ assertEquals(ACTIVE, txControl.getCurrentContext().getTransactionStatus());
+
+ txControl.setRollbackOnly();
+ assertEquals(MARKED_ROLLBACK, txControl.getCurrentContext().getTransactionStatus());
+
+ return null;
+ });
+ }
+
+}
Added: aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java?rev=1730701&view=auto
==============================================================================
--- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java (added)
+++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionLifecycleTest.java Tue Feb 16 15:35:02 2016
@@ -0,0 +1,303 @@
+package org.apache.aries.tx.control.service.local.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.coordinator.Coordinator;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.ResourceProvider;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TransactionLifecycleTest {
+
+ @Mock
+ Coordinator coordinator;
+ @Mock
+ Coordination coordination1;
+ @Mock
+ Coordination coordination2;
+
+ @Mock
+ ResourceProvider<Object> testProvider;
+ @Mock
+ LocalResource testResource;
+
+ TransactionControlImpl txControl;
+
+ Map<Class<?>, Object> variables1;
+ Map<Class<?>, Object> variables2;
+
+ @Before
+ public void setUp() {
+ variables1 = new HashMap<>();
+ variables2 = new HashMap<>();
+
+ setupCoordinations();
+
+ txControl = new TransactionControlImpl(coordinator);
+ }
+
+ /**
+ * Allow up to two Coordinations to be happening
+ */
+ private void setupCoordinations() {
+ Mockito.when(coordinator.begin(Mockito.anyString(), Mockito.anyLong())).then(i -> {
+ Mockito.when(coordinator.peek()).thenReturn(coordination1);
+ return coordination1;
+ }).then(i -> {
+ Mockito.when(coordinator.peek()).thenReturn(coordination2);
+ return coordination2;
+ }).thenThrow(new IllegalStateException("Only two coordinations at a time in the test"));
+
+ Mockito.when(coordination1.getVariables()).thenReturn(variables1);
+ Mockito.when(coordination1.getId()).thenReturn(42L);
+ Mockito.doAnswer(i -> {
+ Mockito.when(coordinator.peek()).thenReturn(null);
+ return null;
+ }).when(coordination1).end();
+ Mockito.doAnswer(i -> {
+ Mockito.when(coordinator.peek()).thenReturn(null);
+ return null;
+ }).when(coordination1).fail(Mockito.any(Throwable.class));
+
+ Mockito.when(coordination2.getVariables()).thenReturn(variables2);
+ Mockito.when(coordination2.getId()).thenReturn(43L);
+ Mockito.doAnswer(i -> {
+ Mockito.when(coordinator.peek()).thenReturn(coordination1);
+ return null;
+ }).when(coordination2).end();
+ Mockito.doAnswer(i -> {
+ Mockito.when(coordinator.peek()).thenReturn(coordination1);
+ return null;
+ }).when(coordination2).fail(Mockito.any(Throwable.class));
+ }
+
+ @Test
+ public void testRequired() {
+
+ txControl.required(() -> {
+
+ assertTrue(txControl.activeTransaction());
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNestedRequired() {
+
+ txControl.required(() -> {
+
+ assertTrue(txControl.activeTransaction());
+
+ Object key = txControl.getCurrentContext().getTransactionKey();
+ txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE);
+
+ txControl.required(() -> {
+ assertEquals(key, txControl.getCurrentContext().getTransactionKey());
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible"));
+ txControl.getCurrentContext().putScopedValue("visible", Boolean.FALSE);
+ return null;
+ });
+
+ assertEquals(key, txControl.getCurrentContext().getTransactionKey());
+ assertEquals(Boolean.FALSE, txControl.getCurrentContext().getScopedValue("visible"));
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNestedRequiredFromNoTran() {
+
+ txControl.supports(() -> {
+
+ assertFalse(txControl.activeTransaction());
+
+ txControl.getCurrentContext().putScopedValue("invisible", Boolean.TRUE);
+
+ txControl.required(() -> {
+ assertTrue(txControl.activeTransaction());
+ assertNull(txControl.getCurrentContext().getScopedValue("invisible"));
+ txControl.getCurrentContext().putScopedValue("invisible", Boolean.FALSE);
+ return null;
+ });
+
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("invisible"));
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testRequiresNew() {
+
+ txControl.requiresNew(() -> {
+
+ assertTrue(txControl.activeTransaction());
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNestedRequiresNew() {
+
+ txControl.required(() -> {
+
+ assertTrue(txControl.activeTransaction());
+
+ Object key = txControl.getCurrentContext().getTransactionKey();
+ txControl.getCurrentContext().putScopedValue("invisible", Boolean.TRUE);
+
+ txControl.requiresNew(() -> {
+ assertFalse("Parent key " + key + " Child Key " + txControl.getCurrentContext().getTransactionKey(),
+ key.equals(txControl.getCurrentContext().getTransactionKey()));
+ assertNull(txControl.getCurrentContext().getScopedValue("invisible"));
+ txControl.getCurrentContext().putScopedValue("invisible", Boolean.FALSE);
+ return null;
+ });
+
+ assertEquals(key, txControl.getCurrentContext().getTransactionKey());
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("invisible"));
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testSupports() {
+
+ txControl.supports(() -> {
+
+ assertFalse(txControl.activeTransaction());
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNestedSupports() {
+
+ txControl.supports(() -> {
+
+ assertFalse(txControl.activeTransaction());
+
+ txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE);
+
+ txControl.supports(() -> {
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible"));
+ txControl.getCurrentContext().putScopedValue("visible", Boolean.FALSE);
+ return null;
+ });
+
+ assertEquals(Boolean.FALSE, txControl.getCurrentContext().getScopedValue("visible"));
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNestedSupportsInActiveTran() {
+
+ txControl.required(() -> {
+
+ assertTrue(txControl.activeTransaction());
+
+ Object key = txControl.getCurrentContext().getTransactionKey();
+ txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE);
+
+ txControl.supports(() -> {
+ assertEquals(key, txControl.getCurrentContext().getTransactionKey());
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible"));
+ txControl.getCurrentContext().putScopedValue("visible", Boolean.FALSE);
+ return null;
+ });
+
+ assertEquals(key, txControl.getCurrentContext().getTransactionKey());
+ assertEquals(Boolean.FALSE, txControl.getCurrentContext().getScopedValue("visible"));
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNotSupported() {
+
+ txControl.notSupported(() -> {
+
+ assertFalse(txControl.activeTransaction());
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNestedNotSupported() {
+
+ txControl.notSupported(() -> {
+
+ assertFalse(txControl.activeTransaction());
+
+ txControl.getCurrentContext().putScopedValue("visible", Boolean.TRUE);
+
+ txControl.notSupported(() -> {
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible"));
+ return null;
+ });
+
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("visible"));
+
+ return null;
+ });
+
+ }
+
+ @Test
+ public void testNestedNotSupportedInActiveTran() {
+
+ txControl.required(() -> {
+
+ assertTrue(txControl.activeTransaction());
+
+ Object key = txControl.getCurrentContext().getTransactionKey();
+ txControl.getCurrentContext().putScopedValue("invisible", Boolean.TRUE);
+
+ txControl.notSupported(() -> {
+ assertFalse(txControl.activeTransaction());
+ assertNull(txControl.getCurrentContext().getScopedValue("invisible"));
+ txControl.getCurrentContext().putScopedValue("invisible", Boolean.FALSE);
+
+ return null;
+ });
+
+ assertEquals(key, txControl.getCurrentContext().getTransactionKey());
+ assertEquals(Boolean.TRUE, txControl.getCurrentContext().getScopedValue("invisible"));
+
+ return null;
+ });
+
+ }
+
+}