From 6df45cd1d73b3df6b5018bb9ee295fc1d99a95c2 Mon Sep 17 00:00:00 2001 From: wenshao Date: Thu, 12 May 2011 05:59:20 +0000 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81XA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://code.alibabatech.com/svn/druid/trunk@27 b9813039-fb51-4c41-a8b9-e21c2acb5095 --- .../druid/pool/DruidDataAbstractSource.java | 24 +- .../alibaba/druid/pool/DruidDataSource.java | 2 +- .../druid/pool/PoolableConnection.java | 12 +- .../xa/DataSourceXAConnectionFactory.java | 99 ++++++ .../druid/pool/xa/DruidXADataSource.java | 165 +++++++++- .../pool/xa/LocalXAConnectionFactory.java | 285 ++++++++++++++++++ .../druid/pool/xa/ManagedConnection.java | 236 +++++++++++++++ .../druid/pool/xa/ManagedDataSource.java | 62 ++++ 8 files changed, 861 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/alibaba/druid/pool/xa/DataSourceXAConnectionFactory.java create mode 100644 src/main/java/com/alibaba/druid/pool/xa/LocalXAConnectionFactory.java create mode 100644 src/main/java/com/alibaba/druid/pool/xa/ManagedConnection.java create mode 100644 src/main/java/com/alibaba/druid/pool/xa/ManagedDataSource.java diff --git a/src/main/java/com/alibaba/druid/pool/DruidDataAbstractSource.java b/src/main/java/com/alibaba/druid/pool/DruidDataAbstractSource.java index 220625eb47..ba8018571d 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidDataAbstractSource.java +++ b/src/main/java/com/alibaba/druid/pool/DruidDataAbstractSource.java @@ -62,14 +62,14 @@ public abstract class DruidDataAbstractSource implements DataSource, DataSourceP */ public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L; - private boolean defaultAutoCommit = false; - private Boolean defaultReadOnly; - private Integer defaultTransactionIsolation; - private String defaultCatalog = null; + protected boolean defaultAutoCommit = false; + protected Boolean defaultReadOnly; + protected Integer defaultTransactionIsolation; + protected String defaultCatalog = null; protected String name; - protected String user; + protected String username; protected String password; protected String jdbcUrl; protected String driverClass; @@ -434,7 +434,7 @@ public abstract class DruidDataAbstractSource implements DataSource, DataSourceP } public String getUsername() { - return user; + return username; } public void setUsername(String user) { @@ -442,7 +442,7 @@ public abstract class DruidDataAbstractSource implements DataSource, DataSourceP throw new UnsupportedOperationException(); } - this.user = user; + this.username = user; } public String getPassword() { @@ -501,7 +501,7 @@ public abstract class DruidDataAbstractSource implements DataSource, DataSourceP public PrintWriter getLogWriter() { return logWriter; } - + protected void printStackTrace(Throwable e) { if (logWriter != null) { e.printStackTrace(logWriter); @@ -533,8 +533,12 @@ public abstract class DruidDataAbstractSource implements DataSource, DataSourceP return false; } - protected void initConnectionFactory() { - connectionFactory = new DruidPoolConnectionFactory(this); + protected void initConnectionFactory() throws SQLException { + connectionFactory = createConnectionFactory(); + } + + protected ConnectionFactory createConnectionFactory() throws SQLException { + return new DruidPoolConnectionFactory(this); } public Driver getDriver() { diff --git a/src/main/java/com/alibaba/druid/pool/DruidDataSource.java b/src/main/java/com/alibaba/druid/pool/DruidDataSource.java index b3779b2256..46f9d48379 100644 --- a/src/main/java/com/alibaba/druid/pool/DruidDataSource.java +++ b/src/main/java/com/alibaba/druid/pool/DruidDataSource.java @@ -247,7 +247,7 @@ public class DruidDataSource extends DruidDataAbstractSource implements DruidDat /** * 回收连接 */ - void recycle(PoolableConnection pooledConnection) throws SQLException { + protected void recycle(PoolableConnection pooledConnection) throws SQLException { final Connection conn = pooledConnection.getConnection(); try { if (activeConnectionTraceEnable) { diff --git a/src/main/java/com/alibaba/druid/pool/PoolableConnection.java b/src/main/java/com/alibaba/druid/pool/PoolableConnection.java index 82c1c6bdfb..ba3e681660 100644 --- a/src/main/java/com/alibaba/druid/pool/PoolableConnection.java +++ b/src/main/java/com/alibaba/druid/pool/PoolableConnection.java @@ -44,10 +44,10 @@ import com.alibaba.druid.pool.PreparedStatementPool.MethodType; /** * @author wenshao */ -public final class PoolableConnection implements PooledConnection, Connection { +public class PoolableConnection implements PooledConnection, Connection { - private Connection conn; - private ConnectionHolder holder; + protected Connection conn; + protected ConnectionHolder holder; public PoolableConnection(ConnectionHolder holder){ this.conn = holder.getConnection(); @@ -413,8 +413,10 @@ public final class PoolableConnection implements PooledConnection, Connection { @Override public boolean isClosed() throws SQLException { - checkOpen(); - + if (holder == null) { + return true; + } + return conn.isClosed(); } diff --git a/src/main/java/com/alibaba/druid/pool/xa/DataSourceXAConnectionFactory.java b/src/main/java/com/alibaba/druid/pool/xa/DataSourceXAConnectionFactory.java new file mode 100644 index 0000000000..136ffcd144 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/xa/DataSourceXAConnectionFactory.java @@ -0,0 +1,99 @@ +package com.alibaba.druid.pool.xa; + +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; + protected String username; + protected String password; + + /** + * 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) { + this(transactionManager, xaDataSource, null, null); + } + + /** + * 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 + * @param username the username used for authenticating new connections or null for unauthenticated + * @param password the password used for authenticating new connections + */ + public DataSourceXAConnectionFactory(TransactionManager transactionManager, XADataSource xaDataSource, String username, String password) { + 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; + this.username = username; + this.password = password; + } + + /** + * Gets the username used to authenticate new connections. + * @return the user name or null if unauthenticated connections are used + */ + public String getUsername() { + return username; + } + + /** + * Sets the username used to authenticate new connections. + * @param username the username used for authenticating the connection or null for unauthenticated + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Sets the password used to authenticate new connections. + * @param password the password used for authenticating the connection or null for unauthenticated + */ + public void setPassword(String password) { + this.password = password; + } + + public TransactionRegistry getTransactionRegistry() { + return transactionRegistry; + } + + public Connection createConnection() throws SQLException { + // create a new XAConection + XAConnection xaConnection; + if (username == null) { + xaConnection = xaDataSource.getXAConnection(); + } else { + xaConnection = xaDataSource.getXAConnection(username, password); + } + + // 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; + } +} + diff --git a/src/main/java/com/alibaba/druid/pool/xa/DruidXADataSource.java b/src/main/java/com/alibaba/druid/pool/xa/DruidXADataSource.java index 673c319720..c30a56c927 100644 --- a/src/main/java/com/alibaba/druid/pool/xa/DruidXADataSource.java +++ b/src/main/java/com/alibaba/druid/pool/xa/DruidXADataSource.java @@ -19,21 +19,170 @@ import java.sql.SQLException; import javax.sql.XAConnection; import javax.sql.XADataSource; +import javax.transaction.TransactionManager; +import org.apache.commons.dbcp.AbandonedConfig; +import org.apache.commons.dbcp.PoolableConnectionFactory; +import org.apache.commons.dbcp.PoolingDataSource; +import org.apache.commons.dbcp.managed.ManagedDataSource; +import org.apache.commons.dbcp.managed.PoolableManagedConnectionFactory; +import org.apache.commons.pool.KeyedObjectPoolFactory; + +import com.alibaba.druid.pool.ConnectionFactory; import com.alibaba.druid.pool.DruidDataSource; -public class DruidXADataSource extends DruidDataSource implements XADataSource { +public class DruidXADataSource extends DruidDataSource { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - @Override - public XAConnection getXAConnection() throws SQLException { - return null; + /** Transaction Registry */ + private TransactionRegistry transactionRegistry; + /** Transaction Manager */ + private transient TransactionManager transactionManager; + /** XA datasource class name */ + private String xaDataSource; + /** XA datasource instance */ + private XADataSource xaDataSourceInstance; + + /** + * Gets the XADataSource instance used by the XAConnectionFactory. + * + * @return the XADataSource + */ + public synchronized XADataSource getXaDataSourceInstance() { + return xaDataSourceInstance; } - @Override - public XAConnection getXAConnection(String user, String password) throws SQLException { - return null; + /** + *

