Author: timothyjward Date: Thu Jun 30 17:55:48 2016 New Revision: 1750850 URL: http://svn.apache.org/viewvc?rev=1750850&view=rev Log: [tx-control] Add support for resource recovery when using XA
Added: aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/recovery/RecoverableXAResource.java aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/NamedXAResourceImpl.java aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/transaction/ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/transaction/manager/ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/transaction/manager/RecoveryWorkAroundTransactionManager.java Removed: aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/recovery/TransactionRecovery.java Modified: aries/trunk/tx-control/pom.xml aries/trunk/tx-control/tx-control-api/pom.xml aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/XATransactionTest.java aries/trunk/tx-control/tx-control-jpa-itests/src/test/java/org/apache/aries/tx/control/itests/XAJPATransactionTest.java aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnection.java aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/test/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnectionTest.java aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/eclipse/impl/EclipseTxControlPlatform.java aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/openjpa/impl/OpenJPATxControlPlatform.java aries/trunk/tx-control/tx-control-provider-jpa-xa/src/test/java/org/apache/aries/tx/control/jpa/xa/impl/XATxContextBindingEntityManagerTest.java aries/trunk/tx-control/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java aries/trunk/tx-control/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.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/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java aries/trunk/tx-control/tx-control-service-xa/bnd.bnd aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/Config.java aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLogTest.java Modified: aries/trunk/tx-control/pom.xml URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/pom.xml?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/pom.xml (original) +++ aries/trunk/tx-control/pom.xml Thu Jun 30 17:55:48 2016 @@ -104,7 +104,7 @@ <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> - <version>1.6.6</version> + <version>1.7.0</version> </dependency> <dependency> <groupId>org.apache.geronimo.specs</groupId> Modified: aries/trunk/tx-control/tx-control-api/pom.xml URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-api/pom.xml?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-api/pom.xml (original) +++ aries/trunk/tx-control/tx-control-api/pom.xml Thu Jun 30 17:55:48 2016 @@ -56,4 +56,5 @@ </plugin> </plugins> </build> + </project> \ No newline at end of file Modified: aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java (original) +++ aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/TransactionContext.java Thu Jun 30 17:55:48 2016 @@ -19,6 +19,8 @@ import java.util.function.Consumer; import javax.transaction.xa.XAResource; +import org.osgi.service.transaction.control.recovery.RecoverableXAResource; + /** * A transaction context defines the current transaction, and allows resources * to register information and/or synchronisations @@ -110,10 +112,14 @@ public interface TransactionContext { * Register an XA resource with the current transaction * * @param resource + * @param name The resource name used for recovery, may be <code>null</code> + * if this resource is not recoverable. If a name is passed then + * a corresponding {@link RecoverableXAResource} must be registered + * in the service registry * @throws IllegalStateException if no transaction is active, or the current * transaction is not XA capable */ - void registerXAResource(XAResource resource) throws IllegalStateException; + void registerXAResource(XAResource resource, String name) throws IllegalStateException; /** * Register an XA resource with the current transaction Added: aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/recovery/RecoverableXAResource.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/recovery/RecoverableXAResource.java?rev=1750850&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/recovery/RecoverableXAResource.java (added) +++ aries/trunk/tx-control/tx-control-api/src/main/java/org/osgi/service/transaction/control/recovery/RecoverableXAResource.java Thu Jun 30 17:55:48 2016 @@ -0,0 +1,75 @@ +/* + * Copyright (c) OSGi Alliance (2016). All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.osgi.service.transaction.control.recovery; + +import javax.transaction.xa.XAResource; + +import org.osgi.service.transaction.control.ResourceProvider; +import org.osgi.service.transaction.control.TransactionContext; + +/** + * A {@link RecoverableXAResource} service may be provided by a + * {@link ResourceProvider} if they are able to support XA recovery + * operations. + * + * There are two main sorts of recovery: + * + * <ul> + * <li>Recovery after a remote failure, where the local transaction + * manager runs throughout</li> + * <li>Recovery after a local failure, where the transaction manager + * replays in-doubt transactions from its log</li> + * </ul> + * + * This service is used in both of these cases. + * + * The identifier returned by {@link #getId()} provides a persistent name + * that can be used to correlate usage of the resource both before and after + * failure. This identifier must also be passed to + * {@link TransactionContext#registerXAResource(XAResource, String)} each time + * the recoverable resource is used. + * + */ +public interface RecoverableXAResource { + + /** + * Get the id of this resource. This should be unique, and persist between restarts + * @return an identifier, never <code>null</code> + */ + String getId(); + + /** + * Get a new, valid XAResource that can be used in recovery + * + * This XAResource will be returned later using the + * {@link #releaseXAResource(XAResource)} method + * + * @return a valid, connected, XAResource + * + * @throws Exception If it is not possible to acquire a valid + * XAResource at the current time, for example if the database + * is temporarily unavailable. + */ + XAResource getXAResource() throws Exception; + + /** + * Release the XAResource that has been used for recovery + * + * @param xaRes An {@link XAResource} previously returned + * by {@link #getXAResource()} + */ + void releaseXAResource(XAResource xaRes); +} Modified: aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/XATransactionTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/XATransactionTest.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/XATransactionTest.java (original) +++ aries/trunk/tx-control/tx-control-itests/src/test/java/org/apache/aries/tx/control/itests/XATransactionTest.java Thu Jun 30 17:55:48 2016 @@ -182,7 +182,7 @@ public class XATransactionTest { return null; }); - txControl.getCurrentContext().registerXAResource(new PoisonResource()); + txControl.getCurrentContext().registerXAResource(new PoisonResource(), null); return null; }); Modified: aries/trunk/tx-control/tx-control-jpa-itests/src/test/java/org/apache/aries/tx/control/itests/XAJPATransactionTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-jpa-itests/src/test/java/org/apache/aries/tx/control/itests/XAJPATransactionTest.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-jpa-itests/src/test/java/org/apache/aries/tx/control/itests/XAJPATransactionTest.java (original) +++ aries/trunk/tx-control/tx-control-jpa-itests/src/test/java/org/apache/aries/tx/control/itests/XAJPATransactionTest.java Thu Jun 30 17:55:48 2016 @@ -325,7 +325,7 @@ public abstract class XAJPATransactionTe return null; }); - txControl.getCurrentContext().registerXAResource(new PoisonResource()); + txControl.getCurrentContext().registerXAResource(new PoisonResource(), null); return null; }); Modified: aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnection.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnection.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnection.java (original) +++ aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnection.java Thu Jun 30 17:55:48 2016 @@ -79,7 +79,7 @@ public class XAEnabledTxContextBindingCo } else if (txContext.supportsXA() && xaEnabled) { toClose = dataSource.getConnection(); toReturn = new TxConnectionWrapper(toClose); - txContext.registerXAResource(getXAResource(toClose)); + txContext.registerXAResource(getXAResource(toClose), null); } else if (txContext.supportsLocal() && localEnabled) { toClose = dataSource.getConnection(); toReturn = new TxConnectionWrapper(toClose); Modified: aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/test/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnectionTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/test/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnectionTest.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/test/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnectionTest.java (original) +++ aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/test/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnectionTest.java Thu Jun 30 17:55:48 2016 @@ -211,7 +211,7 @@ public class XAEnabledTxContextBindingCo Mockito.verify(rawConnection, times(2)).isValid(500); - Mockito.verify(context).registerXAResource(xaResource); + Mockito.verify(context).registerXAResource(xaResource, null); Mockito.verify(context).postCompletion(Mockito.any()); @@ -226,7 +226,7 @@ public class XAEnabledTxContextBindingCo xaConn.isValid(500); Mockito.verify(rawConnection, times(2)).isValid(500); - Mockito.verify(context).registerXAResource(xaResource); + Mockito.verify(context).registerXAResource(xaResource, null); Mockito.verify(context).postCompletion(Mockito.any()); Modified: aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/eclipse/impl/EclipseTxControlPlatform.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/eclipse/impl/EclipseTxControlPlatform.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/eclipse/impl/EclipseTxControlPlatform.java (original) +++ aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/eclipse/impl/EclipseTxControlPlatform.java Thu Jun 30 17:55:48 2016 @@ -204,7 +204,7 @@ public class EclipseTxControlPlatform ex @Override public boolean enlistResource(XAResource xaRes) throws IllegalStateException, RollbackException, SystemException { - context.registerXAResource(xaRes); + context.registerXAResource(xaRes, null); return true; } Modified: aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java (original) +++ aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/impl/JPAEntityManagerProviderFactoryImpl.java Thu Jun 30 17:55:48 2016 @@ -210,7 +210,7 @@ public class JPAEntityManagerProviderFac toReturn = new ScopedConnectionWrapper(toClose); } else if (txContext.supportsXA()) { toReturn = new TxConnectionWrapper(toClose); - txContext.registerXAResource(getXAResource(toClose)); + txContext.registerXAResource(getXAResource(toClose), null); } else { throw new TransactionException( "There is a transaction active, but it does not support XA participants"); Modified: aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/openjpa/impl/OpenJPATxControlPlatform.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/openjpa/impl/OpenJPATxControlPlatform.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/openjpa/impl/OpenJPATxControlPlatform.java (original) +++ aries/trunk/tx-control/tx-control-provider-jpa-xa/src/main/java/org/apache/aries/tx/control/jpa/xa/openjpa/impl/OpenJPATxControlPlatform.java Thu Jun 30 17:55:48 2016 @@ -132,7 +132,7 @@ public class OpenJPATxControlPlatform im @Override public boolean enlistResource(XAResource xaRes) throws IllegalStateException, RollbackException, SystemException { - txControl.getCurrentContext().registerXAResource(xaRes); + txControl.getCurrentContext().registerXAResource(xaRes, null); return true; } Modified: aries/trunk/tx-control/tx-control-provider-jpa-xa/src/test/java/org/apache/aries/tx/control/jpa/xa/impl/XATxContextBindingEntityManagerTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-provider-jpa-xa/src/test/java/org/apache/aries/tx/control/jpa/xa/impl/XATxContextBindingEntityManagerTest.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jpa-xa/src/test/java/org/apache/aries/tx/control/jpa/xa/impl/XATxContextBindingEntityManagerTest.java (original) +++ aries/trunk/tx-control/tx-control-provider-jpa-xa/src/test/java/org/apache/aries/tx/control/jpa/xa/impl/XATxContextBindingEntityManagerTest.java Thu Jun 30 17:55:48 2016 @@ -108,7 +108,7 @@ public class XATxContextBindingEntityMan Mockito.verify(rawEm, times(2)).isOpen(); Mockito.verify(rawEm, times(0)).getTransaction(); - Mockito.verify(context, times(0)).registerXAResource(Mockito.any()); + Mockito.verify(context, times(0)).registerXAResource(Mockito.any(), Mockito.anyString()); Mockito.verify(context).postCompletion(Mockito.any()); } Modified: aries/trunk/tx-control/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java (original) +++ aries/trunk/tx-control/tx-control-service-common/src/main/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextImpl.java Thu Jun 30 17:55:48 2016 @@ -80,7 +80,7 @@ public class NoTransactionContextImpl ex } @Override - public void registerXAResource(XAResource resource) { + public void registerXAResource(XAResource resource, String recoveryName) { throw new IllegalStateException("No transaction is active"); } Modified: aries/trunk/tx-control/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java (original) +++ aries/trunk/tx-control/tx-control-service-common/src/test/java/org/apache/aries/tx/control/service/common/impl/NoTransactionContextTest.java Thu Jun 30 17:55:48 2016 @@ -87,7 +87,7 @@ public class NoTransactionContextTest { @Test(expected=IllegalStateException.class) public void testXAResourceRegistration() { - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); } @Test Modified: 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=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java (original) +++ aries/trunk/tx-control/tx-control-service-local/src/main/java/org/apache/aries/tx/control/service/local/impl/TransactionContextImpl.java Thu Jun 30 17:55:48 2016 @@ -133,7 +133,7 @@ public class TransactionContextImpl exte } @Override - public void registerXAResource(XAResource resource) { + public void registerXAResource(XAResource resource, String name) { throw new IllegalStateException("Not an XA manager"); } Modified: 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=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java (original) +++ aries/trunk/tx-control/tx-control-service-local/src/test/java/org/apache/aries/tx/control/service/local/impl/TransactionContextTest.java Thu Jun 30 17:55:48 2016 @@ -110,7 +110,7 @@ public class TransactionContextTest { @Test(expected=IllegalStateException.class) public void testXAResourceRegistration() { - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); } @Test Modified: aries/trunk/tx-control/tx-control-service-xa/bnd.bnd URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/bnd.bnd?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/bnd.bnd (original) +++ aries/trunk/tx-control/tx-control-service-xa/bnd.bnd Thu Jun 30 17:55:48 2016 @@ -3,7 +3,8 @@ Bundle-Activator: org.apache.aries.tx.co # Export the API so that this is an easily deployable bundle -Export-Package: org.osgi.service.transaction.control +Export-Package: org.osgi.service.transaction.control,\ + org.osgi.service.transaction.control.recovery # This bundle repackages code from a variety of places to make the Modified: aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/Config.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/Config.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/Config.java (original) +++ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/Config.java Thu Jun 30 17:55:48 2016 @@ -11,8 +11,8 @@ import org.osgi.service.metatype.annotat */ @ObjectClassDefinition(pid=Activator.PID, description="Apache Aries Transaction Control Service (XA)") @interface Config { - @AttributeDefinition(name="Enable recovery", required=false, description="Enable recovery") - boolean recovery_enabled() default false; + @AttributeDefinition(name="Enable recovery logging", required=false, description="Enable recovery logging") + boolean recovery_log_enabled() default false; @AttributeDefinition(name="Recovery Log storage folder", required=false, description="Transaction Recovery Log directory") boolean recovery_log_dir(); Added: aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/NamedXAResourceImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/NamedXAResourceImpl.java?rev=1750850&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/NamedXAResourceImpl.java (added) +++ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/NamedXAResourceImpl.java Thu Jun 30 17:55:48 2016 @@ -0,0 +1,143 @@ +package org.apache.aries.tx.control.service.xa.impl; + +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +import org.apache.geronimo.transaction.manager.NamedXAResource; +import org.apache.geronimo.transaction.manager.RecoveryWorkAroundTransactionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NamedXAResourceImpl implements NamedXAResource, AutoCloseable { + + final Logger logger = LoggerFactory.getLogger(NamedXAResourceImpl.class); + + final String name; + final XAResource xaResource; + final RecoveryWorkAroundTransactionManager transactionManager; + final boolean original; + + boolean closed; + + public NamedXAResourceImpl(String name, XAResource xaResource, + RecoveryWorkAroundTransactionManager transactionManager, boolean original) { + this.name = name; + this.xaResource = xaResource; + this.transactionManager = transactionManager; + this.original = original; + } + + @Override + public String getName() { + return name; + } + + @Override + public void close() { + closed = true; + } + + private interface XAAction { + void perform() throws XAException; + } + + private interface XAReturnAction<T> { + T perform() throws XAException; + } + + private void safeCall(XAAction action) throws XAException { + checkOpen(); + + try { + action.perform(); + } catch (Exception e) { + throw handleException(e); + } + } + + private <T> T safeCall(XAReturnAction<T> action) throws XAException { + checkOpen(); + try { + return action.perform(); + } catch (Exception e) { + throw handleException(e); + } + } + + private void checkOpen() throws XAException { + if(closed) { + XAException xaException = new XAException("This instance of the resource named " + name + " is no longer available"); + xaException.errorCode = XAException.XAER_RMFAIL; + throw xaException; + } + } + + private XAException handleException(Exception e) throws XAException { + if(e instanceof XAException) { + XAException xae = (XAException) e; + if(xae.errorCode == 0) { + if(original) { + // We are the originally enlisted resource, and will play some tricks to attempt recovery + if(transactionManager.getNamedResource(name) == null) { + logger.error("The XA resource named {} threw an XAException but did not set the error code. There is also no RecoverableXAResource available with the name {}. It is not possible to recover from this situation and so the transaction will have to be resolved by an operator.", name, name, xae); + xae.errorCode = XAException.XAER_RMERR; + } else { + logger.warn("The XA resource named {} threw an XAException but did not set the error code. Changing it to be an \"RM_FAIL\" to permit recovery attempts", name, xae); + xae.errorCode = XAException.XAER_RMFAIL; + } + } else { + logger.warn("The XA resource named {} threw an XAException but did not set the error code. Recovery has already been attempted for this resource and it has not been possible to recover from this situation. The transaction will have to be resolved by an operator.", name, xae); + xae.errorCode = XAException.XAER_RMERR; + } + } + return xae; + } else { + logger.warn("The recoverable XA resource named {} threw an Exception which is not permitted by the interface. Changing it to be a \"Resource Manager Error\" XAException which prevents recovery", name, e); + XAException xaException = new XAException(XAException.XAER_RMERR); + xaException.initCause(e); + return xaException; + } + } + + public void commit(Xid xid, boolean onePhase) throws XAException { + safeCall(() -> xaResource.commit(xid, onePhase)); + } + + public void end(Xid xid, int flags) throws XAException { + safeCall(() -> xaResource.end(xid, flags)); + } + + public void forget(Xid xid) throws XAException { + safeCall(() -> xaResource.forget(xid)); + } + + public int getTransactionTimeout() throws XAException { + return safeCall(() -> xaResource.getTransactionTimeout()); + } + + public boolean isSameRM(XAResource xares) throws XAException { + return safeCall(() -> xaResource.isSameRM(xares)); + } + + public int prepare(Xid xid) throws XAException { + return safeCall(() -> xaResource.prepare(xid)); + } + + public Xid[] recover(int flag) throws XAException { + return safeCall(() -> xaResource.recover(flag)); + } + + public void rollback(Xid xid) throws XAException { + safeCall(() -> xaResource.rollback(xid)); + } + + public boolean setTransactionTimeout(int seconds) throws XAException { + return safeCall(() -> xaResource.setTransactionTimeout(seconds)); + } + + public void start(Xid xid, int flags) throws XAException { + safeCall(() -> xaResource.start(xid, flags)); + } + +} Modified: aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java (original) +++ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextImpl.java Thu Jun 30 17:55:48 2016 @@ -47,7 +47,7 @@ import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.apache.aries.tx.control.service.common.impl.AbstractTransactionContextImpl; -import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.apache.geronimo.transaction.manager.RecoveryWorkAroundTransactionManager; import org.osgi.service.transaction.control.LocalResource; import org.osgi.service.transaction.control.TransactionContext; import org.osgi.service.transaction.control.TransactionException; @@ -63,7 +63,7 @@ public class TransactionContextImpl exte private final AtomicReference<TransactionStatus> completionState = new AtomicReference<>(); - private final GeronimoTransactionManager transactionManager; + private final RecoveryWorkAroundTransactionManager transactionManager; private final Object key; @@ -71,7 +71,7 @@ public class TransactionContextImpl exte private LocalResourceSupport localResourceSupport; - public TransactionContextImpl(GeronimoTransactionManager transactionManager, + public TransactionContextImpl(RecoveryWorkAroundTransactionManager transactionManager, boolean readOnly, LocalResourceSupport localResourceSupport) { this.transactionManager = transactionManager; this.readOnly = readOnly; @@ -214,13 +214,19 @@ public class TransactionContextImpl exte } @Override - public void registerXAResource(XAResource resource) { + public void registerXAResource(XAResource resource, String name) { TransactionStatus status = getTransactionStatus(); if (status.compareTo(MARKED_ROLLBACK) > 0) { throw new IllegalStateException("The current transaction is in state " + status); } try { - currentTransaction.enlistResource(resource); + if(name == null) { + currentTransaction.enlistResource(resource); + } else { + NamedXAResourceImpl res = new NamedXAResourceImpl(name, resource, transactionManager, true); + postCompletion(x -> res.close()); + currentTransaction.enlistResource(res); + } } catch (Exception e) { throw new TransactionException("The transaction was unable to enlist a resource", e); } @@ -288,52 +294,22 @@ public class TransactionContextImpl exte } } } - - TxListener listener; - boolean manualCallListener; - if(!preCompletion.isEmpty() || !postCompletion.isEmpty()) { - listener = new TxListener(); - try { - transactionManager.registerInterposedSynchronization(listener); - manualCallListener = false; - } catch (Exception e) { - manualCallListener = true; - recordFailure(e); - safeSetRollbackOnly(); - } - } else { - listener = null; - manualCallListener = false; - } - try { - int status; + TxListener listener = new TxListener(); try { + transactionManager.registerInterposedSynchronization(listener); + if (getRollbackOnly()) { // GERONIMO-4449 says that we get no beforeCompletion // callback for rollback :( - if(listener != null) { - listener.beforeCompletion(); - } + listener.beforeCompletion(); transactionManager.rollback(); - status = Status.STATUS_ROLLEDBACK; - completionState.set(ROLLED_BACK); } else { - if(manualCallListener) { - listener.beforeCompletion(); - } transactionManager.commit(); - status = Status.STATUS_COMMITTED; - completionState.set(COMMITTED); } } catch (Exception e) { recordFailure(e); - status = Status.STATUS_ROLLEDBACK; - completionState.set(ROLLED_BACK); - } - if(manualCallListener) { - listener.afterCompletion(status); } } finally { try { @@ -465,7 +441,7 @@ public class TransactionContextImpl exte } private class TxListener implements Synchronization { - + @Override public void beforeCompletion() { TransactionContextImpl.this.beforeCompletion(() -> safeSetRollbackOnly()); @@ -473,8 +449,9 @@ public class TransactionContextImpl exte @Override public void afterCompletion(int status) { - TransactionContextImpl.this.afterCompletion(status == Status.STATUS_COMMITTED ? COMMITTED : ROLLED_BACK); + TransactionStatus ts = status == Status.STATUS_COMMITTED ? COMMITTED : ROLLED_BACK; + completionState.set(ts); + TransactionContextImpl.this.afterCompletion(ts); } - } } Modified: aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java (original) +++ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/aries/tx/control/service/xa/impl/TransactionControlImpl.java Thu Jun 30 17:55:48 2016 @@ -28,23 +28,37 @@ import java.util.HashMap; import java.util.Hashtable; import java.util.Map; +import javax.resource.spi.IllegalStateException; +import javax.transaction.SystemException; +import javax.transaction.xa.XAResource; + import org.apache.aries.tx.control.service.common.impl.AbstractTransactionContextImpl; import org.apache.aries.tx.control.service.common.impl.AbstractTransactionControlImpl; import org.apache.aries.tx.control.service.xa.impl.Activator.ChangeType; import org.apache.geronimo.transaction.log.HOWLLog; -import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.apache.geronimo.transaction.manager.NamedXAResource; +import org.apache.geronimo.transaction.manager.NamedXAResourceFactory; +import org.apache.geronimo.transaction.manager.RecoveryWorkAroundTransactionManager; import org.apache.geronimo.transaction.manager.XidFactory; import org.apache.geronimo.transaction.manager.XidFactoryImpl; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.service.transaction.control.recovery.RecoverableXAResource; +import org.osgi.util.tracker.ServiceTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class TransactionControlImpl extends AbstractTransactionControlImpl { + private static final Logger logger = LoggerFactory.getLogger(TransactionControlImpl.class); + private Map<String, Object> config; private final XidFactory xidFactory; private final HOWLLog log; - private final GeronimoTransactionManager transactionManager; + private final RecoveryWorkAroundTransactionManager transactionManager; private final LocalResourceSupport localResourceSupport; + private final ServiceTracker<RecoverableXAResource, RecoverableXAResource> recoverableResources; public TransactionControlImpl(BundleContext ctx, Map<String, Object> config) throws Exception { @@ -58,8 +72,72 @@ public class TransactionControlImpl exte log.doStart(); } - transactionManager = new GeronimoTransactionManager(getTimeout(), + transactionManager = new RecoveryWorkAroundTransactionManager(getTimeout(), xidFactory, log); + + if(log != null) { + recoverableResources = + new ServiceTracker<RecoverableXAResource, RecoverableXAResource>( + ctx, RecoverableXAResource.class, null) { + + @Override + public RecoverableXAResource addingService( + ServiceReference<RecoverableXAResource> reference) { + RecoverableXAResource resource = super.addingService(reference); + + if(resource.getId() == null) { + logger.warn("The RecoverableXAResource service with id {} does not have a name and will be ignored", + reference.getProperty("service.id")); + return null; + } + + if(log == null) { + logger.warn("A RecoverableXAResource with id {} has been registered, but recovery logging is disabled for this Transaction Control service. No recovery will be availble in the event of a Transaction Manager failure.", resource.getId()); + } + + transactionManager.registerNamedXAResourceFactory(new NamedXAResourceFactory() { + + @Override + public void returnNamedXAResource(NamedXAResource namedXAResource) { + resource.releaseXAResource(((NamedXAResourceImpl)namedXAResource).xaResource); + } + + @Override + public NamedXAResource getNamedXAResource() throws SystemException { + try { + XAResource xaResource = resource.getXAResource(); + if(xaResource == null) { + throw new IllegalStateException("The recoverable resource " + resource.getId() + + " is currently unavailable"); + } + return new NamedXAResourceImpl(resource.getId(), xaResource, + transactionManager, false); + } catch (Exception e) { + throw new SystemException("Unable to get recoverable resource " + + resource.getId() + ": " + e.getMessage()); + } + } + + @Override + public String getName() { + return resource.getId(); + } + }); + + return resource; + } + + @Override + public void removedService(ServiceReference<RecoverableXAResource> reference, + RecoverableXAResource service) { + transactionManager.unregisterNamedXAResourceFactory(service.getId()); + } + + }; + recoverableResources.open(); + } else { + recoverableResources = null; + } } catch (Exception e) { destroy(); throw e; @@ -73,7 +151,7 @@ public class TransactionControlImpl exte } private HOWLLog getLog(BundleContext ctx) throws Exception { - Object recovery = config.getOrDefault("recovery.enabled", false); + Object recovery = config.getOrDefault("recovery.log.enabled", false); if (recovery instanceof Boolean ? (Boolean) recovery : Boolean.valueOf(recovery.toString())) { String logFileExt = "log"; @@ -122,6 +200,9 @@ public class TransactionControlImpl exte } public void destroy() { + if(recoverableResources != null) { + recoverableResources.close(); + } if(log != null) { try { log.doStop(); @@ -172,7 +253,7 @@ public class TransactionControlImpl exte Map<String, Object> filtered = new HashMap<>(); copy(raw, filtered, "transaction.timeout"); - copy(raw, filtered, "recovery.enabled"); + copy(raw, filtered, "recovery.log.enabled"); copy(raw, filtered, "recovery.log.dir"); copy(raw, filtered, "local.resources"); Added: aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/transaction/manager/RecoveryWorkAroundTransactionManager.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/transaction/manager/RecoveryWorkAroundTransactionManager.java?rev=1750850&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/transaction/manager/RecoveryWorkAroundTransactionManager.java (added) +++ aries/trunk/tx-control/tx-control-service-xa/src/main/java/org/apache/geronimo/transaction/manager/RecoveryWorkAroundTransactionManager.java Thu Jun 30 17:55:48 2016 @@ -0,0 +1,17 @@ +package org.apache.geronimo.transaction.manager; + +import javax.transaction.xa.XAException; + +import org.apache.geronimo.transaction.log.HOWLLog; + +public class RecoveryWorkAroundTransactionManager extends GeronimoTransactionManager { + + public RecoveryWorkAroundTransactionManager(int timeout, XidFactory xidFactory, + HOWLLog log) throws XAException { + super(timeout, xidFactory, log); + } + + public NamedXAResourceFactory getNamedResource(String name) { + return super.getNamedXAResourceFactory(name); + } +} Modified: aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java (original) +++ aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionContextTest.java Thu Jun 30 17:55:48 2016 @@ -42,7 +42,8 @@ import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; import org.apache.aries.tx.control.service.common.impl.AbstractTransactionContextImpl; -import org.apache.geronimo.transaction.manager.GeronimoTransactionManager; +import org.apache.geronimo.transaction.manager.RecoveryWorkAroundTransactionManager; +import org.apache.geronimo.transaction.manager.XidFactoryImpl; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,7 +67,11 @@ public class TransactionContextTest { @Before public void setUp() throws XAException { - ctx = new TransactionContextImpl(new GeronimoTransactionManager(), false, ENFORCE_SINGLE); + ctx = new TransactionContextImpl(getTxMgr(), false, ENFORCE_SINGLE); + } + + private RecoveryWorkAroundTransactionManager getTxMgr() throws XAException { + return new RecoveryWorkAroundTransactionManager(30, new XidFactoryImpl(), null); } @Test @@ -87,7 +92,7 @@ public class TransactionContextTest { @Test public void testisReadOnlyTrue() throws XAException { - ctx = new TransactionContextImpl(new GeronimoTransactionManager(), true, ENFORCE_SINGLE); + ctx = new TransactionContextImpl(getTxMgr(), true, ENFORCE_SINGLE); assertTrue(ctx.isReadOnly()); } @@ -113,13 +118,13 @@ public class TransactionContextTest { @Test public void testLocalResourceSupportEnabled() throws XAException { - ctx = new TransactionContextImpl(new GeronimoTransactionManager(), true, ENABLED); + ctx = new TransactionContextImpl(getTxMgr(), true, ENABLED); assertTrue(ctx.supportsLocal()); } @Test public void testLocalResourceSupportDisabled() throws XAException { - ctx = new TransactionContextImpl(new GeronimoTransactionManager(), true, DISABLED); + ctx = new TransactionContextImpl(getTxMgr(), true, DISABLED); assertFalse(ctx.supportsLocal()); } @@ -130,7 +135,12 @@ public class TransactionContextTest { @Test public void testXAResourceRegistration() { - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); + } + + @Test + public void testRecoverableXAResourceRegistration() { + ctx.registerXAResource(xaResource, "anId"); } @Test @@ -351,7 +361,7 @@ public class TransactionContextTest { @Test public void testNoLocalResourceCanBeAddedWhenDisabled() throws Exception { - ctx = new TransactionContextImpl(new GeronimoTransactionManager(), true, DISABLED); + ctx = new TransactionContextImpl(getTxMgr(), true, DISABLED); try { ctx.registerLocalResource(localResource); @@ -363,7 +373,7 @@ public class TransactionContextTest { @Test public void testMultipleLocalResourcesFirstFailsSoRollback() throws Exception { - ctx = new TransactionContextImpl(new GeronimoTransactionManager(), true, ENABLED); + ctx = new TransactionContextImpl(getTxMgr(), true, ENABLED); ctx.registerLocalResource(localResource); @@ -388,7 +398,7 @@ public class TransactionContextTest { @Test public void testSingleXAResource() throws Exception { - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); Mockito.doAnswer(i -> { assertEquals(COMMITTING, ctx.getTransactionStatus()); @@ -411,7 +421,7 @@ public class TransactionContextTest { @Test public void testXAResourceRollbackOnly() throws Exception { - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); ctx.setRollbackOnly(); Mockito.doAnswer(i -> { @@ -435,7 +445,7 @@ public class TransactionContextTest { @Test public void testXAResourcePreCommitException() throws Exception { - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); Mockito.doAnswer(i -> { assertEquals(ROLLING_BACK, ctx.getTransactionStatus()); @@ -460,7 +470,7 @@ public class TransactionContextTest { @Test public void testXAResourcePostCommitException() throws Exception { - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); Mockito.doAnswer(i -> { assertEquals(COMMITTING, ctx.getTransactionStatus()); @@ -490,7 +500,7 @@ public class TransactionContextTest { public void testLastParticipantSuccessSoCommit() throws Exception { ctx.registerLocalResource(localResource); - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); Mockito.doAnswer(i -> { assertEquals(COMMITTING, ctx.getTransactionStatus()); @@ -522,7 +532,7 @@ public class TransactionContextTest { public void testLastParticipantFailsSoRollback() throws Exception { ctx.registerLocalResource(localResource); - ctx.registerXAResource(xaResource); + ctx.registerXAResource(xaResource, null); Mockito.doAnswer(i -> { assertEquals(COMMITTING, ctx.getTransactionStatus()); Modified: aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLogTest.java URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLogTest.java?rev=1750850&r1=1750849&r2=1750850&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLogTest.java (original) +++ aries/trunk/tx-control/tx-control-service-xa/src/test/java/org/apache/aries/tx/control/service/xa/impl/TransactionLogTest.java Thu Jun 30 17:55:48 2016 @@ -18,42 +18,75 @@ */ package org.apache.aries.tx.control.service.xa.impl; +import static javax.transaction.xa.XAResource.XA_OK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.osgi.service.transaction.control.TransactionStatus.ROLLED_BACK; +import java.io.File; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; import javax.sql.XAConnection; +import javax.transaction.xa.XAResource; import org.h2.jdbcx.JdbcDataSource; +import org.h2.tools.Server; import org.junit.After; 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.framework.BundleContext; +import org.osgi.framework.ServiceEvent; +import org.osgi.framework.ServiceListener; +import org.osgi.framework.ServiceReference; +import org.osgi.service.transaction.control.TransactionException; +import org.osgi.service.transaction.control.TransactionRolledBackException; +import org.osgi.service.transaction.control.TransactionStatus; +import org.osgi.service.transaction.control.recovery.RecoverableXAResource; +/** + * The tests in this class look a little odd because we're using an + * unmanaged resource. This is to avoid creating a dependency on a + * JDBCResourceProvider just for the tests, and to give explicit + * control of when things get registered + * + */ @RunWith(MockitoJUnitRunner.class) public class TransactionLogTest { + @Mock + BundleContext ctx; + + @Mock + ServiceReference<RecoverableXAResource> serviceRef; + TransactionControlImpl txControl; JdbcDataSource dataSource; - + + Server server; + @Before public void setUp() throws Exception { Map<String, Object> config = new HashMap<>(); - config.put("recovery.enabled", true); - config.put("recovery.log.dir", "target/generated/recoverylog"); + config.put("recovery.log.enabled", true); + config.put("recovery.log.dir", "target/recovery-test/recoverylog"); - txControl = new TransactionControlImpl(null, config); + txControl = new TransactionControlImpl(ctx, config); - dataSource = new JdbcDataSource(); - dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"); + setupServerAndDataSource(); try (Connection conn = dataSource.getConnection()) { Statement s = conn.createStatement(); @@ -61,21 +94,49 @@ public class TransactionLogTest { s.execute("CREATE TABLE TEST_TABLE ( message varchar(255) )"); } } + + private void setupServerAndDataSource() throws SQLException { + server = Server.createTcpServer("-tcpPort", "0"); + server.start(); + + File dbPath = new File("target/recovery-test/database"); + + dataSource = new JdbcDataSource(); + dataSource.setUrl("jdbc:h2:tcp://127.0.0.1:" + server.getPort() + "/" + dbPath.getAbsolutePath()); + } @After public void destroy() { txControl.destroy(); + try (Connection conn = dataSource.getConnection()) { + conn.createStatement().execute("shutdown immediately"); + } catch (SQLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + delete(new File("target/recovery-test")); + } + + private void delete(File file) { + if(file.isDirectory()) { + for(File f : file.listFiles()) { + delete(f); + } + } + file.delete(); } @Test - public void testRequired() throws Exception { + public void testRequiredNoRecovery() throws Exception { XAConnection xaConn = dataSource.getXAConnection(); try { txControl.required(() -> { - txControl.getCurrentContext().registerXAResource(xaConn.getXAResource()); + txControl.getCurrentContext().registerXAResource(xaConn.getXAResource(), null); Connection conn = xaConn.getConnection(); + // conn.setAutoCommit(false); return conn.createStatement() .execute("Insert into TEST_TABLE values ( 'Hello World!' )"); @@ -89,18 +150,216 @@ public class TransactionLogTest { .executeQuery("Select * from TEST_TABLE"); rs.next(); assertEquals("Hello World!", rs.getString(1)); + assertFalse(rs.next()); + } + } + + @Test + public void testRequired2PCNoRecovery() throws Exception { + XAConnection xaConn = dataSource.getXAConnection(); + XAConnection xaConn2 = dataSource.getXAConnection(); + try { + txControl.required(() -> { + + txControl.getCurrentContext().registerXAResource(xaConn.getXAResource(), null); + txControl.getCurrentContext().registerXAResource(xaConn2.getXAResource(), null); + + Connection conn = xaConn.getConnection(); + // conn.setAutoCommit(false); + Connection conn2 = xaConn2.getConnection(); + conn2.setAutoCommit(false); + + conn.createStatement() + .execute("Insert into TEST_TABLE values ( 'Hello World!' )"); + return conn2.createStatement() + .execute("Insert into TEST_TABLE values ( 'Hello World 2!' )"); + }); + } finally { + xaConn.close(); + } + + try (Connection conn = dataSource.getConnection()) { + ResultSet rs = conn.createStatement() + .executeQuery("Select * from TEST_TABLE order by message DESC"); + rs.next(); + assertEquals("Hello World!", rs.getString(1)); + rs.next(); + assertEquals("Hello World 2!", rs.getString(1)); + assertFalse(rs.next()); + } + } + + @Test + public void testRequiredRecoverable() throws Exception { + XAConnection xaConn = dataSource.getXAConnection(); + try { + txControl.required(() -> { + + txControl.getCurrentContext().registerXAResource(xaConn.getXAResource(), "foo"); + + Connection conn = xaConn.getConnection(); + // conn.setAutoCommit(false); + + return conn.createStatement() + .execute("Insert into TEST_TABLE values ( 'Hello World!' )"); + }); + } finally { + xaConn.close(); + } + + try (Connection conn = dataSource.getConnection()) { + ResultSet rs = conn.createStatement() + .executeQuery("Select * from TEST_TABLE"); + rs.next(); + assertEquals("Hello World!", rs.getString(1)); } } @Test + public void testRequiredRecoveryRequiredPrePrepare() throws Exception { + doRecoveryRequired((good, poison) -> { + txControl.getCurrentContext().registerXAResource(poison, null); + txControl.getCurrentContext().registerXAResource(good, "foo"); + }, TransactionStatus.ROLLED_BACK); + + boolean success = false; + XAConnection conn = dataSource.getXAConnection(); + for(int i=0; i < 5; i++) { + if(conn.getXAResource().recover(XAResource.TMSTARTRSCAN).length == 0) { + success = true; + break; + } else { + // Wait for recovery to happen! + Thread.sleep(500); + } + } + + assertTrue("No recovery in time", success); + } + + @Test + public void testRequiredRecoveryRequiredPostPrepare() throws Exception { + doRecoveryRequired((good, poison) -> { + txControl.getCurrentContext().registerXAResource(good, "foo"); + txControl.getCurrentContext().registerXAResource(poison, null); + }, TransactionStatus.COMMITTED); + + boolean success = false; + for(int i=0; i < 5; i++) { + try (Connection conn = dataSource.getConnection()) { + ResultSet rs = conn.createStatement() + .executeQuery("Select * from TEST_TABLE"); + if(rs.next()) { + assertEquals("Hello World!", rs.getString(1)); + success = true; + break; + } else { + // Wait for recovery to happen! + Thread.sleep(500); + } + } + } + + assertTrue("No recovery in time", success); + } + + public void doRecoveryRequired(BiConsumer<XAResource, XAResource> ordering, + TransactionStatus expectedFinalState) throws Exception { + + //Register the recoverable resource + ArgumentCaptor<ServiceListener> captor = ArgumentCaptor.forClass(ServiceListener.class); + Mockito.verify(ctx).addServiceListener(captor.capture(), Mockito.anyString()); + Mockito.when(ctx.getService(serviceRef)).thenReturn(new TestRecoverableResource("foo", dataSource)); + + captor.getValue().serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, serviceRef)); + + XAConnection xaConn = dataSource.getXAConnection(); + AtomicReference<TransactionStatus> ref = new AtomicReference<TransactionStatus>(); + try { + txControl.required(() -> { + + txControl.getCurrentContext().postCompletion(ref::set); + + Connection conn = xaConn.getConnection(); + // conn.setAutoCommit(false); + + XAResource dsResource = xaConn.getXAResource(); + + XAResource poison = Mockito.mock(XAResource.class); + Mockito.when(poison.prepare(Mockito.any())).thenAnswer(i -> { + // Now kill the db server before it commits! + conn.createStatement().execute("shutdown immediately"); + Thread.sleep(1000); + return XA_OK; + }); + + ordering.accept(dsResource, poison); + + return conn.createStatement() + .execute("Insert into TEST_TABLE values ( 'Hello World!' )"); + }); + } catch (TransactionException te) { + assertEquals(expectedFinalState, ref.get()); + assertEquals(expectedFinalState == ROLLED_BACK, te instanceof TransactionRolledBackException); + } finally { + try { + xaConn.close(); + } catch (SQLException sqle) {} + } + + setupServerAndDataSource(); + + } + + static class TestRecoverableResource implements RecoverableXAResource { + + private final String id; + + private final JdbcDataSource dataSource; + + public TestRecoverableResource(String id, JdbcDataSource dataSource) { + this.id = id; + this.dataSource = dataSource; + } + + @Override + public String getId() { + return id; + } + + @Override + public XAResource getXAResource() throws Exception { + XAConnection xaConnection = dataSource.getXAConnection(); + if(xaConnection.getConnection().isValid(2)) { + return xaConnection.getXAResource(); + } else { + return null; + } + } + + @Override + public void releaseXAResource(XAResource xaRes) { + // This is valid for H2; + try { + ((XAConnection) xaRes).close(); + } catch (SQLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + } + + @Test public void testRequiredWithRollback() throws Exception { XAConnection xaConn = dataSource.getXAConnection(); try { txControl.required(() -> { - txControl.getCurrentContext().registerXAResource(xaConn.getXAResource()); + txControl.getCurrentContext().registerXAResource(xaConn.getXAResource(), null); Connection conn = xaConn.getConnection(); + // conn.setAutoCommit(false); conn.createStatement() .execute("Insert into TEST_TABLE values ( 'Hello World!' )");