Index: src/test/org/apache/commons/dbcp/TesterCallableStatement.java
===================================================================
--- src/test/org/apache/commons/dbcp/TesterCallableStatement.java (revision 0)
+++ src/test/org/apache/commons/dbcp/TesterCallableStatement.java (revision 0)
@@ -0,0 +1,341 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.Ref;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+
+/**
+ * Trivial implementation of a CallableStatement to avoid null pointer exceptions in tests.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class TesterCallableStatement extends TesterPreparedStatement implements CallableStatement {
+
+ public TesterCallableStatement(Connection conn) {
+ super(conn);
+ }
+
+ public TesterCallableStatement(Connection conn, String sql) {
+ super(conn, sql);
+ }
+
+ public TesterCallableStatement(Connection conn, String sql, int resultSetType, int resultSetConcurrency) {
+ super(conn, sql, resultSetType, resultSetConcurrency);
+ }
+
+ public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException {
+ }
+
+ public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException {
+ }
+
+ public boolean wasNull() throws SQLException {
+ return false;
+ }
+
+ public String getString(int parameterIndex) throws SQLException {
+ return null;
+ }
+
+ public boolean getBoolean(int parameterIndex) throws SQLException {
+ return false;
+ }
+
+ public byte getByte(int parameterIndex) throws SQLException {
+ return 0;
+ }
+
+ public short getShort(int parameterIndex) throws SQLException {
+ return 0;
+ }
+
+ public int getInt(int parameterIndex) throws SQLException {
+ return 0;
+ }
+
+ public long getLong(int parameterIndex) throws SQLException {
+ return 0;
+ }
+
+ public float getFloat(int parameterIndex) throws SQLException {
+ return 0;
+ }
+
+ public double getDouble(int parameterIndex) throws SQLException {
+ return 0;
+ }
+
+ public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {
+ return null;
+ }
+
+ public byte[] getBytes(int parameterIndex) throws SQLException {
+ return new byte[0];
+ }
+
+ public Date getDate(int parameterIndex) throws SQLException {
+ return null;
+ }
+
+ public Time getTime(int parameterIndex) throws SQLException {
+ return null;
+ }
+
+ public Timestamp getTimestamp(int parameterIndex) throws SQLException {
+ return null;
+ }
+
+ public Object getObject(int parameterIndex) throws SQLException {
+ return null;
+ }
+
+ public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
+ return null;
+ }
+
+ public Object getObject(int i, Map map) throws SQLException {
+ return null;
+ }
+
+ public Ref getRef(int i) throws SQLException {
+ return null;
+ }
+
+ public Blob getBlob(int i) throws SQLException {
+ return null;
+ }
+
+ public Clob getClob(int i) throws SQLException {
+ return null;
+ }
+
+ public Array getArray(int i) throws SQLException {
+ return null;
+ }
+
+ public Date getDate(int parameterIndex, Calendar cal) throws SQLException {
+ return null;
+ }
+
+ public Time getTime(int parameterIndex, Calendar cal) throws SQLException {
+ return null;
+ }
+
+ public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {
+ return null;
+ }
+
+ public void registerOutParameter(int paramIndex, int sqlType, String typeName) throws SQLException {
+ }
+
+ public void registerOutParameter(String parameterName, int sqlType) throws SQLException {
+ }
+
+ public void registerOutParameter(String parameterName, int sqlType, int scale) throws SQLException {
+ }
+
+ public void registerOutParameter(String parameterName, int sqlType, String typeName) throws SQLException {
+ }
+
+ public URL getURL(int parameterIndex) throws SQLException {
+ return null;
+ }
+
+ public void setURL(String parameterName, URL val) throws SQLException {
+ }
+
+ public void setNull(String parameterName, int sqlType) throws SQLException {
+ }
+
+ public void setBoolean(String parameterName, boolean x) throws SQLException {
+ }
+
+ public void setByte(String parameterName, byte x) throws SQLException {
+ }
+
+ public void setShort(String parameterName, short x) throws SQLException {
+ }
+
+ public void setInt(String parameterName, int x) throws SQLException {
+ }
+
+ public void setLong(String parameterName, long x) throws SQLException {
+ }
+
+ public void setFloat(String parameterName, float x) throws SQLException {
+ }
+
+ public void setDouble(String parameterName, double x) throws SQLException {
+ }
+
+ public void setBigDecimal(String parameterName, BigDecimal x) throws SQLException {
+ }
+
+ public void setString(String parameterName, String x) throws SQLException {
+ }
+
+ public void setBytes(String parameterName, byte x[]) throws SQLException {
+ }
+
+ public void setDate(String parameterName, Date x) throws SQLException {
+ }
+
+ public void setTime(String parameterName, Time x) throws SQLException {
+ }
+
+ public void setTimestamp(String parameterName, Timestamp x) throws SQLException {
+ }
+
+ public void setAsciiStream(String parameterName, InputStream x, int length) throws SQLException {
+ }
+
+ public void setBinaryStream(String parameterName, InputStream x, int length) throws SQLException {
+ }
+
+ public void setObject(String parameterName, Object x, int targetSqlType, int scale) throws SQLException {
+ }
+
+ public void setObject(String parameterName, Object x, int targetSqlType) throws SQLException {
+ }
+
+ public void setObject(String parameterName, Object x) throws SQLException {
+ }
+
+ public void setCharacterStream(String parameterName, Reader reader, int length) throws SQLException {
+ }
+
+ public void setDate(String parameterName, Date x, Calendar cal) throws SQLException {
+ }
+
+ public void setTime(String parameterName, Time x, Calendar cal) throws SQLException {
+ }
+
+ public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException {
+ }
+
+ public void setNull(String parameterName, int sqlType, String typeName) throws SQLException {
+ }
+
+ public String getString(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public boolean getBoolean(String parameterName) throws SQLException {
+ return false;
+ }
+
+ public byte getByte(String parameterName) throws SQLException {
+ return 0;
+ }
+
+ public short getShort(String parameterName) throws SQLException {
+ return 0;
+ }
+
+ public int getInt(String parameterName) throws SQLException {
+ return 0;
+ }
+
+ public long getLong(String parameterName) throws SQLException {
+ return 0;
+ }
+
+ public float getFloat(String parameterName) throws SQLException {
+ return 0;
+ }
+
+ public double getDouble(String parameterName) throws SQLException {
+ return 0;
+ }
+
+ public byte[] getBytes(String parameterName) throws SQLException {
+ return new byte[0];
+ }
+
+ public Date getDate(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Time getTime(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Timestamp getTimestamp(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Object getObject(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public BigDecimal getBigDecimal(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Object getObject(String parameterName, Map map) throws SQLException {
+ return null;
+ }
+
+ public Ref getRef(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Blob getBlob(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Clob getClob(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Array getArray(String parameterName) throws SQLException {
+ return null;
+ }
+
+ public Date getDate(String parameterName, Calendar cal) throws SQLException {
+ return null;
+ }
+
+ public Time getTime(String parameterName, Calendar cal) throws SQLException {
+ return null;
+ }
+
+ public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException {
+ return null;
+ }
+
+ public URL getURL(String parameterName) throws SQLException {
+ return null;
+ }
+}
Index: src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java
===================================================================
--- src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java (revision 0)
+++ src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java (revision 0)
@@ -0,0 +1,246 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.commons.dbcp.ConnectionFactory;
+import org.apache.commons.dbcp.DelegatingConnection;
+import org.apache.commons.dbcp.DriverConnectionFactory;
+import org.apache.commons.dbcp.PoolableConnectionFactory;
+import org.apache.commons.dbcp.PoolingDataSource;
+import org.apache.commons.dbcp.TestConnectionPool;
+import org.apache.commons.dbcp.TesterDriver;
+import org.apache.commons.pool.ObjectPool;
+import org.apache.commons.pool.impl.GenericObjectPool;
+import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
+
+import javax.transaction.TransactionManager;
+import java.sql.Connection;
+import java.util.Properties;
+
+/**
+ * TestSuite for ManagedDataSource without a transaction in progress.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class TestManagedDataSource extends TestConnectionPool {
+ public TestManagedDataSource(String testName) {
+ super(testName);
+ }
+
+ public static Test suite() {
+ return new TestSuite(TestManagedDataSource.class);
+ }
+
+ protected Connection getConnection() throws Exception {
+ return ds.getConnection();
+ }
+
+ protected PoolingDataSource ds = null;
+ private GenericObjectPool pool = null;
+ protected TransactionManager transactionManager;
+
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // create a GeronimoTransactionManager for testing
+ transactionManager = new TransactionManagerImpl();
+
+ // create a driver connection factory
+ Properties properties = new Properties();
+ properties.setProperty("user", "username");
+ properties.setProperty("password", "password");
+ ConnectionFactory connectionFactory = new DriverConnectionFactory(new TesterDriver(), "jdbc:apache:commons:testdriver", properties);
+
+ // wrap it with a LocalXAConnectionFactory
+ XAConnectionFactory xaConnectionFactory = new LocalXAConnectionFactory(transactionManager, connectionFactory);
+
+ // create the pool
+ pool = new GenericObjectPool();
+ pool.setMaxActive(getMaxActive());
+ pool.setMaxWait(getMaxWait());
+
+ // create the pool object factory
+ PoolableConnectionFactory factory = new PoolableConnectionFactory(xaConnectionFactory, pool, null, "SELECT DUMMY FROM DUAL", true, true);
+ pool.setFactory(factory);
+
+ // finally create the datasource
+ ds = new ManagedDataSource(pool, xaConnectionFactory.getTransactionRegistry());
+ ds.setAccessToUnderlyingConnectionAllowed(true);
+ }
+
+ public void tearDown() throws Exception {
+ pool.close();
+ super.tearDown();
+ }
+
+ /**
+ * Verify the accessToUnderlyingConnectionAllowed propertly limits access to the physical connection.
+ */
+ public void testAccessToUnderlyingConnectionAllowed() throws Exception {
+ ds.setAccessToUnderlyingConnectionAllowed(true);
+ ManagedConnection connection = (ManagedConnection) newConnection();
+ assertTrue(connection.isAccessToUnderlyingConnectionAllowed());
+ assertNotNull(connection.getDelegate());
+ assertNotNull(connection.getInnermostDelegate());
+ connection.close();
+
+ ds.setAccessToUnderlyingConnectionAllowed(false);
+ connection = (ManagedConnection) newConnection();
+ assertFalse(connection.isAccessToUnderlyingConnectionAllowed());
+ assertNull(connection.getDelegate());
+ assertNull(connection.getInnermostDelegate());
+ connection.close();
+ }
+
+ /**
+ * Verify that conection sharing is working (or not working) as expected.
+ */
+ public void testSharedConnection() throws Exception {
+ DelegatingConnection connectionA = (DelegatingConnection) newConnection();
+ DelegatingConnection connectionB = (DelegatingConnection) newConnection();
+
+ assertFalse(connectionA.equals(connectionB));
+ assertFalse(connectionB.equals(connectionA));
+ assertFalse(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
+ assertFalse(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
+
+ connectionA.close();
+ connectionB.close();
+ }
+
+ public void testCantCloseConnectionTwice() throws Exception {
+ // this test is invalid... the JavaDoc and spec for the close method specifically
+ // state that the close method on an already closed connection is a no-op
+ }
+
+
+ /**
+ * Verify the close method can be called multiple times on a single connection without
+ * an exception being thrown.
+ */
+ public void testCanCloseConnectionTwice() throws Exception {
+ for (int i = 0; i < getMaxActive(); i++) { // loop to show we *can* close again once we've borrowed it from the pool again
+ Connection conn = newConnection();
+ assertTrue(null != conn);
+ assertTrue(!conn.isClosed());
+ conn.close();
+ assertTrue(conn.isClosed());
+ conn.close();
+ assertTrue(conn.isClosed());
+ }
+ }
+
+ public void testManagedConnectionEqualsSameDelegate() throws Exception {
+ // Get a maximal set of connections from the pool
+ Connection[] c = new Connection[getMaxActive()];
+ for (int i = 0; i < c.length; i++) {
+ c[i] = newConnection();
+ }
+ // Close the delegate of one wrapper in the pool
+ ((DelegatingConnection) c[0]).getDelegate().close();
+
+ // Grab a new connection - should get c[0]'s closed connection
+ // so should be delegate-equivalent, so equal
+ Connection con = newConnection();
+ assertTrue(c[0].equals(con));
+ assertTrue(con.equals(c[0]));
+ for (int i = 0; i < c.length; i++) {
+ c[i].close();
+ }
+ }
+
+ /*
+ * JIRA: DBCP-198
+ */
+ public void testManagedConnectionEqualsReflexive() throws Exception {
+ // Statndard setup - using DelegatingConnections
+ // returned from PoolableConnectionFactory
+ checkManagedConnectionEqualsReflexive();
+
+ // Force ManagedConnections to wrap non-Delegating connections
+ pool.close();
+ pool = new GenericObjectPool();
+ pool.setMaxActive(getMaxActive());
+ pool.setMaxWait(getMaxWait());
+ Properties props = new Properties();
+ props.setProperty("user", "username");
+ props.setProperty("password", "password");
+ NonDelegatingPoolableConnectionFactory factory = new NonDelegatingPoolableConnectionFactory(new DriverConnectionFactory(new TesterDriver(), "jdbc:apache:commons:testdriver", props), pool);
+ pool.setFactory(factory);
+ ds = new PoolingDataSource(pool);
+ checkManagedConnectionEqualsReflexive();
+ }
+
+ private void checkManagedConnectionEqualsReflexive() throws Exception {
+ Connection con = ds.getConnection();
+ Connection con2 = con;
+ assertTrue(con2.equals(con));
+ assertTrue(con.equals(con2));
+ con.close();
+ }
+
+ public void testManagedConnectionEqualsFail() throws Exception {
+ Connection con1 = ds.getConnection();
+ Connection con2 = ds.getConnection();
+ assertFalse(con1.equals(con2));
+ con1.close();
+ con2.close();
+ }
+
+ public void testManagedConnectionEqualsNull() throws Exception {
+ Connection con1 = ds.getConnection();
+ Connection con2 = null;
+ assertFalse(con1.equals(con2));
+ con1.close();
+ }
+
+ public void testManagedConnectionEqualsType() throws Exception {
+ Connection con1 = ds.getConnection();
+ Integer con2 = new Integer(0);
+ assertFalse(con1.equals(con2));
+ con1.close();
+ }
+
+ public void testManagedConnectionEqualInnermost() throws Exception {
+ ds.setAccessToUnderlyingConnectionAllowed(true);
+ DelegatingConnection con = (DelegatingConnection) ds.getConnection();
+ Connection inner = con.getInnermostDelegate();
+ ds.setAccessToUnderlyingConnectionAllowed(false);
+ DelegatingConnection con2 = new DelegatingConnection(inner);
+ assertTrue(con2.equals(con));
+ assertTrue(con.innermostDelegateEquals(con2.getInnermostDelegate()));
+ assertTrue(con2.innermostDelegateEquals(inner));
+ assertTrue(con.equals(con2));
+ }
+
+ /**
+ * Factory to return non-delegating connections for DBCP-198 test
+ */
+ private class NonDelegatingPoolableConnectionFactory extends PoolableConnectionFactory {
+ public NonDelegatingPoolableConnectionFactory(ConnectionFactory connFactory, ObjectPool pool) {
+ super(connFactory, pool, null, null, true, true);
+ }
+
+ synchronized public Object makeObject() throws Exception {
+ return _connFactory.createConnection();
+ }
+ }
+}
Index: src/test/org/apache/commons/dbcp/managed/TestManagedDataSourceInTx.java
===================================================================
--- src/test/org/apache/commons/dbcp/managed/TestManagedDataSourceInTx.java (revision 0)
+++ src/test/org/apache/commons/dbcp/managed/TestManagedDataSourceInTx.java (revision 0)
@@ -0,0 +1,305 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import org.apache.commons.dbcp.DelegatingConnection;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import javax.transaction.Transaction;
+
+/**
+ * TestSuite for ManagedDataSource with an active transaction in progress.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class TestManagedDataSourceInTx extends TestManagedDataSource {
+
+ public TestManagedDataSourceInTx(String testName) {
+ super(testName);
+ }
+
+ public static Test suite() {
+ return new TestSuite(TestManagedDataSourceInTx.class);
+ }
+
+ public void setUp() throws Exception {
+ super.setUp();
+
+ transactionManager.begin();
+ }
+
+ public void tearDown() throws Exception {
+ if (transactionManager.getTransaction() != null) {
+ transactionManager.commit();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * @see #testSharedConnection()
+ */
+ public void testManagedConnectionEqualsFail() throws Exception {
+ // this test is invalid for managed conections since because
+ // two connections to the same datasource are supposed to share
+ // a single connection
+ }
+
+ public void testConnectionsAreDistinct() throws Exception {
+ Connection[] conn = new Connection[getMaxActive()];
+ for(int i=0;i
+ * An implementation can assume that the caller of this will wrap the connection in
+ * a proxy that protects access to the setAutoCommit, commit and rollback when
+ * enrolled in a XA transaction.
+ *
+ * @return a new {@link java.sql.Connection}
+ * @throws java.sql.SQLException if a database error occurs creating the connection
+ */
+ Connection createConnection() throws SQLException;
+}
Index: src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java
===================================================================
--- src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java (revision 0)
+++ src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java (revision 0)
@@ -0,0 +1,80 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import org.apache.commons.pool.ObjectPool;
+import org.apache.commons.dbcp.PoolingDataSource;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * The ManagedDataSource is a PoolingDataSource that creates ManagedConnections.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class ManagedDataSource extends PoolingDataSource {
+ private TransactionRegistry transactionRegistry;
+
+ /**
+ * Creates an uninitialized datasource. Before this data source can be used a pool and
+ * transaction registry must be set.
+ */
+ public ManagedDataSource() {
+ }
+
+ /**
+ * Creates a ManagedDataSource which obtains connections from the specified pool and
+ * manages them using the specified transaction registry. The TransactionRegistry must
+ * be the transaction registry obtained from the XAConnectionFactory used to create
+ * the connection pool. If not an error will occure when attempting to use the connection
+ * in a global transaction because the XAResource object associated with the connection
+ * will be unavailable.
+ *
+ * @param pool the connection pool
+ * @param transactionRegistry the transaction registry obtained from the
+ * XAConnectionFactory used to create the connection pool object factory
+ */
+ public ManagedDataSource(ObjectPool pool, TransactionRegistry transactionRegistry) {
+ super(pool);
+ this.transactionRegistry = transactionRegistry;
+ }
+
+ /**
+ * Sets the transaction registry from the XAConnectionFactory used to create the pool.
+ * The transaction registry can only be set once using either a connector or this setter
+ * method.
+ * @param transactionRegistry the transaction registry acquired from the XAConnectionFactory
+ * used to create the pool
+ */
+ public void setTransactionRegistry(TransactionRegistry transactionRegistry) {
+ if(this.transactionRegistry != null) throw new IllegalStateException("TransactionRegistry already set");
+ if(transactionRegistry == null) throw new NullPointerException("TransactionRegistry is null");
+
+ this.transactionRegistry = transactionRegistry;
+ }
+
+ public Connection getConnection() throws SQLException {
+ if (_pool == null) throw new IllegalStateException("Pool has not been set");
+ if (transactionRegistry == null) throw new IllegalStateException("TransactionRegistry has not been set");
+
+ Connection connection = new ManagedConnection(_pool, transactionRegistry, isAccessToUnderlyingConnectionAllowed());
+ return connection;
+ }
+}
Index: src/java/org/apache/commons/dbcp/managed/TransactionContext.java
===================================================================
--- src/java/org/apache/commons/dbcp/managed/TransactionContext.java (revision 0)
+++ src/java/org/apache/commons/dbcp/managed/TransactionContext.java (revision 0)
@@ -0,0 +1,131 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.xa.XAResource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * TransactionContext represents the association between a single XAConnectionFactory and a Transaction.
+ * This context contains a single shared connection which should be used by all ManagedConnections for
+ * the XAConnectionFactory, the ability to listen for the transaction completion event, and a method
+ * to check the status of the transaction.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class TransactionContext {
+ private final TransactionRegistry transactionRegistry;
+ private final Transaction transaction;
+ private Connection sharedConnection;
+
+ /**
+ * Creates a TransactionContext for the specified Transaction and TransactionRegistry. The
+ * TransactionRegistry is used to obtain the XAResource for the shared connection when it is
+ * enlisted in the transaction.
+ *
+ * @param transactionRegistry the TransactionRegistry used to obtain the XAResource for the
+ * shared connection
+ * @param transaction the transaction
+ */
+ public TransactionContext(TransactionRegistry transactionRegistry, Transaction transaction) {
+ if (transactionRegistry == null) throw new NullPointerException("transactionRegistry is null");
+ if (transaction == null) throw new NullPointerException("transaction is null");
+ this.transactionRegistry = transactionRegistry;
+ this.transaction = transaction;
+ }
+
+ /**
+ * Gets the connection shared by all ManagedConnections in the transaction. Specifically,
+ * connection using the same XAConnectionFactory from which the TransactionRegistry was
+ * obtained.
+ * @return the shared connection for this transaction
+ */
+ public Connection getSharedConnection() {
+ return sharedConnection;
+ }
+
+ /**
+ * Sets the shared connection for this transaction. The shared connection is enlisted
+ * in the transaction.
+ *
+ * @param sharedConnection the shared connection
+ * @throws SQLException if a shared connection is already set, if XAResource for the connection
+ * could not be found in the transaction registry, or if there was a problem enlisting the
+ * connection in the transaction
+ */
+ public void setSharedConnection(Connection sharedConnection) throws SQLException {
+ if (this.sharedConnection != null) {
+ throw new IllegalStateException("A shared connection is alredy set");
+ }
+
+ // This is the first use of the connection in this transaction, so we must
+ // enlist it in the transaction
+ try {
+ XAResource xaResource = transactionRegistry.getXAResource(sharedConnection);
+ transaction.enlistResource(xaResource);
+ } catch (RollbackException e) {
+ // transaction was rolled back... proceed as if there never was a transaction
+ } catch (SystemException e) {
+ throw (SQLException) new SQLException("Unable to enlist connection the transaction").initCause(e);
+ }
+
+ this.sharedConnection = sharedConnection;
+ }
+
+ /**
+ * Adds a listener for transaction completion events.
+ *
+ * @param listener the listener to add
+ * @throws SQLException if a problem occurs adding the listener to the transaction
+ */
+ public void addTransactionContextListener(final TransactionContextListener listener) throws SQLException {
+ try {
+ transaction.registerSynchronization(new Synchronization() {
+ public void beforeCompletion() {
+ }
+
+ public void afterCompletion(int status) {
+ listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED);
+ }
+ });
+ } catch (Exception e) {
+ throw (SQLException) new SQLException("Unable to register transaction context listener").initCause(e);
+ }
+ }
+
+ /**
+ * True if the transaction is active or marked for rollback only.
+ * @return true if the transaction is active or marked for rollback only; false otherwise
+ * @throws SQLException if a problem occurs obtaining the transaction status
+ */
+ public boolean isActive() throws SQLException {
+ try {
+ int status = transaction.getStatus();
+ return status == Status.STATUS_ACTIVE || status == Status.STATUS_MARKED_ROLLBACK;
+ } catch (SystemException e) {
+ throw (SQLException) new SQLException("Unable to get transaction status").initCause(e);
+ }
+ }
+}
Index: src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java
===================================================================
--- src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java (revision 0)
+++ src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java (revision 0)
@@ -0,0 +1,69 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import javax.sql.XAConnection;
+import javax.sql.XADataSource;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * An implementation of XAConnectionFactory which uses a real XADataSource to obtain connections and XAResources.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class DataSourceXAConnectionFactory implements XAConnectionFactory {
+ protected TransactionRegistry transactionRegistry;
+ protected XADataSource xaDataSource;
+
+ /**
+ * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database
+ * connections. The connections are enlisted into transactions using the specified transaction manager.
+ *
+ * @param transactionManager the transaction manager in which connections will be enlisted
+ * @param xaDataSource the data source from which connections will be retrieved
+ */
+ public DataSourceXAConnectionFactory(TransactionManager transactionManager, XADataSource xaDataSource) {
+ if (transactionManager == null) throw new NullPointerException("transactionManager is null");
+ if (xaDataSource == null) throw new NullPointerException("xaDataSource is null");
+
+ this.transactionRegistry = new TransactionRegistry(transactionManager);
+ this.xaDataSource = xaDataSource;
+ }
+
+ public TransactionRegistry getTransactionRegistry() {
+ return transactionRegistry;
+ }
+
+ public Connection createConnection() throws SQLException {
+ // create a new XAConection
+ XAConnection xaConnection = xaDataSource.getXAConnection();
+
+ // get the real connection and XAResource from the connection
+ Connection connection = xaConnection.getConnection();
+ XAResource xaResource = xaConnection.getXAResource();
+
+ // register the xa resource for the connection
+ transactionRegistry.registerConnection(connection, xaResource);
+
+ return connection;
+ }
+}
Index: src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java
===================================================================
--- src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java (revision 0)
+++ src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java (revision 0)
@@ -0,0 +1,298 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import org.apache.commons.dbcp.ConnectionFactory;
+
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * An implementation of XAConnectionFactory which manages non-XA connections in XA transactions. A non-XA connection
+ * commits and rolls back as part of the XA transaction, but is not recoverable since the connection does not implement
+ * the 2-phase protocol.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class LocalXAConnectionFactory implements XAConnectionFactory {
+ protected TransactionRegistry transactionRegistry;
+ protected ConnectionFactory connectionFactory;
+
+ /**
+ * Creates an LocalXAConnectionFactory which uses the specified connection factory to create database
+ * connections. The connections are enlisted into transactions using the specified transaction manager.
+ *
+ * @param transactionManager the transaction manager in which connections will be enlisted
+ * @param connectionFactory the connection factory from which connections will be retrieved
+ */
+ public LocalXAConnectionFactory(TransactionManager transactionManager, ConnectionFactory connectionFactory) {
+ if (transactionManager == null) throw new NullPointerException("transactionManager is null");
+ if (connectionFactory == null) throw new NullPointerException("connectionFactory is null");
+
+ this.transactionRegistry = new TransactionRegistry(transactionManager);
+ this.connectionFactory = connectionFactory;
+ }
+
+ public TransactionRegistry getTransactionRegistry() {
+ return transactionRegistry;
+ }
+
+ public Connection createConnection() throws SQLException {
+ // create a new conection
+ Connection connection = connectionFactory.createConnection();
+
+ // create a XAResource to manage the connection during XA transacitons
+ XAResource xaResource = new LocalXAResource(connection);
+
+ // register the xa resource for the connection
+ transactionRegistry.registerConnection(connection, xaResource);
+
+ return connection;
+ }
+
+ /**
+ * LocalXAResource is a fake XAResource for non-XA connections. When a transaction is started
+ * the connection auto-commit is turned off. When the connection is committed or rolled back,
+ * the commit or rollback method is called on the connection and then the original auto-commit
+ * value is restored.
+ *
+ * The LocalXAResource also respects the connection read-only setting. If the connection is
+ * read-only the commit method will not be called, and the prepare method returns the XA_RDONLY.
+ *
+ * It is assumed that the wrapper around a managed connection disables the setAutoCommit(),
+ * commit(), rollback() and setReadOnly() methods while a transaction is in progress.
+ */
+ protected static class LocalXAResource implements XAResource {
+ private final Connection connection;
+ private Xid xid;
+ private boolean originalAutoCommit;
+
+ public LocalXAResource(Connection localTransaction) {
+ this.connection = localTransaction;
+ }
+
+ /**
+ * Gets the current xid of the transaction branch associated with this XAResource.
+ *
+ * @return the current xid of the transaction branch associated with this XAResource.
+ */
+ public synchronized Xid getXid() {
+ return xid;
+ }
+
+ /**
+ * Signals that a the connection has been enrolled in a transaction. This method saves off the
+ * current auto commit flag, and then disables auto commit. The original auto commit setting is
+ * restored when the transaction completes.
+ *
+ * @param xid the id of the transaction branch for this connection
+ * @param flag either XAResource.TMNOFLAGS or XAResource.TMRESUME
+ * @throws XAException if the connection is already elisted in another tranction, or if auto-commit
+ * could not be disabled
+ */
+ public synchronized void start(Xid xid, int flag) throws XAException {
+ if (flag == XAResource.TMNOFLAGS) {
+ // first time in this transaction
+
+ // make sure we aren't already in another tx
+ if (this.xid != null) {
+ throw new XAException("Already enlisted in another transaction with xid " + xid);
+ }
+
+ // save off the current auto commit flag so it can be restored after the transaction completes
+ try {
+ originalAutoCommit = connection.getAutoCommit();
+ } catch (SQLException ignored) {
+ // no big deal, just assume it was off
+ originalAutoCommit = true;
+ }
+
+ // update the auto commit flag
+ try {
+ connection.setAutoCommit(false);
+ } catch (SQLException e) {
+ throw (XAException) new XAException("Count not turn off auto commit for a XA transaction").initCause(e);
+ }
+
+ this.xid = xid;
+ } else if (flag == XAResource.TMRESUME) {
+ if (xid != this.xid) {
+ throw new XAException("Attempting to resume in different transaction: expected " + this.xid + ", but was " + xid);
+ }
+ } else {
+ throw new XAException("Unknown start flag " + flag);
+ }
+ }
+
+ /**
+ * This method does nothing.
+ *
+ * @param xid the id of the transaction branch for this connection
+ * @param flag ignored
+ * @throws XAException if the connection is already elisted in another tranction
+ */
+ public synchronized void end(Xid xid, int flag) throws XAException {
+ if (xid == null) throw new NullPointerException("xid is null");
+ if (!this.xid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.xid + ", but was " + xid);
+
+ // This notification tells us that the application server is done using this
+ // connection for the time being. The connection is still associated with an
+ // open transaction, so we must still wait for the commit or rollback method
+ }
+
+ /**
+ * This method does nothing since the LocalXAConnection does not support two-phase-commit. This method
+ * will return XAResource.XA_RDONLY if the connection isReadOnly(). This assumes that the physical
+ * connection is wrapped with a proxy that prevents an application from changing the read-only flag
+ * while enrolled in a transaction.
+ *
+ * @param xid the id of the transaction branch for this connection
+ * @return XAResource.XA_RDONLY if the connection.isReadOnly(); XAResource.XA_OK otherwise
+ */
+ public synchronized int prepare(Xid xid) {
+ // if the connection is read-only, then the resource is read-only
+ // NOTE: this assumes that the outer proxy throws an exception when application code
+ // attempts to set this in a transaction
+ try {
+ if (connection.isReadOnly()) {
+ // update the auto commit flag
+ connection.setAutoCommit(originalAutoCommit);
+
+ // tell the transaction manager we are read only
+ return XAResource.XA_RDONLY;
+ }
+ } catch (SQLException ignored) {
+ // no big deal
+ }
+
+ // this is a local (one phase) only connectiion, so we can't prepare
+ return XAResource.XA_OK;
+ }
+
+ /**
+ * Commits the transaction and restores the original auto commit setting.
+ *
+ * @param xid the id of the transaction branch for this connection
+ * @param flag ignored
+ * @throws XAException if connection.commit() throws a SQLException
+ */
+ public synchronized void commit(Xid xid, boolean flag) throws XAException {
+ if (xid == null) throw new NullPointerException("xid is null");
+ if (!this.xid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.xid + ", but was " + xid);
+
+ try {
+ // make sure the connection isn't already closed
+ if (connection.isClosed()) {
+ throw new XAException("Conection is closed");
+ }
+
+ // A read only connection should not be committed
+ if (!connection.isReadOnly()) {
+ connection.commit();
+ }
+ } catch (SQLException e) {
+ throw (XAException) new XAException().initCause(e);
+ } finally {
+ try {
+ connection.setAutoCommit(originalAutoCommit);
+ } catch (SQLException e) {
+ }
+ this.xid = null;
+ }
+ }
+
+ /**
+ * Rollsback the transaction and restores the original auto commit setting.
+ *
+ * @param xid the id of the transaction branch for this connection
+ * @throws XAException if connection.rollback() throws a SQLException
+ */
+ public synchronized void rollback(Xid xid) throws XAException {
+ if (xid == null) throw new NullPointerException("xid is null");
+ if (!this.xid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.xid + ", but was " + xid);
+
+ try {
+ connection.rollback();
+ } catch (SQLException e) {
+ throw (XAException) new XAException().initCause(e);
+ } finally {
+ try {
+ connection.setAutoCommit(originalAutoCommit);
+ } catch (SQLException e) {
+ }
+ this.xid = null;
+ }
+ }
+
+ /**
+ * Returns true if the specified XAResource == this XAResource.
+ *
+ * @param xaResource the XAResource to test
+ * @return true if the specified XAResource == this XAResource; false otherwise
+ */
+ public boolean isSameRM(XAResource xaResource) {
+ return this == xaResource;
+ }
+
+ /**
+ * Clears the currently associated transaction if it is the specified xid.
+ *
+ * @param xid the id of the transaction to forget
+ */
+ public synchronized void forget(Xid xid) {
+ if (xid != null && this.xid.equals(xid)) {
+ this.xid = null;
+ }
+ }
+
+ /**
+ * Always returns a zero length Xid array. The LocalXAConnectionFactory can not support recovery, so no xids will ever be found.
+ *
+ * @param flag ignored since recoverty is not supported
+ * @return always a zero length Xid array.
+ */
+ public Xid[] recover(int flag) {
+ return new Xid[0];
+ }
+
+ /**
+ * Always returns 0 since we have no way to set a transaction timeout on a JDBC connection.
+ *
+ * @return always 0
+ */
+ public int getTransactionTimeout() {
+ return 0;
+ }
+
+ /**
+ * Always returns false since we have no way to set a transaction timeout on a JDBC connection.
+ *
+ * @param transactionTimeout ignored since we have no way to set a transaction timeout on a JDBC connection
+ * @return always false
+ */
+ public boolean setTransactionTimeout(int transactionTimeout) {
+ return false;
+ }
+ }
+
+}
Index: src/java/org/apache/commons/dbcp/managed/ManagedConnection.java
===================================================================
--- src/java/org/apache/commons/dbcp/managed/ManagedConnection.java (revision 0)
+++ src/java/org/apache/commons/dbcp/managed/ManagedConnection.java (revision 0)
@@ -0,0 +1,271 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import org.apache.commons.dbcp.DelegatingConnection;
+import org.apache.commons.pool.ObjectPool;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * ManagedConnection is responsible for managing a database connection in a transactional environment
+ * (typically called "Container Managed"). A managed connection opperates like any other connection
+ * when no gloabal transaction (a.k.a. XA transaction or JTA Transaction) is in progress. When a
+ * global transaction is active a single physical connection to the database is used by all
+ * ManagedConnections accessed in the scope of the transaction. Connection sharing means that all
+ * data access during a transaction has a consistent view of the database. When the global transaction
+ * is committed or rolled back the enlisted connections are committed or rolled back. Typically upon
+ * transaction completion, a connection returns to the auto commit setting in effect before being
+ * elisted in the transaction, but some vendors do not propertly implement this.
+ *
+ * When enslisted in a transaction the setAutoCommit(), commit(), rollback(), and setReadOnly() methods
+ * throw a SQLException. This is necessary to assure that the transaction completes as a single unit.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class ManagedConnection extends DelegatingConnection {
+ private final ObjectPool pool;
+ private final TransactionRegistry transactionRegistry;
+ private final boolean accessToUnderlyingConnectionAllowed;
+ private TransactionContext transactionContext;
+ private boolean isSharedConnection;
+
+ public ManagedConnection(ObjectPool pool, TransactionRegistry transactionRegistry, boolean accessToUnderlyingConnectionAllowed) throws SQLException {
+ super(null);
+ this.pool = pool;
+ this.transactionRegistry = transactionRegistry;
+ this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
+ updateTransactionStatus();
+ }
+
+ protected void checkOpen() throws SQLException {
+ super.checkOpen();
+ updateTransactionStatus();
+ }
+
+ private void updateTransactionStatus() throws SQLException {
+ // if there is a is an active transaction context, assure the transaction context hasn't changed
+ if (transactionContext != null) {
+ if (transactionContext.isActive()) {
+ if (transactionContext != transactionRegistry.getActiveTransactionContext()) {
+ throw new SQLException("Connection can not be used while enlisted in another transaction");
+ }
+ return;
+ } else {
+ // transaction should have been cleared up by TransactionContextListener, but in
+ // rare cases another lister could have registered which uses the connection before
+ // our listener is called. In that rare case, trigger the transaction complete call now
+ transactionComplete();
+ }
+ }
+
+ // the existing transction context ended (or we didn't have one), get the active transaction context
+ transactionContext = transactionRegistry.getActiveTransactionContext();
+
+ // if there is an active transaction context and it already has a shared connection, use it
+ if (transactionContext != null && transactionContext.getSharedConnection() != null) {
+ // A connection for the connection factory has already been enrolled
+ // in the transaction, replace our delegate with the enrolled connection
+
+ // return current connection to the pool
+ Connection connection = getDelegateInternal();
+ setDelegate(null);
+ if (connection != null) {
+ try {
+ pool.returnObject(connection);
+ } catch (Exception ignored) {
+ // whatever... try to invalidat the connection
+ try {
+ pool.invalidateObject(connection);
+ } catch (Exception ignore) {
+ // no big deal
+ }
+ }
+ }
+
+ // add a listener to the transaction context
+ transactionContext.addTransactionContextListener(new CompletionListener());
+
+ // set our delegate to the shared connection
+ setDelegate(transactionContext.getSharedConnection());
+
+ // remember that we are using a shared connection so it can be cleared after the
+ // transaction completes
+ isSharedConnection = true;
+ } else {
+ // if our delegate is null, create one
+ if (getDelegateInternal() == null) {
+ try {
+ // borrow a new connection from the pool
+ Connection connection = (Connection) pool.borrowObject();
+ setDelegate(connection);
+ } catch (Exception e) {
+ throw (SQLException) new SQLException("Unable to acquire a new connection from the pool").initCause(e);
+ }
+ }
+
+ // if we have a transaction, out delegate becomes the shared delegate
+ if (transactionContext != null) {
+ // add a listener to the transaction context
+ transactionContext.addTransactionContextListener(new CompletionListener());
+
+ // register our connection as the shared connection
+ try {
+ transactionContext.setSharedConnection(getDelegateInternal());
+ } catch (SQLException e) {
+ // transaction is hosed
+ transactionContext = null;
+ throw e;
+ }
+ }
+ }
+ }
+
+ public void close() throws SQLException {
+ // close can be called multiple times, but PoolableConnection improperly
+ // throws an exception when a connection is closed twice, so before calling
+ // close we aren't alreayd closed
+ if (!isClosed()) {
+
+ // don't use super.close() because it calls passivate() which marks the
+ // the connection as cloased without returning it to the pool
+ try {
+ // don't actually close the connection if in a transaction
+ // the connection will be closed by the transactionComplete method
+ if (transactionContext == null) {
+ getDelegateInternal().close();
+ }
+ } finally {
+ _closed = true;
+ }
+ }
+ }
+
+ protected class CompletionListener implements TransactionContextListener {
+ public void afterCompletion(TransactionContext transactionContext, boolean commited) {
+ if (transactionContext == transactionContext) {
+ transactionComplete();
+ }
+ }
+ }
+
+ protected void transactionComplete() {
+ transactionContext = null;
+
+ // if we were using a shared connection, clear the reference now that the transaction has completed
+ if (isSharedConnection) {
+ // for now, just set the delegate to null, it will be created later if needed
+ setDelegate(null);
+ isSharedConnection = false;
+ }
+
+ // if this connection was closed during the transaction and there is still a delegate present close it
+ Connection delegate = getDelegateInternal();
+ if (_closed && delegate != null) {
+ try {
+ setDelegate(null);
+
+ // don't actually close the connection if in a transaction
+ if (!delegate.isClosed()) {
+ // don't use super.close() because it calls passivate() which marks the
+ // the connection as cloased without returning it to the pool
+ delegate.close();
+ }
+ } catch (SQLException ignored) {
+ // not a whole lot we can do here as connection is closed
+ // and this is a transaction classback so there is no
+ // way to report the error
+ } finally {
+ _closed = true;
+ }
+ }
+
+ }
+
+ //
+ // The following methods can't be used while enlisted in a transaction
+ //
+
+ public void setAutoCommit(boolean autoCommit) throws SQLException {
+ if (transactionContext != null) {
+ throw new SQLException("Auto-commit can not be set while enrolled in a transaction");
+ }
+ super.setAutoCommit(autoCommit);
+ }
+
+
+ public void commit() throws SQLException {
+ if (transactionContext != null) {
+ throw new SQLException("Commit can not be set while enrolled in a transaction");
+ }
+ super.commit();
+ }
+
+ public void rollback() throws SQLException {
+ if (transactionContext != null) {
+ throw new SQLException("Commit can not be set while enrolled in a transaction");
+ }
+ super.rollback();
+ }
+
+
+ public void setReadOnly(boolean readOnly) throws SQLException {
+ if (transactionContext != null) {
+ throw new SQLException("Read-only can not be set while enrolled in a transaction");
+ }
+ super.setReadOnly(readOnly);
+ }
+
+ //
+ // Methods for accessing the delegate connection
+ //
+
+ /**
+ * If false, getDelegate() and getInnermostDelegate() will return null.
+ * @return if false, getDelegate() and getInnermostDelegate() will return null
+ */
+ public boolean isAccessToUnderlyingConnectionAllowed() {
+ return accessToUnderlyingConnectionAllowed;
+ }
+
+ public Connection getDelegate() {
+ if (isAccessToUnderlyingConnectionAllowed()) {
+ return getDelegateInternal();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Gets the actual delegate without checking the isAccessToUnderlyingConnectionAllowed() flag. This method is for internal use only.
+ * @return the delegate of this connection
+ */
+ protected Connection getDelegateInternal() {
+ return super.getDelegate();
+ }
+
+ public Connection getInnermostDelegate() {
+ if (isAccessToUnderlyingConnectionAllowed()) {
+ return super.getInnermostDelegate();
+ } else {
+ return null;
+ }
+ }
+}
Index: src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java
===================================================================
--- src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java (revision 0)
+++ src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java (revision 0)
@@ -0,0 +1,116 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.commons.dbcp.managed;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.TransactionManager;
+import javax.transaction.Transaction;
+import javax.transaction.SystemException;
+import javax.transaction.Status;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * TransactionRegistry tracks Connections and XAResources in a transacted environment for a single XAConnectionFactory.
+ *
+ * The TransactionRegistry hides the details of transaction processing from the existing DBCP pooling code, and gives
+ * the ManagedConnection a way to enlist connections in a tranaction, allowing for the maximal resue of DBCP.
+ *
+ * @author Dain Sundstrom
+ * @version $Revision$
+ */
+public class TransactionRegistry {
+ private TransactionManager transactionManager;
+ private Map caches = new WeakHashMap();
+ private Map xaResources = new WeakHashMap();
+
+ /**
+ * Creates a TransactionRegistry for the specified transaction manager.
+ * @param transactionManager the transaction manager used to enlist connections
+ */
+ public TransactionRegistry(TransactionManager transactionManager) {
+ this.transactionManager = transactionManager;
+ }
+
+ /**
+ * Registers the association between a Connection and a XAResource. When a conection
+ * is enlisted in a transaction, it is acutally the XAResource that is given to the transaction
+ * manager.
+ *
+ * @param connection the JDBC connection
+ * @param xaResource the XAResource which managed the connection within a transaction
+ */
+ public synchronized void registerConnection(Connection connection, XAResource xaResource) {
+ if (connection == null) throw new NullPointerException("connection is null");
+ if (xaResource == null) throw new NullPointerException("xaResource is null");
+ xaResources.put(connection, xaResource);
+ }
+
+ /**
+ * Gets the XAResource registered for the connection.
+ * @param connection the connection
+ * @return the XAResource registered for the connection; never null
+ * @throws SQLException if the connection does not have a registered XAResource
+ */
+ public synchronized XAResource getXAResource(Connection connection) throws SQLException {
+ if (connection == null) throw new NullPointerException("connection is null");
+ XAResource xaResource = (XAResource) xaResources.get(connection);
+ if (xaResource == null) {
+ throw new SQLException("Connection does not have a registered XAResource " + connection);
+ }
+ return xaResource;
+ }
+
+ /**
+ * Gets the active TransactionContext or null if not Transaction is active.
+ * @return the active TransactionContext or null if not Transaction is active
+ * @throws SQLException if an error occurs while fetching the transaction
+ */
+ public TransactionContext getActiveTransactionContext() throws SQLException {
+ Transaction transaction = null;
+ try {
+ transaction = transactionManager.getTransaction();
+
+ // was there a transaction?
+ if (transaction == null) {
+ return null;
+ }
+
+ // is it active
+ int status = transaction.getStatus();
+ if (status != Status.STATUS_ACTIVE && status != Status.STATUS_MARKED_ROLLBACK) {
+ return null;
+ }
+ } catch (SystemException e) {
+ throw (SQLException) new SQLException("Unable to determine current transaction ").initCause(e);
+ }
+
+ // register the the context (or create a new one)
+ synchronized (this) {
+ TransactionContext cache = (TransactionContext) caches.get(transaction);
+ if (cache == null) {
+ cache = new TransactionContext(this, transaction);
+ caches.put(transaction, cache);
+ }
+ return cache;
+ }
+ }
+}
+
Index: pom.xml
===================================================================
--- pom.xml (revision 553027)
+++ pom.xml (working copy)
@@ -122,6 +122,10 @@
Wayne Woodfield
+
+ Dain Sundstrom
+ dain@apache.org
+
@@ -138,6 +142,14 @@
test
+
+
+ org.apache.geronimo.specs
+ geronimo-jta_1.1_spec
+ 1.1
+ true
+
+
tomcat
@@ -160,7 +172,15 @@
1.0.4
test
-
+
+
+
+ org.apache.geronimo.modules
+ geronimo-transaction
+ 1.2-beta
+ test
+
+
@@ -215,6 +235,9 @@
org/apache/commons/dbcp/datasources/TestFactory.java
org/apache/commons/dbcp/datasources/TestPerUserPoolDataSource.java
org/apache/commons/dbcp/datasources/TestSharedPoolDataSource.java
+
+ org/apache/commons/dbcp/managed/TestManagedDataSource.java
+ org/apache/commons/dbcp/managed/TestManagedDataSourceInTx.java