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