Return-Path: Delivered-To: apmail-jakarta-commons-dev-archive@www.apache.org Received: (qmail 81385 invoked from network); 5 Jul 2007 14:13:24 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 5 Jul 2007 14:13:24 -0000 Received: (qmail 19063 invoked by uid 500); 5 Jul 2007 14:13:22 -0000 Delivered-To: apmail-jakarta-commons-dev-archive@jakarta.apache.org Received: (qmail 19015 invoked by uid 500); 5 Jul 2007 14:13:22 -0000 Mailing-List: contact commons-dev-help@jakarta.apache.org; run by ezmlm Precedence: bulk List-Unsubscribe: List-Help: List-Post: List-Id: "Jakarta Commons Developers List" Reply-To: "Jakarta Commons Developers List" Delivered-To: mailing list commons-dev@jakarta.apache.org Received: (qmail 19000 invoked by uid 500); 5 Jul 2007 14:13:22 -0000 Received: (qmail 18973 invoked by uid 99); 5 Jul 2007 14:13:22 -0000 Received: from herse.apache.org (HELO herse.apache.org) (140.211.11.133) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Jul 2007 07:13:21 -0700 X-ASF-Spam-Status: No, hits=-99.5 required=10.0 tests=ALL_TRUSTED,NO_REAL_NAME X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 05 Jul 2007 07:13:16 -0700 Received: by eris.apache.org (Postfix, from userid 65534) id A5F341A981A; Thu, 5 Jul 2007 07:12:55 -0700 (PDT) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r553511 [1/2] - in /jakarta/commons/proper/dbcp/trunk: ./ src/java/org/apache/commons/dbcp/managed/ src/test/org/apache/commons/dbcp/ src/test/org/apache/commons/dbcp/managed/ xdocs/ Date: Thu, 05 Jul 2007 14:12:49 -0000 To: commons-cvs@jakarta.apache.org From: psteitz@apache.org X-Mailer: svnmailer-1.1.0 Message-Id: <20070705141255.A5F341A981A@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: psteitz Date: Thu Jul 5 07:12:47 2007 New Revision: 553511 URL: http://svn.apache.org/viewvc?view=rev&rev=553511 Log: Added support for pooling managed connections. JIRA: DBCP-228 Patch provided by Dain Sundstrom. Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedConnection.java jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContext.java jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContextListener.java jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/XAConnectionFactory.java jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterCallableStatement.java jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/managed/ jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/managed/TestManagedDataSourceInTx.java Modified: jakarta/commons/proper/dbcp/trunk/build.properties.sample jakarta/commons/proper/dbcp/trunk/build.xml jakarta/commons/proper/dbcp/trunk/pom.xml jakarta/commons/proper/dbcp/trunk/project.properties jakarta/commons/proper/dbcp/trunk/project.xml jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterConnection.java jakarta/commons/proper/dbcp/trunk/xdocs/changes.xml Modified: jakarta/commons/proper/dbcp/trunk/build.properties.sample URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/build.properties.sample?view=diff&rev=553511&r1=553510&r2=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/build.properties.sample (original) +++ jakarta/commons/proper/dbcp/trunk/build.properties.sample Thu Jul 5 07:12:47 2007 @@ -50,3 +50,7 @@ # Commons logging - dependency of naming jars logging.home=${repository}/commons-logging/jars logging.jar=${logging.home}/commons-logging-1.0.4.jar + +# JTA - needed for managed connections +jta-spec.jar=${repository}/org.apache.geronimo.specs/jars/geronimo-jta_1.1_spec-1.1.jar +jta-impl.jar=${repository}/org.apache.geronimo.modules/jars/geronimo-transaction-1.2-beta.jar Modified: jakarta/commons/proper/dbcp/trunk/build.xml URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/build.xml?view=diff&rev=553511&r1=553510&r2=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/build.xml (original) +++ jakarta/commons/proper/dbcp/trunk/build.xml Thu Jul 5 07:12:47 2007 @@ -46,7 +46,7 @@ + ${xerces.jar}:${xml-apis.jar}:${jta-spec.jar}:${jta-impl.jar}"/> Modified: jakarta/commons/proper/dbcp/trunk/pom.xml URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/pom.xml?view=diff&rev=553511&r1=553510&r2=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/pom.xml (original) +++ jakarta/commons/proper/dbcp/trunk/pom.xml Thu Jul 5 07:12:47 2007 @@ -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 Modified: jakarta/commons/proper/dbcp/trunk/project.properties URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/project.properties?view=diff&rev=553511&r1=553510&r2=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/project.properties (original) +++ jakarta/commons/proper/dbcp/trunk/project.properties Thu Jul 5 07:12:47 2007 @@ -23,16 +23,15 @@ maven.changes.issue.template=http://issues.apache.org/jira/browse/%ISSUE% maven.javadoc.author=false -maven.javadoc.links=http://java.sun.com/j2se/1.5.0/docs/api,http://jakarta.apache.org/commons/pool/api-1.3 - +maven.javadoc.links=http://java.sun.com/j2se/1.5.0/docs/api,http://jakarta.apache.org/commons/pool/api-1.3,http://java.sun.com/j2ee/sdk_1.3/techdocs/api maven.xdoc.date=bottom maven.xdoc.poweredby.image=maven-feather.png maven.xdoc.version=${pom.currentVersion} maven.xdoc.developmentProcessUrl=http://jakarta.apache.org/commons/charter.html # JDK level -maven.compile.source=1.3 -maven.compile.target=1.3 +maven.compile.source=1.4 +maven.compile.target=1.4 # Merge in a file containing just Built-By attribute maven.jar.manifest=${basedir}/manifestMods.txt Modified: jakarta/commons/proper/dbcp/trunk/project.xml URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/project.xml?view=diff&rev=553511&r1=553510&r2=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/project.xml (original) +++ jakarta/commons/proper/dbcp/trunk/project.xml Thu Jul 5 07:12:47 2007 @@ -192,6 +192,24 @@ test + + + + org.apache.geronimo.specs + geronimo-jta_1.1_spec + 1.1 + + + + + org.apache.geronimo.modules + geronimo-transaction + 1.2-beta + + test + Only required for managed connections tests + + Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/DataSourceXAConnectionFactory.java Thu Jul 5 07:12:47 2007 @@ -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; + } +} Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/LocalXAConnectionFactory.java Thu Jul 5 07:12:47 2007 @@ -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; + } + } + +} Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedConnection.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedConnection.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedConnection.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedConnection.java Thu Jul 5 07:12:47 2007 @@ -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; + } + } +} Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/ManagedDataSource.java Thu Jul 5 07:12:47 2007 @@ -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; + } +} Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContext.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContext.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContext.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContext.java Thu Jul 5 07:12:47 2007 @@ -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); + } + } +} Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContextListener.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContextListener.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContextListener.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionContextListener.java Thu Jul 5 07:12:47 2007 @@ -0,0 +1,33 @@ +/** + * + * 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; + +/** + * A listener for transaction completion events. + * + * @author Dain Sundstrom + * @version $Revision$ + */ +public interface TransactionContextListener { + /** + * Occurs after the transaction commits or rolls back. + * @param transactionContext the transaction context that completed + * @param commited true if the transaction committed; false otherwise + */ + void afterCompletion(TransactionContext transactionContext, boolean commited); +} Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/TransactionRegistry.java Thu Jul 5 07:12:47 2007 @@ -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; + } + } +} + Added: jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/XAConnectionFactory.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/XAConnectionFactory.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/XAConnectionFactory.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp/managed/XAConnectionFactory.java Thu Jul 5 07:12:47 2007 @@ -0,0 +1,57 @@ +/** + * + * 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 java.sql.Connection; +import java.sql.SQLException; + +/** + * XAConnectionFactory is an extension of ConnectionFactory used to create connections + * in a transaction managed environment. The XAConnectionFactory opperates like a normal + * ConnectionFactory except an TransactionRegistry is provided from which the XAResource + * for a connection can be obtained. This allows the existing DBCP pool code to work with + * XAConnections and gives a the ManagedConnection a way to enlist a connection in the + * the transaction. + * + * @author Dain Sundstrom + * @author Rodney Waldhoff + * @version $Revision$ + */ +public interface XAConnectionFactory extends ConnectionFactory { + /** + * Gets the TransactionRegistry for this connection factory which contains a the + * XAResource for every connection created by this factory. + * + * @return the transaction registry for this connection factory + */ + TransactionRegistry getTransactionRegistry(); + + /** + * Create a new {@link java.sql.Connection} in an implementation specific fashion. + *

+ * 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; +} Added: jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterCallableStatement.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterCallableStatement.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterCallableStatement.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterCallableStatement.java Thu Jul 5 07:12:47 2007 @@ -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; + } +} Modified: jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterConnection.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterConnection.java?view=diff&rev=553511&r1=553510&r2=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterConnection.java (original) +++ jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/TesterConnection.java Thu Jul 5 07:12:47 2007 @@ -136,12 +136,12 @@ if ("warning".equals(sql)) { setWarnings(new SQLWarning("warning in prepareCall")); } - return null; + return new TesterCallableStatement(this); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { checkOpen(); - return null; + return new TesterCallableStatement(this); } public PreparedStatement prepareStatement(String sql) throws SQLException { Added: jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java?view=auto&rev=553511 ============================================================================== --- jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java (added) +++ jakarta/commons/proper/dbcp/trunk/src/test/org/apache/commons/dbcp/managed/TestManagedDataSource.java Thu Jul 5 07:12:47 2007 @@ -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(); + } + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org For additional commands, e-mail: commons-dev-help@jakarta.apache.org