Author: timothyjward
Date: Fri Feb 26 12:18:47 2016
New Revision: 1732464

URL: http://svn.apache.org/viewvc?rev=1732464&view=rev
Log:
[tx-control] Add support for no-rollback exceptions

Added:
    
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlRunningTest.java
Modified:
    
aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java
    
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java

Modified: 
aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java
URL: 
http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java?rev=1732464&r1=1732463&r2=1732464&view=diff
==============================================================================
--- 
aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java
 (original)
+++ 
aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/ExceptionManagementTransactionTest.java
 Fri Feb 26 12:18:47 2016
@@ -29,6 +29,7 @@ import static org.ops4j.pax.exam.CoreOpt
 
 import java.net.URISyntaxException;
 import java.sql.Connection;
+import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
@@ -134,8 +135,6 @@ public class ExceptionManagementTransact
                assertRollback();
        }
 
-       //This test currently fails - the local implementation should probably
-       //use the coordinator a little differently
        @Test
        public void testPreCompletionException() {
                RuntimeException toThrow = new RuntimeException("Bang!");
@@ -158,6 +157,43 @@ public class ExceptionManagementTransact
                assertRollback();
        }
 
+       @Test
+       public void testNoRollbackForException() {
+               RuntimeException toThrow = new RuntimeException("Bang!");
+               
+               try {
+                       txControl.build()
+                               .noRollbackFor(RuntimeException.class)
+                               .required(() -> {
+                                               PreparedStatement ps = 
connection
+                                                               
.prepareStatement("Insert into TEST_TABLE values ( ? )");
+                                               
+                                               ps.setString(1, "Hello World!");
+                                               ps.executeUpdate();
+                                               
+                                               throw toThrow;
+                                       });
+                       fail("An exception should occur!");
+                       // We have to catch Exception as the compiler complains
+                       // otherwise
+               } catch (ScopedWorkException swe) {
+                       assertSame(toThrow, swe.getCause());
+               }
+               
+               assertEquals("1: Hello World!", txControl.notSupported(() -> {
+                       Statement s = connection.createStatement();
+                       
+                       ResultSet rs = s.executeQuery("Select count(*) from 
TEST_TABLE");
+                       rs.next();
+                       int count = rs.getInt(1);
+                       
+                       rs = s.executeQuery("Select message from TEST_TABLE 
ORDER BY message");
+                       
+                       rs.next();
+                       return "" + count + ": " + rs.getString(1);
+               }));
+       }
+
        private void assertRollback() {
                assertEquals(Integer.valueOf(0), txControl.notSupported(() -> {
                        ResultSet rs = connection.createStatement()

Modified: 
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
URL: 
http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java?rev=1732464&r1=1732463&r2=1732464&view=diff
==============================================================================
--- 
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
 (original)
+++ 
aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
 Fri Feb 26 12:18:47 2016
@@ -1,9 +1,13 @@
 package org.apache.aries.tx.control.service.local.impl;
 
 import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
 import static 
org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION;
 import static 
org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.Callable;
 
 import org.osgi.service.coordinator.Coordination;
@@ -20,11 +24,21 @@ public class TransactionControlImpl impl
 
        private final class TransactionBuilderImpl extends TransactionBuilder {
 
+               private void checkExceptions() {
+                       List<Class<? extends Throwable>> duplicates = 
rollbackFor.stream()
+                                       .filter(noRollbackFor::contains)
+                                       .collect(toList());
+                       if(!duplicates.isEmpty()) {
+                               throw new TransactionException("The transaction 
declares that the Exceptions " + 
+                                               duplicates + " must both 
trigger and not trigger rollback");
+                       }
+               }
 
                @Override
                public <T> T required(Callable<T> work)
                                throws TransactionException, 
TransactionRolledBackException {
-
+                       checkExceptions();
+                       
                        Coordination currentCoord = coordinator.peek();
                        boolean endCoordination = false;
 
@@ -58,6 +72,8 @@ public class TransactionControlImpl impl
                @Override
                public <T> T requiresNew(Callable<T> work)
                                throws TransactionException, 
TransactionRolledBackException {
+                       checkExceptions();
+                       
                        Coordination currentCoord = null;
                        AbstractTransactionContextImpl currentTran;
                        try {
@@ -79,6 +95,8 @@ public class TransactionControlImpl impl
 
                @Override
                public <T> T supports(Callable<T> work) throws 
TransactionException {
+                       checkExceptions();
+                       
                        Coordination currentCoord = coordinator.peek();
                        boolean endCoordination = false;
 
@@ -110,6 +128,8 @@ public class TransactionControlImpl impl
                @Override
                public <T> T notSupported(Callable<T> work)
                                throws TransactionException {
+                       checkExceptions();
+                       
                        Coordination currentCoord = coordinator.peek();
                        boolean endCoordination = false;
 
@@ -147,7 +167,9 @@ public class TransactionControlImpl impl
 
                        } catch (Throwable t) {
                                //TODO handle noRollbackFor
-                               currentCoord.fail(t);
+                               if(requiresRollback(t)) {
+                                       currentCoord.fail(t);
+                               }
                                try {
                                        currentTran.finish();
                                } catch (Exception e) {
@@ -202,6 +224,24 @@ public class TransactionControlImpl impl
                        
                        return result;
                }
+
+               private boolean requiresRollback(Throwable t) {
+                       return mostSpecificMatch(noRollbackFor, t)
+                               .map(noRollbackType -> 
mostSpecificMatch(rollbackFor, t)
+                                               .map(rollbackType -> 
noRollbackType.isAssignableFrom(rollbackType))
+                                               .orElse(false))
+                               .orElse(true);
+               }
+               
+               private Optional<Class<? extends Throwable>> 
mostSpecificMatch(Collection<Class<? extends Throwable>> types, Throwable t) {
+                       return types.stream()
+                                       .filter(c -> c.isInstance(t))
+                                       .max((c1, c2) -> {
+                                                       if(c1 == c2) return 0;
+                                                       
+                                                       return 
c1.isAssignableFrom(c2) ? 1 : c2.isAssignableFrom(c1) ? -1 : 0;
+                                               });
+               }
        }
 
        private static class TransactionContextKey {}

Added: 
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlRunningTest.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/TransactionControlRunningTest.java?rev=1732464&view=auto
==============================================================================
--- 
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlRunningTest.java
 (added)
+++ 
aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionControlRunningTest.java
 Fri Feb 26 12:18:47 2016
@@ -0,0 +1,451 @@
+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.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.osgi.service.transaction.control.TransactionStatus.COMMITTED;
+import static 
org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK;
+
+import java.net.BindException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
+
+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.Coordinator;
+import org.osgi.service.coordinator.Participant;
+import org.osgi.service.transaction.control.LocalResource;
+import org.osgi.service.transaction.control.ResourceProvider;
+import org.osgi.service.transaction.control.ScopedWorkException;
+import org.osgi.service.transaction.control.TransactionStatus;
+
+@RunWith(MockitoJUnitRunner.class)
+public class TransactionControlRunningTest {
+
+       @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);
+                       ArgumentCaptor<Participant> captor = 
ArgumentCaptor.forClass(Participant.class);
+                       Mockito.verify(coordination1, 
Mockito.atLeast(1)).addParticipant(captor.capture());
+                       
+                       for(Participant p : captor.getAllValues()) {
+                               p.ended(coordination1);
+                       }
+                       return null;
+               }).when(coordination1).end();
+               Mockito.doAnswer(i -> {
+                       Mockito.when(coordinator.peek()).thenReturn(null);
+                       ArgumentCaptor<Participant> captor = 
ArgumentCaptor.forClass(Participant.class);
+                       Mockito.verify(coordination1, 
Mockito.atLeast(1)).addParticipant(captor.capture());
+                       
+                       for(Participant p : captor.getAllValues()) {
+                               p.failed(coordination1);
+                       }
+                       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);
+                       ArgumentCaptor<Participant> captor = 
ArgumentCaptor.forClass(Participant.class);
+                       Mockito.verify(coordination2, 
Mockito.atLeast(1)).addParticipant(captor.capture());
+                       
+                       for(Participant p : captor.getAllValues()) {
+                               p.ended(coordination2);
+                       }
+                       return null;
+               }).when(coordination2).end();
+               Mockito.doAnswer(i -> {
+                       
Mockito.when(coordinator.peek()).thenReturn(coordination1);
+                       ArgumentCaptor<Participant> captor = 
ArgumentCaptor.forClass(Participant.class);
+                       Mockito.verify(coordination2, 
Mockito.atLeast(1)).addParticipant(captor.capture());
+                       
+                       for(Participant p : captor.getAllValues()) {
+                               p.failed(coordination2);
+                       }
+                       return null;
+               }).when(coordination2).fail(Mockito.any(Throwable.class));
+       }
+
+       @Test
+       public void testRequired() {
+
+               AtomicReference<TransactionStatus> finalStatus = new 
AtomicReference<>();
+               
+               txControl.required(() -> {
+
+                       assertTrue(txControl.activeTransaction());
+
+                       
txControl.getCurrentContext().postCompletion(finalStatus::set);
+                       return null;
+               });
+               
+               assertEquals(COMMITTED, finalStatus.get());
+
+       }
+
+       @Test
+       public void testRequiredMarkedRollback() {
+               
+               AtomicReference<TransactionStatus> finalStatus = new 
AtomicReference<>();
+               
+               txControl.required(() -> {
+                       
+                       assertTrue(txControl.activeTransaction());
+                       
+                       
txControl.getCurrentContext().postCompletion(finalStatus::set);
+                       
+                       txControl.setRollbackOnly();
+                       return null;
+               });
+               
+               assertEquals(ROLLED_BACK, finalStatus.get());
+       }
+
+       @Test
+       public void testRequiredUserException() {
+               
+               AtomicReference<TransactionStatus> finalStatus = new 
AtomicReference<>();
+               
+               Exception userEx = new Exception("Bang!");
+               
+               try {
+                       txControl.required(() -> {
+                               
+                               assertTrue(txControl.activeTransaction());
+                               
+                               
txControl.getCurrentContext().postCompletion(finalStatus::set);
+                               
+                               throw userEx;
+                       });
+                       fail("Should not be reached");
+               } catch (ScopedWorkException swe) {
+                       assertSame(userEx, swe.getCause());
+               }
+               
+               assertEquals(ROLLED_BACK, finalStatus.get());
+       }
+
+       @Test
+       public void testRequiredNoRollbackException() {
+               
+               AtomicReference<TransactionStatus> finalStatus = new 
AtomicReference<>();
+               
+               Exception userEx = new BindException("Bang!");
+               
+               try {
+                       txControl.build()
+                               .noRollbackFor(BindException.class)
+                               .required(() -> {
+                                       
+                                       
assertTrue(txControl.activeTransaction());
+                                       
+                                       
txControl.getCurrentContext().postCompletion(finalStatus::set);
+                                       
+                                       throw userEx;
+                               });
+                       fail("Should not be reached");
+               } catch (ScopedWorkException swe) {
+                       assertSame(userEx, swe.getCause());
+               }
+               
+               assertEquals(COMMITTED, finalStatus.get());
+       }
+
+       @Test
+       public void testTwoRequiredsNested() {
+
+               AtomicReference<TransactionStatus> finalStatusOuter = new 
AtomicReference<>();
+               AtomicReference<TransactionStatus> finalStatusInner = new 
AtomicReference<>();
+               
+               txControl.required(() -> {
+
+                       assertTrue(txControl.activeTransaction());
+                       
+                       Object key = 
txControl.getCurrentContext().getTransactionKey();
+
+                       
txControl.getCurrentContext().postCompletion(finalStatusOuter::set);
+                       
+                       txControl.requiresNew(() -> {
+                                       
assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey()));
+                                       
+                                       
txControl.getCurrentContext().postCompletion(finalStatusInner::set);
+                                       return null;
+                               });
+                       
+                       return null;
+               });
+               
+               assertEquals(COMMITTED, finalStatusOuter.get());
+               assertEquals(COMMITTED, finalStatusInner.get());
+
+       }
+
+       @Test
+       public void testTwoRequiredsNestedOuterMarkedRollback() {
+               
+               AtomicReference<TransactionStatus> finalStatusOuter = new 
AtomicReference<>();
+               AtomicReference<TransactionStatus> finalStatusInner = new 
AtomicReference<>();
+               
+               txControl.required(() -> {
+                       
+                       assertTrue(txControl.activeTransaction());
+                       
+                       Object key = 
txControl.getCurrentContext().getTransactionKey();
+                       
+                       
txControl.getCurrentContext().postCompletion(finalStatusOuter::set);
+                       
+                       txControl.setRollbackOnly();
+                       
+                       txControl.requiresNew(() -> {
+                               
assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey()));
+                               
+                               
txControl.getCurrentContext().postCompletion(finalStatusInner::set);
+                               return null;
+                       });
+                       
+                       return null;
+               });
+               
+               assertEquals(ROLLED_BACK, finalStatusOuter.get());
+               assertEquals(COMMITTED, finalStatusInner.get());
+               
+       }
+
+       @Test
+       public void testTwoRequiredsNestedInnerMarkedRollback() {
+               
+               AtomicReference<TransactionStatus> finalStatusOuter = new 
AtomicReference<>();
+               AtomicReference<TransactionStatus> finalStatusInner = new 
AtomicReference<>();
+               
+               txControl.required(() -> {
+                       
+                       assertTrue(txControl.activeTransaction());
+                       
+                       Object key = 
txControl.getCurrentContext().getTransactionKey();
+                       
+                       
txControl.getCurrentContext().postCompletion(finalStatusOuter::set);
+                       
+                       txControl.requiresNew(() -> {
+                               
assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey()));
+                               
+                               
txControl.getCurrentContext().postCompletion(finalStatusInner::set);
+
+                               txControl.setRollbackOnly();
+                               
+                               return null;
+                       });
+                       
+                       return null;
+               });
+               
+               assertEquals(COMMITTED, finalStatusOuter.get());
+               assertEquals(ROLLED_BACK, finalStatusInner.get());
+               
+       }
+
+       @Test
+       public void testTwoRequiredsNestedBothMarkedRollback() {
+               
+               AtomicReference<TransactionStatus> finalStatusOuter = new 
AtomicReference<>();
+               AtomicReference<TransactionStatus> finalStatusInner = new 
AtomicReference<>();
+               
+               txControl.required(() -> {
+                       
+                       assertTrue(txControl.activeTransaction());
+                       
+                       Object key = 
txControl.getCurrentContext().getTransactionKey();
+                       
+                       
txControl.getCurrentContext().postCompletion(finalStatusOuter::set);
+                       
+                       txControl.setRollbackOnly();
+
+                       txControl.requiresNew(() -> {
+                               
assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey()));
+                               
+                               
txControl.getCurrentContext().postCompletion(finalStatusInner::set);
+                               
+                               txControl.setRollbackOnly();
+                               
+                               return null;
+                       });
+                       
+                       return null;
+               });
+               
+               assertEquals(ROLLED_BACK, finalStatusOuter.get());
+               assertEquals(ROLLED_BACK, finalStatusInner.get());
+               
+       }
+       
+       @Test
+       public void testTwoRequiredsNestedOuterThrowsException() {
+               
+               AtomicReference<TransactionStatus> finalStatusOuter = new 
AtomicReference<>();
+               AtomicReference<TransactionStatus> finalStatusInner = new 
AtomicReference<>();
+               
+               Exception userEx = new Exception("Bang!");
+               
+               try {
+                       txControl.required(() -> {
+                               
+                               assertTrue(txControl.activeTransaction());
+                               
+                               Object key = 
txControl.getCurrentContext().getTransactionKey();
+                               
+                               
txControl.getCurrentContext().postCompletion(finalStatusOuter::set);
+                               
+                               txControl.setRollbackOnly();
+                               
+                               txControl.requiresNew(() -> {
+                                       
assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey()));
+                                       
+                                       
txControl.getCurrentContext().postCompletion(finalStatusInner::set);
+                                       return null;
+                               });
+                               
+                               throw userEx;
+                       });
+                       fail("Should not be reached");
+               } catch (ScopedWorkException swe) {
+                       assertSame(userEx, swe.getCause());
+               }
+               
+               assertEquals(ROLLED_BACK, finalStatusOuter.get());
+               assertEquals(COMMITTED, finalStatusInner.get());
+               
+       }
+       
+       @Test
+       public void testTwoRequiredsNestedInnerThrowsException() {
+               
+               AtomicReference<TransactionStatus> finalStatusOuter = new 
AtomicReference<>();
+               AtomicReference<TransactionStatus> finalStatusInner = new 
AtomicReference<>();
+               
+               Exception userEx = new Exception("Bang!");
+               
+               txControl.required(() -> {
+                       
+                       assertTrue(txControl.activeTransaction());
+                       
+                       Object key = 
txControl.getCurrentContext().getTransactionKey();
+                       
+                       
txControl.getCurrentContext().postCompletion(finalStatusOuter::set);
+                       
+                       try {
+                               txControl.requiresNew(() -> {
+                                       
assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey()));
+                                       
+                                       
txControl.getCurrentContext().postCompletion(finalStatusInner::set);
+                                       
+                                       txControl.setRollbackOnly();
+                                       
+                                       throw userEx;
+                               });
+                               fail("Should not be reached!");
+                       } catch (ScopedWorkException swe) {
+                               assertSame(userEx, swe.getCause());
+                       }
+                       return null;
+               });
+               
+               assertEquals(COMMITTED, finalStatusOuter.get());
+               assertEquals(ROLLED_BACK, finalStatusInner.get());
+               
+       }
+       
+       @Test
+       public void testTwoRequiredsNestedNoRollbackForInnerException() {
+               
+               AtomicReference<TransactionStatus> finalStatusOuter = new 
AtomicReference<>();
+               AtomicReference<TransactionStatus> finalStatusInner = new 
AtomicReference<>();
+               
+               Exception userEx = new BindException("Bang!");
+               
+               try {
+                       txControl.required(() -> {
+                               
+                               assertTrue(txControl.activeTransaction());
+                               
+                               Object key = 
txControl.getCurrentContext().getTransactionKey();
+                               
+                               
txControl.getCurrentContext().postCompletion(finalStatusOuter::set);
+                               
+                               try {
+                                       txControl.build()
+                                               
.noRollbackFor(BindException.class)
+                                               .requiresNew(() -> {
+                                                               
assertFalse(key.equals(txControl.getCurrentContext().getTransactionKey()));
+                                                               
+                                                               
txControl.getCurrentContext().postCompletion(finalStatusInner::set);
+                                                               
+                                                               throw userEx;
+                                                       });
+                                       fail("Should not be reached!");
+                               } catch (ScopedWorkException swe) {
+                                       throw swe.as(BindException.class);
+                               }
+                               
+                               return null;
+                       });
+                       fail("Should not be reached!");
+               } catch (ScopedWorkException swe) {
+                       assertSame(userEx, swe.getCause());
+               }
+               
+               assertEquals(ROLLED_BACK, finalStatusOuter.get());
+               assertEquals(COMMITTED, finalStatusInner.get());
+               
+       }
+       
+}


Reply via email to