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/26 13:18:48 UTC
svn commit: r1732464 - in /aries/trunk/tx-control:
tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/
tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/
tx-control-service-local/src/test/java/org/apa...
Author: timothyjward
Date: 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());
+
+ }
+
+}