[tx-control] Add support for read-only transactions

git-svn-id: https://svn.apache.org/repos/asf/aries/trunk/tx-control@1740209 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionBuilder.java b/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionBuilder.java
index 24d2fb6..df35e70 100644
--- a/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionBuilder.java
+++ b/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionBuilder.java
@@ -131,4 +131,23 @@
 		noRollbackFor.addAll(Arrays.asList(throwables));
 		return this;
 	}
+	
+	/**
+	 * Indicate to the Transaction Control service that this transaction
+	 * will be read-only. This hint may be used by the Transaction Control
+	 * service and associated resources to optimise the transaction.
+	 * 
+	 * <p>
+	 * Note that this method is for optimisation purposes only. The TransactionControl
+	 * service is free to ignore the call if it does not offer read-only optimisation. 
+	 * 
+	 * <p>
+	 * If a transaction is marked read-only and then the scoped work performs a write
+	 * operation on a resource then this is a programming error. The resource is
+	 * free to raise an exception when the write is attempted, or to permit the write 
+	 * operation. As a result the transaction may commit successfully, or may rollback.
+	 * 
+	 * @return this builder
+	 */
+	public abstract TransactionBuilder readOnly();
 }
diff --git a/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java b/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java
index e511fb5..6a96ec4 100644
--- a/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java
+++ b/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java
@@ -99,6 +99,14 @@
 	boolean supportsLocal();
 
 	/**
+	 * @return true if the TransactionContext supports read-only optimisations
+	 * <em>and</em> the transaction was marked read only. In particular it is
+	 * legal for this method to return false even if the transaction was marked
+	 * read only by the initiating client.
+	 */
+	boolean isReadOnly();
+
+	/**
 	 * Register an XA resource with the current transaction
 	 * 
 	 * @param resource
diff --git a/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/AbstractTransactionControlImpl.java b/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/AbstractTransactionControlImpl.java
index 7454c8a..7a1d2b4 100644
--- a/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/AbstractTransactionControlImpl.java
+++ b/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/AbstractTransactionControlImpl.java
@@ -24,6 +24,14 @@
 
 	private final class TransactionBuilderImpl extends TransactionBuilder {
 
+		private boolean readOnly = false;
+		
+		@Override
+		public TransactionBuilder readOnly() {
+			readOnly = true;
+			return this;
+		}
+
 		private void checkExceptions() {
 			List<Class<? extends Throwable>> duplicates = rollbackFor.stream()
 					.filter(noRollbackFor::contains)
@@ -56,10 +64,12 @@
 					currentCoord = coordinator.begin(
 							"Resource-Local-Transaction.REQUIRED", 30000);
 					endCoordination = true;
-					currentTran = startTransaction(currentCoord);
+					currentTran = startTransaction(currentCoord, readOnly);
 					endTransaction = true;
 					currentCoord.getVariables().put(TransactionContextKey.class,
 							currentTran);
+				} else if (currentTran.isReadOnly() && !readOnly){
+					throw new TransactionException("A read only transaction is currently active, and cannot be upgraded to a writeable transaction");
 				}
 			} catch (RuntimeException re) {
 				if(endTransaction) {
@@ -85,7 +95,7 @@
 				currentCoord = coordinator.begin(
 						"Resource-Local-Transaction.REQUIRES_NEW", 30000);
 
-				currentTran = startTransaction(currentCoord);
+				currentTran = startTransaction(currentCoord, readOnly);
 				currentCoord.getVariables().put(TransactionContextKey.class,
 						currentTran);
 			} catch (RuntimeException re) {
@@ -271,7 +281,7 @@
 		coordinator = c;
 	}
 
-	protected abstract AbstractTransactionContextImpl startTransaction(Coordination currentCoord);
+	protected abstract AbstractTransactionContextImpl startTransaction(Coordination currentCoord, boolean readOnly);
 
 	@Override
 	public TransactionBuilder build() {
diff --git a/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java b/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java
index 8ac0cfb..73c0a83 100644
--- a/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java
+++ b/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java
@@ -83,6 +83,11 @@
 	}
 
 	@Override
+	public boolean isReadOnly() {
+		return false;
+	}
+
+	@Override
 	protected boolean isAlive() {
 		return !finished.get();
 	}
diff --git a/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java b/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java
index baab431..4cf8ee1 100644
--- a/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java
+++ b/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java
@@ -24,10 +24,14 @@
 
 	final List<LocalResource> resources = new ArrayList<>();
 
+	private final boolean readOnly;
+
 	private AtomicReference<TransactionStatus> tranStatus = new AtomicReference<>(ACTIVE);
 
-	public TransactionContextImpl(Coordination coordination) {
+
+	public TransactionContextImpl(Coordination coordination, boolean readOnly) {
 		super(coordination);
+		this.readOnly = readOnly;
 	}
 
 	@Override
@@ -134,6 +138,11 @@
 	}
 
 	@Override
+	public boolean isReadOnly() {
+		return readOnly;
+	}
+
+	@Override
 	protected boolean isAlive() {
 		TransactionStatus status = tranStatus.get();
 		return status != COMMITTED && status != ROLLED_BACK;
diff --git a/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java b/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
index 7632abf..1654899 100644
--- a/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
+++ b/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionControlImpl.java
@@ -12,8 +12,8 @@
 	}
 
 	@Override
-	protected AbstractTransactionContextImpl startTransaction(Coordination currentCoord) {
-		return new TransactionContextImpl(currentCoord);
+	protected AbstractTransactionContextImpl startTransaction(Coordination currentCoord, boolean readOnly) {
+		return new TransactionContextImpl(currentCoord, readOnly);
 	}
 	
 }
diff --git a/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java b/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java
index b436f5a..c66cd72 100644
--- a/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java
+++ b/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java
@@ -48,7 +48,7 @@
 	
 	@Before
 	public void setUp() {
-		ctx = new TransactionContextImpl(coordination);
+		ctx = new TransactionContextImpl(coordination, false);
 		variables = new HashMap<>();
 		Mockito.when(coordination.getVariables()).thenReturn(variables);
 	}
@@ -65,6 +65,17 @@
 	}
 
 	@Test
+	public void testisReadOnlyFalse() {
+		assertFalse(ctx.isReadOnly());
+	}
+
+	@Test
+	public void testisReadOnlyTrue() {
+		ctx = new TransactionContextImpl(coordination, true);
+		assertTrue(ctx.isReadOnly());
+	}
+
+	@Test
 	public void testTransactionKey() {
 		Mockito.when(coordination.getId()).thenReturn(42L);
 		
diff --git a/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java b/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java
index 827d462..990eb2c 100644
--- a/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java
+++ b/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java
@@ -49,9 +49,13 @@
 	
 	private final Object key;
 
-	public TransactionContextImpl(GeronimoTransactionManager transactionManager, Coordination coordination) {
+	private final boolean readOnly;
+
+	public TransactionContextImpl(GeronimoTransactionManager transactionManager, Coordination coordination, 
+			boolean readOnly) {
 		super(coordination);
 		this.transactionManager = transactionManager;
+		this.readOnly = readOnly;
 		Transaction tmp = null;
 		try {
 			tmp = transactionManager.suspend();
@@ -222,6 +226,11 @@
 	}
 
 	@Override
+	public boolean isReadOnly() {
+		return readOnly;
+	}
+
+	@Override
 	protected boolean isAlive() {
 		TransactionStatus status = getTransactionStatus();
 		return status != COMMITTED && status != ROLLED_BACK;
diff --git a/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java b/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java
index 4db6950..6785e4d 100644
--- a/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java
+++ b/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java
@@ -16,8 +16,8 @@
 	}
 
 	@Override
-	protected AbstractTransactionContextImpl startTransaction(Coordination currentCoord) {
-		return new TransactionContextImpl(transactionManager, currentCoord);
+	protected AbstractTransactionContextImpl startTransaction(Coordination currentCoord, boolean readOnly) {
+		return new TransactionContextImpl(transactionManager, currentCoord, readOnly);
 	}
 	
 }
diff --git a/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java b/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java
index 6e3a4c2..75a041f 100644
--- a/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java
+++ b/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java
@@ -53,7 +53,7 @@
 	
 	@Before
 	public void setUp() throws XAException {
-		ctx = new TransactionContextImpl(new GeronimoTransactionManager(), coordination);
+		ctx = new TransactionContextImpl(new GeronimoTransactionManager(), coordination, false);
 		variables = new HashMap<>();
 		Mockito.when(coordination.getVariables()).thenReturn(variables);
 	}
@@ -68,6 +68,18 @@
 		ctx.setRollbackOnly();
 		assertTrue(ctx.getRollbackOnly());
 	}
+	
+	@Test
+	public void testisReadOnlyFalse() {
+		assertFalse(ctx.isReadOnly());
+	}
+
+	@Test
+	public void testisReadOnlyTrue() throws XAException {
+		ctx = new TransactionContextImpl(new GeronimoTransactionManager(), coordination, true);
+		assertTrue(ctx.isReadOnly());
+	}
+
 
 	@Test
 	public void testTransactionKey() {