All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy