Added: aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAConnectionWrapper.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/XAConnectionWrapper.java?rev=1734385&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAConnectionWrapper.java (added) +++ aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAConnectionWrapper.java Thu Mar 10 11:27:48 2016 @@ -0,0 +1,30 @@ +package org.apache.aries.tx.control.jdbc.xa.impl; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.sql.XAConnection; +import javax.transaction.xa.XAResource; + +import org.apache.aries.tx.control.jdbc.common.impl.ConnectionWrapper; + +public class XAConnectionWrapper extends ConnectionWrapper { + + private final Connection connection; + + private final XAResource xaResource; + + public XAConnectionWrapper(XAConnection xaConnection) throws SQLException { + this.connection = xaConnection.getConnection(); + this.xaResource = xaConnection.getXAResource(); + } + + @Override + protected Connection getDelegate() { + return connection; + } + + public XAResource getXaResource() { + return xaResource; + } +}
Added: aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XADataSourceMapper.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/XADataSourceMapper.java?rev=1734385&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XADataSourceMapper.java (added) +++ aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XADataSourceMapper.java Thu Mar 10 11:27:48 2016 @@ -0,0 +1,70 @@ +package org.apache.aries.tx.control.jdbc.xa.impl; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +import javax.sql.DataSource; +import javax.sql.XADataSource; + +public class XADataSourceMapper implements DataSource { + + private final XADataSource xaDataSource; + + public XADataSourceMapper(XADataSource xaDataSource) { + super(); + this.xaDataSource = xaDataSource; + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return xaDataSource.getLogWriter(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + xaDataSource.setLogWriter(out); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + xaDataSource.setLoginTimeout(seconds); + } + + @Override + public int getLoginTimeout() throws SQLException { + return xaDataSource.getLoginTimeout(); + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return xaDataSource.getParentLogger(); + } + + @SuppressWarnings("unchecked") + @Override + public <T> T unwrap(Class<T> iface) throws SQLException { + if(isWrapperFor(iface)) { + return (T) xaDataSource; + } + throw new SQLException("This datasource is not a wrapper for " + iface); + } + + @Override + public boolean isWrapperFor(Class<?> iface) throws SQLException { + return iface == XADataSource.class || iface.isInstance(xaDataSource); + } + + @Override + public Connection getConnection() throws SQLException { + return new XAConnectionWrapper(xaDataSource.getXAConnection()); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return new XAConnectionWrapper(xaDataSource.getXAConnection(username, password)); + } + +} Added: 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=1734385&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnection.java (added) +++ aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/main/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnection.java Thu Mar 10 11:27:48 2016 @@ -0,0 +1,127 @@ +package org.apache.aries.tx.control.jdbc.xa.impl; + +import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.UUID; + +import javax.sql.DataSource; +import javax.transaction.xa.XAResource; + +import org.apache.aries.tx.control.jdbc.common.impl.ConnectionWrapper; +import org.apache.aries.tx.control.jdbc.common.impl.ScopedConnectionWrapper; +import org.apache.aries.tx.control.jdbc.common.impl.TxConnectionWrapper; +import org.osgi.service.transaction.control.LocalResource; +import org.osgi.service.transaction.control.TransactionContext; +import org.osgi.service.transaction.control.TransactionControl; +import org.osgi.service.transaction.control.TransactionException; + +public class XAEnabledTxContextBindingConnection extends ConnectionWrapper { + + private final TransactionControl txControl; + private final UUID resourceId; + private final DataSource dataSource; + private final boolean xaEnabled; + private final boolean localEnabled; + + public XAEnabledTxContextBindingConnection(TransactionControl txControl, + DataSource dataSource, UUID resourceId, boolean xaEnabled, boolean localEnabled) { + this.txControl = txControl; + this.dataSource = dataSource; + this.resourceId = resourceId; + this.xaEnabled = xaEnabled; + this.localEnabled = localEnabled; + } + + @Override + protected final Connection getDelegate() { + + TransactionContext txContext = txControl.getCurrentContext(); + + if (txContext == null) { + throw new TransactionException("The resource " + dataSource + + " cannot be accessed outside of an active Transaction Context"); + } + + Connection existing = (Connection) txContext.getScopedValue(resourceId); + + if (existing != null) { + return existing; + } + + Connection toReturn; + Connection toClose; + + try { + if (txContext.getTransactionStatus() == NO_TRANSACTION) { + toClose = dataSource.getConnection(); + toReturn = new ScopedConnectionWrapper(toClose); + } else if (txContext.supportsXA() && xaEnabled) { + toClose = dataSource.getConnection(); + toReturn = new TxConnectionWrapper(toClose); + txContext.registerXAResource(getXAResource(toClose)); + } else if (txContext.supportsLocal() && localEnabled) { + toClose = dataSource.getConnection(); + toReturn = new TxConnectionWrapper(toClose); + txContext.registerLocalResource(getLocalResource(toClose)); + } else { + throw new TransactionException( + "There is a transaction active, but it does not support local participants"); + } + } catch (Exception sqle) { + throw new TransactionException( + "There was a problem getting hold of a database connection", + sqle); + } + + + txContext.postCompletion(x -> { + try { + toClose.close(); + } catch (SQLException sqle) { + // TODO log this + } + }); + + txContext.putScopedValue(resourceId, toReturn); + + return toReturn; + } + + + private XAResource getXAResource(Connection conn) throws SQLException { + if(conn instanceof XAConnectionWrapper) { + return ((XAConnectionWrapper)conn).getXaResource(); + } else if(conn.isWrapperFor(XAConnectionWrapper.class)){ + return conn.unwrap(XAConnectionWrapper.class).getXaResource(); + } else { + throw new IllegalArgumentException("The XAResource for the connection cannot be found"); + } + } + + private LocalResource getLocalResource(Connection conn) { + return new LocalResource() { + @Override + public void commit() throws TransactionException { + try { + conn.commit(); + } catch (SQLException e) { + throw new TransactionException( + "An error occurred when committing the connection", e); + } + } + + @Override + public void rollback() throws TransactionException { + try { + conn.rollback(); + } catch (SQLException e) { + throw new TransactionException( + "An error occurred when rolling back the connection", e); + } + } + + }; + } +} Added: 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=1734385&view=auto ============================================================================== --- aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/test/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnectionTest.java (added) +++ aries/trunk/tx-control/tx-control-provider-jdbc-xa/src/test/java/org/apache/aries/tx/control/jdbc/xa/impl/XAEnabledTxContextBindingConnectionTest.java Thu Mar 10 11:27:48 2016 @@ -0,0 +1,224 @@ +package org.apache.aries.tx.control.jdbc.xa.impl; + +import static org.mockito.Mockito.times; +import static org.osgi.service.transaction.control.TransactionStatus.ACTIVE; +import static org.osgi.service.transaction.control.TransactionStatus.NO_TRANSACTION; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.sql.DataSource; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import javax.transaction.xa.XAResource; + +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.transaction.control.LocalResource; +import org.osgi.service.transaction.control.TransactionContext; +import org.osgi.service.transaction.control.TransactionControl; +import org.osgi.service.transaction.control.TransactionException; + +@RunWith(MockitoJUnitRunner.class) +public class XAEnabledTxContextBindingConnectionTest { + + @Mock + TransactionControl control; + + @Mock + TransactionContext context; + + @Mock + DataSource dataSource; + + @Mock + XADataSource xaDataSource; + + @Mock + XAConnection xaMock; + + @Mock + XAResource xaResource; + + @Mock + Connection rawConnection; + + Map<Object, Object> variables = new HashMap<>(); + + UUID id = UUID.randomUUID(); + + XAEnabledTxContextBindingConnection localConn; + XAEnabledTxContextBindingConnection xaConn; + + @Before + public void setUp() throws SQLException { + Mockito.when(dataSource.getConnection()).thenReturn(rawConnection).thenReturn(null); + + Mockito.doAnswer(i -> variables.put(i.getArguments()[0], i.getArguments()[1])) + .when(context).putScopedValue(Mockito.any(), Mockito.any()); + Mockito.when(context.getScopedValue(Mockito.any())) + .thenAnswer(i -> variables.get(i.getArguments()[0])); + + Mockito.when(xaDataSource.getXAConnection()).thenReturn(xaMock); + Mockito.when(xaMock.getConnection()).thenReturn(rawConnection); + Mockito.when(xaMock.getXAResource()).thenReturn(xaResource); + + localConn = new XAEnabledTxContextBindingConnection(control, dataSource, id, false, true); + xaConn = new XAEnabledTxContextBindingConnection(control, new XADataSourceMapper(xaDataSource), + id, true, false); + } + + private void setupNoTransaction() { + Mockito.when(control.getCurrentContext()).thenReturn(context); + Mockito.when(context.getTransactionStatus()).thenReturn(NO_TRANSACTION); + } + + private void setupLocalTransaction() { + Mockito.when(control.getCurrentContext()).thenReturn(context); + Mockito.when(context.supportsLocal()).thenReturn(true); + Mockito.when(context.getTransactionStatus()).thenReturn(ACTIVE); + } + + private void setupXATransaction() { + Mockito.when(control.getCurrentContext()).thenReturn(context); + Mockito.when(context.supportsXA()).thenReturn(true); + Mockito.when(context.getTransactionStatus()).thenReturn(ACTIVE); + } + + + @Test(expected=TransactionException.class) + public void testUnscopedLocal() throws SQLException { + localConn.isValid(500); + } + + @Test(expected=TransactionException.class) + public void testUnscopedXA() throws SQLException { + xaConn.isValid(500); + } + + @Test + public void testNoTransaction() throws SQLException { + setupNoTransaction(); + + localConn.isValid(500); + localConn.isValid(500); + + Mockito.verify(rawConnection, times(2)).isValid(500); + Mockito.verify(context, times(0)).registerLocalResource(Mockito.any()); + + Mockito.verify(context).postCompletion(Mockito.any()); + } + + @Test + public void testNoTransactionXA() throws SQLException { + setupNoTransaction(); + + xaConn.isValid(500); + xaConn.isValid(500); + + Mockito.verify(rawConnection, times(2)).isValid(500); + Mockito.verify(context, times(0)).registerLocalResource(Mockito.any()); + + Mockito.verify(context).postCompletion(Mockito.any()); + } + + @Test + public void testLocalTransactionCommit() throws SQLException { + setupLocalTransaction(); + + localConn.isValid(500); + localConn.isValid(500); + + ArgumentCaptor<LocalResource> captor = ArgumentCaptor.forClass(LocalResource.class); + + Mockito.verify(rawConnection, times(2)).isValid(500); + Mockito.verify(context).registerLocalResource(captor.capture()); + + Mockito.verify(context).postCompletion(Mockito.any()); + + captor.getValue().commit(); + + Mockito.verify(rawConnection).commit(); + } + + @Test + public void testLocalTransactionRollback() throws SQLException { + setupLocalTransaction(); + + localConn.isValid(500); + localConn.isValid(500); + + ArgumentCaptor<LocalResource> captor = ArgumentCaptor.forClass(LocalResource.class); + + Mockito.verify(rawConnection, times(2)).isValid(500); + Mockito.verify(context).registerLocalResource(captor.capture()); + + Mockito.verify(context).postCompletion(Mockito.any()); + + captor.getValue().rollback(); + + Mockito.verify(rawConnection).rollback(); + } + + @Test(expected=TransactionException.class) + public void testLocalTransactionNoLocal() throws SQLException { + setupLocalTransaction(); + + Mockito.when(context.supportsLocal()).thenReturn(false); + localConn.isValid(500); + } + + @Test(expected=TransactionException.class) + public void testLocalConnWithXATransaction() throws SQLException { + setupXATransaction(); + + localConn.isValid(500); + } + + @Test + public void testXATransactionCommit() throws SQLException { + setupXATransaction(); + + xaConn.isValid(500); + xaConn.isValid(500); + + + Mockito.verify(rawConnection, times(2)).isValid(500); + Mockito.verify(context).registerXAResource(xaResource); + + Mockito.verify(context).postCompletion(Mockito.any()); + + Mockito.verify(rawConnection, times(0)).commit(); + } + + @Test + public void testXATransactionRollback() throws SQLException { + setupXATransaction(); + + xaConn.isValid(500); + xaConn.isValid(500); + + Mockito.verify(rawConnection, times(2)).isValid(500); + Mockito.verify(context).registerXAResource(xaResource); + + Mockito.verify(context).postCompletion(Mockito.any()); + + Mockito.verify(rawConnection, times(0)).rollback(); + } + + @Test(expected=TransactionException.class) + public void testXAConnTransactionWithLocal() throws SQLException { + setupLocalTransaction(); + + xaConn.isValid(500); + } + +} Modified: aries/trunk/tx-control/tx-control-service-xa/pom.xml URL: http://svn.apache.org/viewvc/aries/trunk/tx-control/tx-control-service-xa/pom.xml?rev=1734385&r1=1734384&r2=1734385&view=diff ============================================================================== --- aries/trunk/tx-control/tx-control-service-xa/pom.xml (original) +++ aries/trunk/tx-control/tx-control-service-xa/pom.xml Thu Mar 10 11:27:48 2016 @@ -48,9 +48,15 @@ org.apache.aries.tx.control.service.xa.*, org.apache.geronimo.transaction.* </aries.osgi.private.pkg> - <!-- No transaction log at the moment --> + <!-- + No transaction log at the moment. + Also we must explicitly import javax.transaction.xa at zero so that we can pick it + up from the JRE. + --> <aries.osgi.import.pkg> + !javax.resource.*, !org.objectweb.howl.*, + javax.transaction.xa;version=0, org.osgi.service.transaction.control;version="[0.0.1,0.0.2)", * </aries.osgi.import.pkg>