+ * Sets the XADataSource instance used by the XAConnectionFactory. + *

+ *

+ * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first + * time one of the following methods is invoked: getConnection, setLogwriter, + * setLoginTimeout, getLoginTimeout, getLogWriter. + *

+ * + * @param xaDataSourceInstance XADataSource instance + */ + public synchronized void setXaDataSourceInstance(XADataSource xaDataSourceInstance) { + this.xaDataSourceInstance = xaDataSourceInstance; + xaDataSource = xaDataSourceInstance == null ? null : xaDataSourceInstance.getClass().getName(); + } + + /** + * Gets the required transaction manager property. + * + * @return the transaction manager used to enlist connections + */ + public TransactionManager getTransactionManager() { + return transactionManager; + } + + /** + * Gets the transaction registry. + * + * @return the transaction registry associating XAResources with managed connections + */ + protected synchronized TransactionRegistry getTransactionRegistry() { + return transactionRegistry; + } + + /** + * Sets the required transaction manager property. + * + * @param transactionManager the transaction manager used to enlist connections + */ + public void setTransactionManager(TransactionManager transactionManager) { + this.transactionManager = transactionManager; + } + + /** + * Gets the optional XADataSource class name. + * + * @return the optional XADataSource class name + */ + public synchronized String getXADataSource() { + return xaDataSource; + } + + /** + * Sets the optional XADataSource class name. + * + * @param xaDataSource the optional XADataSource class name + */ + public synchronized void setXADataSource(String xaDataSource) { + this.xaDataSource = xaDataSource; + } + + protected ConnectionFactory createConnectionFactory() throws SQLException { + if (transactionManager == null) { + throw new SQLException("Transaction manager must be set before a connection can be created"); + } + + // If xa data source is not specified a DriverConnectionFactory is created and wrapped with a + // LocalXAConnectionFactory + if (xaDataSource == null) { + ConnectionFactory connectionFactory = super.createConnectionFactory(); + XAConnectionFactory xaConnectionFactory = new LocalXAConnectionFactory(getTransactionManager(), + connectionFactory); + transactionRegistry = xaConnectionFactory.getTransactionRegistry(); + return xaConnectionFactory; + } + + // Create the XADataSource instance using the configured class name if it has not been set + if (xaDataSourceInstance == null) { + Class xaDataSourceClass = null; + try { + xaDataSourceClass = Class.forName(xaDataSource); + } catch (Throwable t) { + String message = "Cannot load XA data source class '" + xaDataSource + "'"; + throw (SQLException) new SQLException(message).initCause(t); + } + + try { + xaDataSourceInstance = (XADataSource) xaDataSourceClass.newInstance(); + } catch (Throwable t) { + String message = "Cannot create XA data source of class '" + xaDataSource + "'"; + throw (SQLException) new SQLException(message).initCause(t); + } + } + + // finally, create the XAConectionFactory using the XA data source + XAConnectionFactory xaConnectionFactory = new DataSourceXAConnectionFactory(getTransactionManager(), + xaDataSourceInstance, username, + password); + transactionRegistry = xaConnectionFactory.getTransactionRegistry(); + return xaConnectionFactory; + } + + /** + * Creates the PoolableConnectionFactory and attaches it to the connection pool. + * + * @param driverConnectionFactory JDBC connection factory created by {@link #createConnectionFactory()} + * @param statementPoolFactory statement pool factory (null if statement pooling is turned off) + * @param abandonedConfig abandoned connection tracking configuration (null if no tracking) + * @throws SQLException if an error occurs creating the PoolableConnectionFactory + */ + protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory, + KeyedObjectPoolFactory statementPoolFactory, + AbandonedConfig abandonedConfig) throws SQLException { + // PoolableConnectionFactory connectionFactory = null; + // try { + // connectionFactory = new PoolableManagedConnectionFactory((XAConnectionFactory) driverConnectionFactory, + // connectionPool, statementPoolFactory, + // validationQuery, validationQueryTimeout, + // connectionInitSqls, defaultReadOnly, + // defaultAutoCommit, defaultTransactionIsolation, + // defaultCatalog, abandonedConfig); + // validateConnectionFactory(connectionFactory); + // } catch (RuntimeException e) { + // throw e; + // } catch (Exception e) { + // throw (SQLException) new SQLException("Cannot create PoolableConnectionFactory (" + e.getMessage() + + // ")").initCause(e); + // } + throw new SQLException("TODO"); } } diff --git a/src/main/java/com/alibaba/druid/pool/xa/LocalXAConnectionFactory.java b/src/main/java/com/alibaba/druid/pool/xa/LocalXAConnectionFactory.java new file mode 100644 index 0000000000..a9352bc70e --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/xa/LocalXAConnectionFactory.java @@ -0,0 +1,285 @@ +package com.alibaba.druid.pool.xa; + +import java.sql.Connection; +import java.sql.SQLException; + +import javax.transaction.TransactionManager; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +import com.alibaba.druid.pool.ConnectionFactory; + +/** + * 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 connection + Connection connection = connectionFactory.createConnection(); + + // create a XAResource to manage the connection during XA transactions + 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 currentXid; + 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 currentXid; + } + + /** + * 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 enlisted in another transaction, 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.currentXid != 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.currentXid = xid; + } else if (flag == XAResource.TMRESUME) { + if (xid != this.currentXid) { + throw new XAException("Attempting to resume in different transaction: expected " + this.currentXid + + ", 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 enlisted in another transaction + */ + public synchronized void end(Xid xid, int flag) throws XAException { + if (xid == null) throw new NullPointerException("xid is null"); + if (!this.currentXid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.currentXid + + ", 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 connection, 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.currentXid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.currentXid + + ", 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.currentXid = null; + } + } + + /** + * Rolls back 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.currentXid.equals(xid)) throw new XAException("Invalid Xid: expected " + this.currentXid + + ", but was " + xid); + + try { + connection.rollback(); + } catch (SQLException e) { + throw (XAException) new XAException().initCause(e); + } finally { + try { + connection.setAutoCommit(originalAutoCommit); + } catch (SQLException e) { + } + this.currentXid = 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.currentXid.equals(xid)) { + this.currentXid = 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 recovery 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; + } + } + +} diff --git a/src/main/java/com/alibaba/druid/pool/xa/ManagedConnection.java b/src/main/java/com/alibaba/druid/pool/xa/ManagedConnection.java new file mode 100644 index 0000000000..c599d99903 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/xa/ManagedConnection.java @@ -0,0 +1,236 @@ +package com.alibaba.druid.pool.xa; + +import java.sql.Connection; +import java.sql.SQLException; + +import com.alibaba.druid.pool.ConnectionHolder; +import com.alibaba.druid.pool.PoolableConnection; + +/** + * ManagedConnection is responsible for managing a database connection in a transactional environment (typically called + * "Container Managed"). A managed connection operates like any other connection when no global 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 enlisted in the transaction, but + * some vendors do not properly implement this. When enlisted 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 PoolableConnection { + + private final TransactionRegistry transactionRegistry; + private final boolean accessToUnderlyingConnectionAllowed; + private TransactionContext transactionContext; + private boolean isSharedConnection; + + public ManagedConnection(ConnectionHolder holder, TransactionRegistry transactionRegistry, + boolean accessToUnderlyingConnectionAllowed) throws SQLException{ + super(holder); + 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 transaction 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 invalidate 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; + // } + // } + // } + throw new SQLException("TODO"); + } + + public void close() throws SQLException { + if (transactionContext == null) { + super.close(); + } + } + + /** + * Delegates to {@link ManagedConnection#transactionComplete()} for transaction completion events. + */ + protected class CompletionListener implements TransactionContextListener { + + public void afterCompletion(TransactionContext completedContext, boolean commited) { + if (completedContext == 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 (isClosed() && 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 closed 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 callback so there is no +// // way to report the error +// } finally { +// _closed = true; +// } +// } + throw new RuntimeException("TODO"); + } + + // + // 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; + } + + @Deprecated + public Connection getDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return getRawConnection(); + } else { + return null; + } + } + + @Deprecated + public Connection getInnermostDelegate() { + if (isAccessToUnderlyingConnectionAllowed()) { + return getRawConnection(); + } else { + return null; + } + } +} diff --git a/src/main/java/com/alibaba/druid/pool/xa/ManagedDataSource.java b/src/main/java/com/alibaba/druid/pool/xa/ManagedDataSource.java new file mode 100644 index 0000000000..e9c8dda9f1 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/xa/ManagedDataSource.java @@ -0,0 +1,62 @@ +package com.alibaba.druid.pool.xa; + +import java.sql.Connection; +import java.sql.SQLException; + + +/** + * The ManagedDataSource is a PoolingDataSource that creates ManagedConnections. + * + * @author Dain Sundstrom + * @version $Revision$ + */ +public class ManagedDataSource { + 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(TransactionRegistry transactionRegistry) { + 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; + throw new SQLException("TODO"); // TODO + } +} +