bitronix.tm.resource.jdbc.PoolingDataSource Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2006-2013 Bitronix Software (http://www.bitronix.be)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package bitronix.tm.resource.jdbc;
import bitronix.tm.internal.XAResourceHolderState;
import bitronix.tm.recovery.RecoveryException;
import bitronix.tm.resource.ResourceConfigurationException;
import bitronix.tm.resource.ResourceObjectFactory;
import bitronix.tm.resource.ResourceRegistrar;
import bitronix.tm.resource.common.RecoveryXAResourceHolder;
import bitronix.tm.resource.common.ResourceBean;
import bitronix.tm.resource.common.XAPool;
import bitronix.tm.resource.common.XAResourceProducer;
import bitronix.tm.utils.ManagementRegistrar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.sql.DataSource;
import javax.sql.XADataSource;
import javax.transaction.xa.XAResource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Implementation of a JDBC {@link DataSource} wrapping vendor's {@link XADataSource} implementation.
*
* @author Ludovic Orban
* @author Brett Wooldridge
*/
@SuppressWarnings("serial")
public class PoolingDataSource extends ResourceBean implements DataSource, XAResourceProducer, PoolingDataSourceMBean {
private final static Logger log = LoggerFactory.getLogger(PoolingDataSource.class);
private volatile transient XAPool pool;
private volatile transient XADataSource xaDataSource;
private volatile transient RecoveryXAResourceHolder recoveryXAResourceHolder;
private volatile transient Connection recoveryConnectionHandle;
private volatile transient Map xaResourceHolderMap;
private volatile String testQuery;
private volatile boolean enableJdbc4ConnectionTest;
private volatile int connectionTestTimeout;
private volatile int preparedStatementCacheSize = 0;
private volatile String isolationLevel;
private volatile String cursorHoldability;
private volatile String localAutoCommit;
private volatile String jmxName;
private final List connectionCustomizers = new CopyOnWriteArrayList();
public PoolingDataSource() {
xaResourceHolderMap = new ConcurrentHashMap();
}
/**
* Initializes the pool by creating the initial amount of connections.
*/
@Override
public synchronized void init() {
if (this.pool != null)
return;
try {
buildXAPool();
this.jmxName = "bitronix.tm:type=JDBC,UniqueName=" + ManagementRegistrar.makeValidName(getUniqueName());
ManagementRegistrar.register(jmxName, this);
} catch (Exception ex) {
throw new ResourceConfigurationException("cannot create JDBC datasource named " + getUniqueName(), ex);
}
}
private void buildXAPool() throws Exception {
if (pool != null)
return;
if (log.isDebugEnabled()) { log.debug("building XA pool for " + getUniqueName() + " with " + getMinPoolSize() + " connection(s)"); }
pool = new XAPool(this, this, xaDataSource);
boolean builtXaFactory = false;
if (xaDataSource == null) {
xaDataSource = (XADataSource) pool.getXAFactory();
builtXaFactory = true;
}
try {
ResourceRegistrar.register(this);
} catch (RecoveryException ex) {
if (builtXaFactory) xaDataSource = null;
pool = null;
throw ex;
}
}
/**
* @return the wrapped XADataSource.
*/
public XADataSource getXaDataSource() {
return xaDataSource;
}
/**
* Inject a pre-configured XADataSource instead of relying on className and driverProperties
* to build one. Upon deserialization the xaDataSource will be null and will need to be
* manually re-injected.
* @param xaDataSource the pre-configured XADataSource.
*/
public void setXaDataSource(XADataSource xaDataSource) {
this.xaDataSource = xaDataSource;
}
/**
* @return the query that will be used to test connections.
*/
public String getTestQuery() {
return testQuery;
}
/**
* When set, the specified query will be executed on the connection acquired from the pool before being handed to
* the caller. The connections won't be tested when not set. Default value is null.
* @param testQuery the query that will be used to test connections.
*/
public void setTestQuery(String testQuery) {
this.testQuery = testQuery;
}
/**
* When set and the underlying JDBC driver supports JDBC 4 isValid(), a Connection.isValid() call
* is performed to test the connection before handing it to the caller.
* If both testQuery and enableJdbc4ConnectionTest are set, enableJdbc4ConnectionTest takes precedence.
* @param enableJdbc4ConnectionTest true if JDBC 4 isValid() testing should be performed, false otherwise.
*/
public void setEnableJdbc4ConnectionTest(boolean enableJdbc4ConnectionTest) {
this.enableJdbc4ConnectionTest = enableJdbc4ConnectionTest;
}
/**
* @return true if JDBC 4 isValid() testing should be performed, false otherwise.
*/
public boolean isEnableJdbc4ConnectionTest() {
return enableJdbc4ConnectionTest;
}
/**
* Determines how many seconds the connection test logic
* will wait for a response from the database.
* @param connectionTestTimeout connection timeout
*/
public void setConnectionTestTimeout(int connectionTestTimeout) {
this.connectionTestTimeout = connectionTestTimeout;
}
/**
* @return how many seconds each connection test will wait for a response.
*/
public int getConnectionTestTimeout() {
return connectionTestTimeout;
}
/**
* @return how many seconds each connection test will wait for a response,
* bounded above by the acquisition timeout.
*/
public int getEffectiveConnectionTestTimeout() {
int t1 = getConnectionTestTimeout();
int t2 = getAcquisitionTimeout();
if ((t1 > 0) && (t2 > 0)) {
return Math.min(t1, t2);
} else {
return Math.max(t1, t2);
}
}
/**
* @return the target maximum prepared statement cache size.
*/
public int getPreparedStatementCacheSize() {
return preparedStatementCacheSize;
}
/**
* Set the target maximum size of the prepared statement cache. In
* reality under certain unusual conditions the cache may temporarily
* drift higher in size.
* @param preparedStatementCacheSize the target maximum prepared statement cache size.
*/
public void setPreparedStatementCacheSize(int preparedStatementCacheSize) {
this.preparedStatementCacheSize = preparedStatementCacheSize;
}
/**
* @return the default isolation level.
*/
public String getIsolationLevel() {
return isolationLevel;
}
/**
* Set the default isolation level for connections.
* @param isolationLevel the default isolation level.
*/
public void setIsolationLevel(String isolationLevel) {
this.isolationLevel = isolationLevel;
}
/**
* @return cursorHoldability the default cursor holdability.
*/
public String getCursorHoldability() {
return cursorHoldability;
}
/**
* Set the default cursor holdability for connections.
* @param cursorHoldability the default cursor holdability.
*/
public void setCursorHoldability(String cursorHoldability) {
this.cursorHoldability = cursorHoldability;
}
/**
* @return localAutoCommit the default local transactions autocommit mode.
*/
public String getLocalAutoCommit() {
return localAutoCommit;
}
/**
* Set the default local transactions autocommit mode.
* @param localAutoCommit the default local transactions autocommit mode.
*/
public void setLocalAutoCommit(String localAutoCommit) {
this.localAutoCommit = localAutoCommit;
}
public void addConnectionCustomizer(ConnectionCustomizer connectionCustomizer) {
connectionCustomizers.add(connectionCustomizer);
}
public void removeConnectionCustomizer(ConnectionCustomizer connectionCustomizer) {
Iterator it = connectionCustomizers.iterator();
while (it.hasNext()) {
ConnectionCustomizer customizer = it.next();
if (customizer == connectionCustomizer) {
it.remove();
return;
}
}
}
void fireOnAcquire(Connection connection) {
for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) {
try {
connectionCustomizer.onAcquire(connection, getUniqueName());
} catch (Exception ex) {
log.warn("ConnectionCustomizer.onAcquire() failed for " + connectionCustomizer, ex);
}
}
}
void fireOnLease(Connection connection){
for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) {
try {
connectionCustomizer.onLease(connection, getUniqueName());
} catch (Exception ex){
log.warn("ConnectionCustomizer.onLease() failed for " + connectionCustomizer, ex);
}
}
}
void fireOnRelease(Connection connection){
for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) {
try {
connectionCustomizer.onRelease(connection, getUniqueName());
} catch (Exception ex){
log.warn("ConnectionCustomizer.onRelease() failed for " + connectionCustomizer, ex);
}
}
}
void fireOnDestroy(Connection connection) {
for (ConnectionCustomizer connectionCustomizer : connectionCustomizers) {
try {
connectionCustomizer.onDestroy(connection, getUniqueName());
} catch (Exception ex) {
log.warn("ConnectionCustomizer.onDestroy() failed for " + connectionCustomizer, ex);
}
}
}
/* Implementation of DataSource interface */
@Override
public Connection getConnection() throws SQLException {
if (isDisabled()) {
throw new SQLException("JDBC connection pool '" + getUniqueName() + "' is disabled, cannot get a connection from it");
}
init();
if (log.isDebugEnabled()) { log.debug("acquiring connection from " + this); }
if (pool == null) {
if (log.isDebugEnabled()) { log.debug("pool is closed, returning null connection"); }
return null;
}
try {
Connection conn = (Connection) pool.getConnectionHandle();
if (log.isDebugEnabled()) { log.debug("acquired connection from " + this); }
return conn;
} catch (Exception ex) {
throw new SQLException("unable to get a connection from pool of " + this, ex);
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
if (log.isDebugEnabled()) { log.debug("JDBC connections are pooled, username and password ignored"); }
return getConnection();
}
@Override
public String toString() {
return "a PoolingDataSource containing " + pool;
}
/* XAResourceProducer implementation */
@Override
public XAResourceHolderState startRecovery() throws RecoveryException {
init();
if (recoveryConnectionHandle != null)
throw new RecoveryException("recovery already in progress on " + this);
try {
recoveryConnectionHandle = (Connection) pool.getConnectionHandle(false);
PooledConnectionProxy pooledConnection = (PooledConnectionProxy) recoveryConnectionHandle;
recoveryXAResourceHolder = pooledConnection.getPooledConnection().createRecoveryXAResourceHolder();
return new XAResourceHolderState(pooledConnection.getPooledConnection(), this);
} catch (Exception ex) {
throw new RecoveryException("cannot start recovery on " + this, ex);
}
}
@Override
public void endRecovery() throws RecoveryException {
if (recoveryConnectionHandle == null)
return;
try {
if (log.isDebugEnabled()) { log.debug("recovery xa resource is being closed: " + recoveryXAResourceHolder); }
recoveryConnectionHandle.close();
} catch (Exception ex) {
throw new RecoveryException("error ending recovery on " + this, ex);
}
finally {
recoveryConnectionHandle = null;
// the recoveryXAResourceHolder actually wraps the recoveryConnectionHandle so closing it
// would close the recoveryConnectionHandle twice which must not happen
recoveryXAResourceHolder = null;
}
}
@Override
public void setFailed(boolean failed) {
if (pool != null) {
pool.setFailed(failed);
}
}
@Override
public boolean isFailed() {
return (pool != null ? pool.isFailed() : false);
}
@Override
public void close() {
if (pool == null) {
if (log.isDebugEnabled()) { log.debug("trying to close already closed PoolingDataSource " + getUniqueName()); }
return;
}
if (log.isDebugEnabled()) { log.debug("closing " + this); }
pool.close();
pool = null;
xaResourceHolderMap.clear();
connectionCustomizers.clear();
ManagementRegistrar.unregister(jmxName);
jmxName = null;
ResourceRegistrar.unregister(this);
}
@Override
public JdbcPooledConnection createPooledConnection(Object xaFactory, ResourceBean bean) throws Exception {
if (!(xaFactory instanceof XADataSource))
throw new IllegalArgumentException("class '" + xaFactory.getClass().getName() + "' does not implement " + XADataSource.class.getName());
XADataSource xads = (XADataSource) xaFactory;
JdbcPooledConnection pooledConnection = new JdbcPooledConnection(this, xads.getXAConnection());
xaResourceHolderMap.put(pooledConnection.getXAResource(), pooledConnection);
return pooledConnection;
}
@Override
public JdbcPooledConnection findXAResourceHolder(XAResource xaResource) {
return xaResourceHolderMap.get(xaResource);
}
/**
* {@link PoolingDataSource} must alway have a unique name so this method builds a reference to this object using
* the unique name as {@link javax.naming.RefAddr}.
* @return a reference to this {@link PoolingDataSource}.
*/
@Override
public Reference getReference() throws NamingException {
if (log.isDebugEnabled()) { log.debug("creating new JNDI reference of " + this); }
return new Reference(
PoolingDataSource.class.getName(),
new StringRefAddr("uniqueName", getUniqueName()),
ResourceObjectFactory.class.getName(),
null);
}
/* DataSource implementation */
@Override
public int getLoginTimeout() throws SQLException {
return xaDataSource.getLoginTimeout();
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
xaDataSource.setLoginTimeout(seconds);
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return xaDataSource.getLogWriter();
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
xaDataSource.setLogWriter(out);
}
/* java.sql.Wrapper implementation */
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return iface.isAssignableFrom(xaDataSource.getClass());
}
@SuppressWarnings("unchecked")
@Override
public T unwrap(Class iface) throws SQLException {
if (isWrapperFor(iface)) {
return (T) xaDataSource;
}
throw new SQLException(getClass().getName() + " is not a wrapper for " + iface);
}
/* management */
@Override
public int getInPoolSize() {
return pool.inPoolSize();
}
@Override
public int getTotalPoolSize() {
return pool.totalPoolSize();
}
@Override
public void reset() throws Exception {
pool.reset();
}
public void unregister(JdbcPooledConnection xaResourceHolder) {
xaResourceHolderMap.remove(xaResourceHolder.getXAResource());
}
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
}