blob: f42fbe7ad448009ede3c68a9c7b9a4a19c6412b0 [file] [log] [blame]
package org.apache.commons.dbcp.jdbc2pool;
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" and
* "Apache Turbine" must not be used to endorse or promote products
* derived from this software without prior written permission. For
* written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* "Apache Turbine", nor may "Apache" appear in their name, without
* prior written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import javax.naming.BinaryRefAddr;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import org.apache.commons.collections.FastHashMap;
import org.apache.commons.collections.LRUMap;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
/**
* <p>
* A pooling <code>DataSource</code> appropriate for deployment within
* J2EE environment. There are many configuration options. Multiple users
* can share a common set of parameters, such as a single maximum number
* of Connections. The pool can also support individual pools per user, if the
* deployment environment can support initialization of mapped properties.
* So for example, a pool of admin or write-access Connections can be
* guaranteed a certain number of connections, separate from a maximum
* set for read-only connections.
* </p>
*
* <p>
* A J2EE container will normally provide some method of initializing the
* <code>DataSource</code> whose attributes are presented
* as bean getters/setters and then deploying it via JNDI. It is then
* available to an application as a source of pooled logical connections to
* the database. The pool needs a source of physical connections. This
* source is in the form of a <code>ConnectionPoolDataSource</code> that
* can be specified via the {@link #setDataSourceName(String)} used to
* lookup the source via JNDI.
* </p>
*
* <p>
* Although normally used within a JNDI environment, Jdbc2PoolDataSource
* can be instantiated and initialized as any bean. In this case the
* <code>ConnectionPoolDataSource</code> will likely be instantiated in
* a similar manner. The source can then be attached directly to this
* pool using the
* {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method.
* </p>
*
* <p>
* If this <code>DataSource</code>
* is requested via JNDI multiple times, it maintains
* state between lookups. Also, multiple instances can be deployed using
* different backend <code>ConnectionPoolDataSource</code> sources.
* </p>
*
* <p>
* The dbcp package contains an adapter,
* {@link org.apache.commons.dbcp.cpdsadapter.DriverAdapterCPDS},
* that can be used to allow the
* use of Jdbc2PoolDataSource with jdbc driver implementations that
* do not supply a <code>ConnectionPoolDataSource</code>, but still
* provide a {@link java.sql.Driver} implementation.
* </p>
*
* <p>
* The <a href="package-summary.html">package documentation</a> contains an
* example using catalina and JNDI and it also contains a non-JNDI example.
* </p>
*
* @author <a href="mailto:jmcnally@collab.net">John D. McNally</a>
* @version $Id: Jdbc2PoolDataSource.java,v 1.12 2003/06/29 12:42:16 mpoeschl Exp $
*/
public class Jdbc2PoolDataSource
implements DataSource, Referenceable, Serializable, ObjectFactory {
private static final String GET_CONNECTION_CALLED
= "A Connection was already requested from this source, "
+ "further initialization is not allowed.";
private static Map dsInstanceMap = new HashMap();
private static final Map userKeys = new LRUMap(10);
private static final Map poolKeys = new HashMap();
private boolean getConnectionCalled = false;
private ConnectionPoolDataSource cpds = null;
/** DataSource Name used to find the ConnectionPoolDataSource */
private String dataSourceName = null;
private boolean defaultAutoCommit = false;
private int defaultMaxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
private int defaultMaxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
private int defaultMaxWait =
(((long)Integer.MAX_VALUE) < GenericObjectPool.DEFAULT_MAX_WAIT) ?
(int)(GenericObjectPool.DEFAULT_MAX_WAIT) :
Integer.MAX_VALUE;
private boolean defaultReadOnly = false;
/** Description */
private String description = null;
/** Environment that may be used to set up a jndi initial context. */
private Properties jndiEnvironment = null;
/** Login TimeOut in seconds */
private int loginTimeout = 0;
/** Log stream */
private PrintWriter logWriter = null;
private Map perUserDefaultAutoCommit = null;
private Map perUserMaxActive = null;
private Map perUserMaxIdle = null;
private Map perUserMaxWait = null;
private Map perUserDefaultReadOnly = null;
private boolean _testOnBorrow = GenericObjectPool.DEFAULT_TEST_ON_BORROW;
private boolean _testOnReturn = GenericObjectPool.DEFAULT_TEST_ON_RETURN;
private int _timeBetweenEvictionRunsMillis =
(((long)Integer.MAX_VALUE) < GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS) ?
(int)(GenericObjectPool.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS) :
Integer.MAX_VALUE;;
private int _numTestsPerEvictionRun = GenericObjectPool.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
private int _minEvictableIdleTimeMillis =
(((long)Integer.MAX_VALUE) < GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS) ?
(int)(GenericObjectPool.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS) :
Integer.MAX_VALUE;;
private boolean _testWhileIdle = GenericObjectPool.DEFAULT_TEST_WHILE_IDLE;
private String validationQuery = null;
private boolean testPositionSet = false;
private boolean isNew = false;
private Integer instanceKey = null;
/**
* Default no-arg constructor for Serialization
*/
public Jdbc2PoolDataSource() {
isNew = true;
defaultAutoCommit = true;
}
/**
* Throws an IllegalStateException, if a PooledConnection has already
* been requested.
*/
private void assertInitializationAllowed()
throws IllegalStateException {
if (getConnectionCalled) {
throw new IllegalStateException(GET_CONNECTION_CALLED);
}
}
/**
* Close all pools associated with this class.
*/
public static void closeAll() {
//Get iterator to loop over all instances of this datasource.
Iterator instanceIterator = dsInstanceMap.entrySet().iterator();
while (instanceIterator.hasNext()) {
Map.Entry nextInstance = (Map.Entry) instanceIterator.next();
Map nextPoolMap = (Map) nextInstance.getValue();
close(nextPoolMap);
}
dsInstanceMap.clear();
}
/**
* Close all pools in the given Map.
*/
private static void close(Map poolMap) {
//Get iterator to loop over all pools.
Iterator poolIter = poolMap.entrySet().iterator();
while (poolIter.hasNext()) {
Map.Entry nextPoolEntry = (Map.Entry) poolIter.next();
if (nextPoolEntry.getValue() instanceof ObjectPool) {
ObjectPool nextPool = (ObjectPool) nextPoolEntry.getValue();
try {
nextPool.close();
} catch (Exception closePoolException) {
//ignore and try to close others.
}
} else {
KeyedObjectPool nextPool =
(KeyedObjectPool) nextPoolEntry.getValue();
try {
nextPool.close();
} catch (Exception closePoolException) {
//ignore and try to close others.
}
}
}
}
/**
* Close pool(s) being maintained by this datasource.
*/
public void close() {
close((Map)dsInstanceMap.get(instanceKey));
}
// -------------------------------------------------------------------
// Properties
/**
* Get the value of connectionPoolDataSource. This method will return
* null, if the backing datasource is being accessed via jndi.
*
* @return value of connectionPoolDataSource.
*/
public ConnectionPoolDataSource getConnectionPoolDataSource() {
return cpds;
}
/**
* Set the backend ConnectionPoolDataSource. This property should not be
* set if using jndi to access the datasource.
*
* @param v Value to assign to connectionPoolDataSource.
*/
public void setConnectionPoolDataSource(ConnectionPoolDataSource v) {
assertInitializationAllowed();
if (dataSourceName != null) {
throw new IllegalStateException(
"Cannot set the DataSource, if JNDI is used.");
}
this.cpds = v;
if (isNew) {
registerInstance();
}
}
/**
* Get the name of the ConnectionPoolDataSource which backs this pool.
* This name is used to look up the datasource from a jndi service
* provider.
*
* @return value of dataSourceName.
*/
public String getDataSourceName() {
return dataSourceName;
}
/**
* Set the name of the ConnectionPoolDataSource which backs this pool.
* This name is used to look up the datasource from a jndi service
* provider.
*
* @param v Value to assign to dataSourceName.
*/
public void setDataSourceName(String v) {
assertInitializationAllowed();
if (cpds != null) {
throw new IllegalStateException(
"Cannot set the JNDI name for the DataSource, if already " +
"set using setConnectionPoolDataSource.");
}
this.dataSourceName = v;
if (isNew) {
registerInstance();
}
}
/**
* Get the value of defaultAutoCommit, which defines the state of
* connections handed out from this pool. The value can be changed
* on the Connection using Connection.setAutoCommit(boolean).
* The default is true.
*
* @return value of defaultAutoCommit.
*/
public boolean isDefaultAutoCommit() {
return defaultAutoCommit;
}
/**
* Set the value of defaultAutoCommit, which defines the state of
* connections handed out from this pool. The value can be changed
* on the Connection using Connection.setAutoCommit(boolean).
* The default is true.
*
* @param v Value to assign to defaultAutoCommit.
*/
public void setDefaultAutoCommit(boolean v) {
assertInitializationAllowed();
this.defaultAutoCommit = v;
}
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or zero for no limit.
* This value is used for any username which is not specified
* in perUserMaxConnections. The default is 0.
*/
public int getDefaultMaxActive() {
return (this.defaultMaxActive);
}
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or zero for no limit.
* This value is used for any username which is not specified
* in perUserMaxConnections. The default is 0.
*/
public void setDefaultMaxActive(int maxActive) {
assertInitializationAllowed();
this.defaultMaxActive = maxActive;
}
/**
* The maximum number of active connections that can remain idle in the
* pool, without extra ones being released, or zero for no limit.
* This value is used for any username which is not specified
* in perUserMaxIdle. The default is 0.
*/
public int getDefaultMaxIdle() {
return (this.defaultMaxIdle);
}
/**
* The maximum number of active connections that can remain idle in the
* pool, without extra ones being released, or zero for no limit.
* This value is used for any username which is not specified
* in perUserMaxIdle. The default is 0.
*/
public void setDefaultMaxIdle(int defaultMaxIdle) {
assertInitializationAllowed();
this.defaultMaxIdle = defaultMaxIdle;
}
/**
* The maximum number of milliseconds that the pool will wait (when there
* are no available connections) for a connection to be returned before
* throwing an exception, or -1 to wait indefinitely. Will fail
* immediately if value is 0.
* This value is used for any username which is not specified
* in perUserMaxWait. The default is -1.
*/
public int getDefaultMaxWait() {
return (this.defaultMaxWait);
}
/**
* The maximum number of milliseconds that the pool will wait (when there
* are no available connections) for a connection to be returned before
* throwing an exception, or -1 to wait indefinitely. Will fail
* immediately if value is 0.
* This value is used for any username which is not specified
* in perUserMaxWait. The default is -1.
*/
public void setDefaultMaxWait(int defaultMaxWait) {
assertInitializationAllowed();
this.defaultMaxWait = defaultMaxWait;
}
/**
* Get the value of defaultReadOnly, which defines the state of
* connections handed out from this pool. The value can be changed
* on the Connection using Connection.setReadOnly(boolean).
* The default is false.
*
* @return value of defaultReadOnly.
*/
public boolean isDefaultReadOnly() {
return defaultReadOnly;
}
/**
* Set the value of defaultReadOnly, which defines the state of
* connections handed out from this pool. The value can be changed
* on the Connection using Connection.setReadOnly(boolean).
* The default is false.
*
* @param v Value to assign to defaultReadOnly.
*/
public void setDefaultReadOnly(boolean v) {
assertInitializationAllowed();
this.defaultReadOnly = v;
}
/**
* Get the description. This property is defined by jdbc as for use with
* GUI (or other) tools that might deploy the datasource. It serves no
* internal purpose.
*
* @return value of description.
*/
public String getDescription() {
return description;
}
/**
* Set the description. This property is defined by jdbc as for use with
* GUI (or other) tools that might deploy the datasource. It serves no
* internal purpose.
*
* @param v Value to assign to description.
*/
public void setDescription(String v) {
this.description = v;
}
/**
* Get the value of jndiEnvironment which is used when instantiating
* a jndi InitialContext. This InitialContext is used to locate the
* backend ConnectionPoolDataSource.
*
* @return value of jndiEnvironment.
*/
public String getJndiEnvironment(String key) {
String value = null;
if (jndiEnvironment != null) {
value = jndiEnvironment.getProperty(key);
}
return value;
}
/**
* Set the value of jndiEnvironment which is used when instantiating
* a jndi InitialContext. This InitialContext is used to locate the
* backend ConnectionPoolDataSource.
*
* @param v Value to assign to jndiEnvironment.
*/
public void setJndiEnvironment(String key, String value) {
if (jndiEnvironment == null) {
jndiEnvironment = new Properties();
}
jndiEnvironment.setProperty(key, value);
}
/**
* Get the value of loginTimeout.
* @return value of loginTimeout.
*/
public int getLoginTimeout() {
return loginTimeout;
}
/**
* Set the value of loginTimeout.
* @param v Value to assign to loginTimeout.
*/
public void setLoginTimeout(int v) {
this.loginTimeout = v;
}
/**
* Get the value of logWriter.
* @return value of logWriter.
*/
public PrintWriter getLogWriter() {
if (logWriter == null) {
logWriter = new PrintWriter(System.out);
}
return logWriter;
}
/**
* Set the value of logWriter.
* @param v Value to assign to logWriter.
*/
public void setLogWriter(PrintWriter v) {
this.logWriter = v;
}
/**
* The keys are usernames and the value is the --. Any
* username specified here will override the value of defaultAutoCommit.
*/
public Boolean getPerUserDefaultAutoCommit(String key) {
Boolean value = null;
if (perUserDefaultAutoCommit != null) {
value = (Boolean) perUserDefaultAutoCommit.get(key);
}
return value;
}
/**
* The keys are usernames and the value is the --. Any
* username specified here will override the value of defaultAutoCommit.
*/
public void setPerUserDefaultAutoCommit(String username, Boolean value) {
assertInitializationAllowed();
if (perUserDefaultAutoCommit == null) {
perUserDefaultAutoCommit = new HashMap();
}
perUserDefaultAutoCommit.put(username, value);
}
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or zero for no limit.
* The keys are usernames and the value is the maximum connections. Any
* username specified here will override the value of defaultMaxActive.
*/
public Integer getPerUserMaxActive(String username) {
Integer value = null;
if (perUserMaxActive != null) {
value = (Integer) perUserMaxActive.get(username);
}
return value;
}
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or zero for no limit.
* The keys are usernames and the value is the maximum connections. Any
* username specified here will override the value of defaultMaxActive.
*/
public void setPerUserMaxActive(String username, Integer value) {
assertInitializationAllowed();
if (perUserMaxActive == null) {
perUserMaxActive = new HashMap();
}
perUserMaxActive.put(username, value);
}
/**
* The maximum number of active connections that can remain idle in the
* pool, without extra ones being released, or zero for no limit.
* The keys are usernames and the value is the maximum connections. Any
* username specified here will override the value of defaultMaxIdle.
*/
public Integer getPerUserMaxIdle(String username) {
Integer value = null;
if (perUserMaxIdle != null) {
value = (Integer) perUserMaxIdle.get(username);
}
return value;
}
/**
* The maximum number of active connections that can remain idle in the
* pool, without extra ones being released, or zero for no limit.
* The keys are usernames and the value is the maximum connections. Any
* username specified here will override the value of defaultMaxIdle.
*/
public void setPerUserMaxIdle(String username, Integer value) {
assertInitializationAllowed();
if (perUserMaxIdle == null) {
perUserMaxIdle = new HashMap();
}
perUserMaxIdle.put(username, value);
}
/**
* The maximum number of milliseconds that the pool will wait (when there
* are no available connections) for a connection to be returned before
* throwing an exception, or -1 to wait indefinitely. Will fail
* immediately if value is 0.
* The keys are usernames and the value is the maximum connections. Any
* username specified here will override the value of defaultMaxWait.
*/
public Integer getPerUserMaxWait(String username) {
Integer value = null;
if (perUserMaxWait != null) {
value = (Integer) perUserMaxWait.get(username);
}
return value;
}
/**
* The maximum number of milliseconds that the pool will wait (when there
* are no available connections) for a connection to be returned before
* throwing an exception, or -1 to wait indefinitely. Will fail
* immediately if value is 0.
* The keys are usernames and the value is the maximum connections. Any
* username specified here will override the value of defaultMaxWait.
*/
public void setPerUserMaxWait(String username, Integer value) {
assertInitializationAllowed();
if (perUserMaxWait == null) {
perUserMaxWait = new HashMap();
}
perUserMaxWait.put(username, value);
}
/**
* The keys are usernames and the value is the --. Any
* username specified here will override the value of defaultReadOnly.
*/
public Boolean getPerUserDefaultReadOnly(String username) {
Boolean value = null;
if (perUserDefaultReadOnly != null) {
value = (Boolean) perUserDefaultReadOnly.get(username);
}
return value;
}
/**
* The keys are usernames and the value is the --. Any
* username specified here will override the value of defaultReadOnly.
*/
public void setPerUserDefaultReadOnly(String username, Boolean value) {
assertInitializationAllowed();
if (perUserDefaultReadOnly == null) {
perUserDefaultReadOnly = new HashMap();
}
perUserDefaultReadOnly.put(username, value);
}
/**
* @see #getTestOnBorrow
*/
public final boolean isTestOnBorrow() {
return getTestOnBorrow();
}
/**
* When <tt>true</tt>, objects will be
* {*link PoolableObjectFactory#validateObject validated}
* before being returned by the {*link #borrowObject}
* method. If the object fails to validate,
* it will be dropped from the pool, and we will attempt
* to borrow another.
*
* @see #setTestOnBorrow
*/
public boolean getTestOnBorrow() {
return _testOnBorrow;
}
/**
* When <tt>true</tt>, objects will be
* {*link PoolableObjectFactory#validateObject validated}
* before being returned by the {*link #borrowObject}
* method. If the object fails to validate,
* it will be dropped from the pool, and we will attempt
* to borrow another.
*
* @see #getTestOnBorrow
*/
public void setTestOnBorrow(boolean testOnBorrow) {
assertInitializationAllowed();
_testOnBorrow = testOnBorrow;
testPositionSet = true;
}
/**
* @see #getTestOnReturn
*/
public final boolean isTestOnReturn() {
return getTestOnReturn();
}
/**
* When <tt>true</tt>, objects will be
* {*link PoolableObjectFactory#validateObject validated}
* before being returned to the pool within the
* {*link #returnObject}.
*
* @see #setTestOnReturn
*/
public boolean getTestOnReturn() {
return _testOnReturn;
}
/**
* When <tt>true</tt>, objects will be
* {*link PoolableObjectFactory#validateObject validated}
* before being returned to the pool within the
* {*link #returnObject}.
*
* @see #getTestOnReturn
*/
public void setTestOnReturn(boolean testOnReturn) {
assertInitializationAllowed();
_testOnReturn = testOnReturn;
testPositionSet = true;
}
/**
* Returns the number of milliseconds to sleep between runs of the
* idle object evictor thread.
* When non-positive, no idle object evictor thread will be
* run.
*
* @see #setTimeBetweenEvictionRunsMillis
*/
public int getTimeBetweenEvictionRunsMillis() {
return _timeBetweenEvictionRunsMillis;
}
/**
* Sets the number of milliseconds to sleep between runs of the
* idle object evictor thread.
* When non-positive, no idle object evictor thread will be
* run.
*
* @see #getTimeBetweenEvictionRunsMillis
*/
public void
setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
assertInitializationAllowed();
_timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
/**
* Returns the number of objects to examine during each run of the
* idle object evictor thread (if any).
*
* @see #setNumTestsPerEvictionRun
* @see #setTimeBetweenEvictionRunsMillis
*/
public int getNumTestsPerEvictionRun() {
return _numTestsPerEvictionRun;
}
/**
* Sets the number of objects to examine during each run of the
* idle object evictor thread (if any).
* <p>
* When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt>
* tests will be run. I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the
* idle objects will be tested per run.
*
* @see #getNumTestsPerEvictionRun
* @see #setTimeBetweenEvictionRunsMillis
*/
public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
assertInitializationAllowed();
_numTestsPerEvictionRun = numTestsPerEvictionRun;
}
/**
* Returns the minimum amount of time an object may sit idle in the pool
* before it is eligable for eviction by the idle object evictor
* (if any).
*
* @see #setMinEvictableIdleTimeMillis
* @see #setTimeBetweenEvictionRunsMillis
*/
public int getMinEvictableIdleTimeMillis() {
return _minEvictableIdleTimeMillis;
}
/**
* Sets the minimum amount of time an object may sit idle in the pool
* before it is eligable for eviction by the idle object evictor
* (if any).
* When non-positive, no objects will be evicted from the pool
* due to idle time alone.
*
* @see #getMinEvictableIdleTimeMillis
* @see #setTimeBetweenEvictionRunsMillis
*/
public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
assertInitializationAllowed();
_minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
/**
* @see #getTestWhileIdle
*/
public final boolean isTestWhileIdle() {
return getTestWhileIdle();
}
/**
* When <tt>true</tt>, objects will be
* {*link PoolableObjectFactory#validateObject validated}
* by the idle object evictor (if any). If an object
* fails to validate, it will be dropped from the pool.
*
* @see #setTestWhileIdle
* @see #setTimeBetweenEvictionRunsMillis
*/
public boolean getTestWhileIdle() {
return _testWhileIdle;
}
/**
* When <tt>true</tt>, objects will be
* {*link PoolableObjectFactory#validateObject validated}
* by the idle object evictor (if any). If an object
* fails to validate, it will be dropped from the pool.
*
* @see #getTestWhileIdle
* @see #setTimeBetweenEvictionRunsMillis
*/
public void setTestWhileIdle(boolean testWhileIdle) {
assertInitializationAllowed();
_testWhileIdle = testWhileIdle;
testPositionSet = true;
}
/**
* The SQL query that will be used to validate connections from this pool
* before returning them to the caller. If specified, this query
* <strong>MUST</strong> be an SQL SELECT statement that returns at least
* one row.
*/
public String getValidationQuery() {
return (this.validationQuery);
}
/**
* The SQL query that will be used to validate connections from this pool
* before returning them to the caller. If specified, this query
* <strong>MUST</strong> be an SQL SELECT statement that returns at least
* one row. Default behavior is to test the connection when it is
* borrowed.
*/
public void setValidationQuery(String validationQuery) {
assertInitializationAllowed();
this.validationQuery = validationQuery;
if (!testPositionSet) {
setTestOnBorrow(true);
}
}
// ----------------------------------------------------------------------
// Instrumentation Methods
/**
* Get the number of active connections in the default pool.
*/
public int getNumActive() {
return getNumActive(null, null);
}
/**
* Get the number of active connections in the pool for a given user.
*/
public int getNumActive(String username, String password) {
PoolKey key = getPoolKey(username);
Object pool = ((Map) dsInstanceMap.get(instanceKey)).get(key);
if (pool instanceof ObjectPool) {
return ((ObjectPool) pool).getNumActive();
} else {
return ((KeyedObjectPool) pool).getNumActive();
}
}
/**
* Get the number of idle connections in the default pool.
*/
public int getNumIdle() {
return getNumIdle(null, null);
}
/**
* Get the number of idle connections in the pool for a given user.
*/
public int getNumIdle(String username, String password) {
PoolKey key = getPoolKey(username);
Object pool = ((Map) dsInstanceMap.get(instanceKey)).get(key);
if (pool instanceof ObjectPool) {
return ((ObjectPool) pool).getNumIdle();
} else {
return ((KeyedObjectPool) pool).getNumIdle();
}
}
// ----------------------------------------------------------------------
// DataSource implementation
/**
* Attempt to establish a database connection.
*/
public Connection getConnection() throws SQLException {
return getConnection(null, null);
}
/**
* Attempt to establish a database connection.
*/
public synchronized Connection getConnection(String username,
String password)
throws SQLException {
if (isNew) {
throw new SQLException("Must set the ConnectionPoolDataSource "
+ "through setDataSourceName or setConnectionPoolDataSource"
+ " before calling getConnection.");
}
getConnectionCalled = true;
Map pools = (Map) dsInstanceMap.get(instanceKey);
PoolKey key = getPoolKey(username);
Object pool = pools.get(key);
if (pool == null) {
try {
registerPool(username, password);
pool = pools.get(key);
} catch (Exception e) {
e.printStackTrace();
throw new SQLException(e.getMessage());
}
}
PooledConnectionAndInfo info = null;
if (pool instanceof ObjectPool) {
try {
info = (PooledConnectionAndInfo)
((ObjectPool) pool).borrowObject();
} catch (NoSuchElementException e) {
closeDueToException(info);
throw new SQLException(e.getMessage());
} catch (RuntimeException e) {
closeDueToException(info);
throw e;
} catch (SQLException e) {
closeDueToException(info);
throw e;
} catch (Exception e) {
closeDueToException(info);
throw new SQLException(e.getMessage());
}
} else {
// assume KeyedObjectPool
try {
UserPassKey upkey = getUserPassKey(username, password);
info = (PooledConnectionAndInfo)
((KeyedObjectPool) pool).borrowObject(upkey);
} catch (NoSuchElementException e) {
closeDueToException(info);
throw new SQLException(e.getMessage());
} catch (RuntimeException e) {
closeDueToException(info);
throw e;
} catch (SQLException e) {
closeDueToException(info);
throw e;
} catch (Exception e) {
closeDueToException(info);
throw new SQLException(e.getMessage());
}
}
if (!(null == password ? null == info.getPassword()
: password.equals(info.getPassword()))) {
closeDueToException(info);
throw new SQLException("Given password did not match password used "
+ "to create the PooledConnection.");
}
PooledConnection pc = info.getPooledConnection();
boolean defaultAutoCommit = isDefaultAutoCommit();
if (username != null) {
Boolean userMax = getPerUserDefaultAutoCommit(username);
if (userMax != null) {
defaultAutoCommit = userMax.booleanValue();
}
}
boolean defaultReadOnly = isDefaultReadOnly();
if (username != null) {
Boolean userMax = getPerUserDefaultReadOnly(username);
if (userMax != null) {
defaultReadOnly = userMax.booleanValue();
}
}
Connection con = pc.getConnection();
con.setAutoCommit(defaultAutoCommit);
con.setReadOnly(defaultReadOnly);
return con;
}
private void closeDueToException(PooledConnectionAndInfo info) {
if (info != null) {
try {
info.getPooledConnection().getConnection().close();
} catch (Exception e) {
// do not throw this exception because we are in the middle
// of handling another exception. But record it because
// it potentially leaks connections from the pool.
getLogWriter().println("[ERROR] Could not return connection to "
+ "pool during exception handling. " + e.getMessage());
}
}
}
private PoolKey getPoolKey(String username) {
PoolKey key = null;
if (username != null && (perUserMaxActive == null
|| !perUserMaxActive.containsKey(username))) {
username = null;
}
String dsName = getDataSourceName();
Map dsMap = (Map) poolKeys.get(dsName);
if (dsMap != null) {
key = (PoolKey) dsMap.get(username);
}
if (key == null) {
key = new PoolKey(dsName, username);
if (dsMap == null) {
dsMap = new HashMap();
poolKeys.put(dsName, dsMap);
}
dsMap.put(username, key);
}
return key;
}
private UserPassKey getUserPassKey(String username, String password) {
UserPassKey key = (UserPassKey) userKeys.get(username);
if (key == null) {
key = new UserPassKey(username, password);
userKeys.put(username, key);
}
return key;
}
private synchronized void registerInstance() {
if (isNew) {
int max = 0;
Iterator i = dsInstanceMap.keySet().iterator();
while (i.hasNext()) {
int key = ((Integer) i.next()).intValue();
max = Math.max(max, key);
}
instanceKey = new Integer(max + 1);
FastHashMap fhm = new FastHashMap();
fhm.setFast(true);
dsInstanceMap.put(instanceKey, fhm);
isNew = false;
}
}
private synchronized void registerPool(String username, String password)
throws javax.naming.NamingException {
Map pools = (Map) dsInstanceMap.get(instanceKey);
PoolKey key = getPoolKey(username);
if (!pools.containsKey(key)) {
int maxActive = getDefaultMaxActive();
int maxIdle = getDefaultMaxIdle();
int maxWait = getDefaultMaxWait();
// The source of physical db connections
ConnectionPoolDataSource cpds = this.cpds;
if (cpds == null) {
Context ctx = null;
if (jndiEnvironment == null) {
ctx = new InitialContext();
} else {
ctx = new InitialContext(jndiEnvironment);
}
cpds = (ConnectionPoolDataSource) ctx.lookup(dataSourceName);
}
Object whicheverPool = null;
if (perUserMaxActive != null
&& perUserMaxActive.containsKey(username)) {
Integer userMax = getPerUserMaxActive(username);
if (userMax != null) {
maxActive = userMax.intValue();
}
userMax = getPerUserMaxIdle(username);
if (userMax != null) {
maxIdle = userMax.intValue();
}
userMax = getPerUserMaxWait(username);
if (userMax != null) {
maxWait = userMax.intValue();
}
// Create an object pool to contain our PooledConnections
GenericObjectPool pool = new GenericObjectPool(null);
pool.setMaxActive(maxActive);
pool.setMaxIdle(maxIdle);
pool.setMaxWait(maxWait);
pool.setWhenExhaustedAction(
getWhenExhausted(maxActive, maxWait));
pool.setTestOnBorrow(getTestOnBorrow());
pool.setTestOnReturn(getTestOnReturn());
pool.setTimeBetweenEvictionRunsMillis(
getTimeBetweenEvictionRunsMillis());
pool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
pool.setMinEvictableIdleTimeMillis(
getMinEvictableIdleTimeMillis());
pool.setTestWhileIdle(getTestWhileIdle());
// Set up the factory we will use (passing the pool associates
// the factory with the pool, so we do not have to do so
// explicitly)
new CPDSConnectionFactory(cpds, pool, validationQuery,
username, password);
whicheverPool = pool;
} else {
// use default pool
// Create an object pool to contain our PooledConnections
GenericKeyedObjectPool pool = new GenericKeyedObjectPool(null);
pool.setMaxActive(maxActive);
pool.setMaxIdle(maxIdle);
pool.setMaxWait(maxWait);
pool.setWhenExhaustedAction(
getWhenExhausted(maxActive, maxWait));
pool.setTestOnBorrow(getTestOnBorrow());
pool.setTestOnReturn(getTestOnReturn());
pool.setTimeBetweenEvictionRunsMillis(
getTimeBetweenEvictionRunsMillis());
pool.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
pool.setMinEvictableIdleTimeMillis(
getMinEvictableIdleTimeMillis());
pool.setTestWhileIdle(getTestWhileIdle());
// Set up the factory we will use (passing the pool associates
// the factory with the pool, so we do not have to do so
// explicitly)
new KeyedCPDSConnectionFactory(cpds, pool, validationQuery);
whicheverPool = pool;
}
// pools is a FastHashMap set to put the pool in a thread-safe way
pools.put(key, whicheverPool);
}
}
private byte getWhenExhausted(int maxActive, int maxWait) {
byte whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
if (maxActive <= 0) {
whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_GROW;
} else if (maxWait == 0) {
whenExhausted = GenericObjectPool.WHEN_EXHAUSTED_FAIL;
}
return whenExhausted;
}
// ----------------------------------------------------------------------
// Referenceable implementation
/**
* <CODE>Referenceable</CODE> implementation prepares object for
* binding in jndi.
*/
public Reference getReference() throws NamingException {
// this class implements its own factory
String factory = getClass().getName();
Reference ref = new Reference(getClass().getName(), factory, null);
ref.add(new StringRefAddr("isNew", String.valueOf(isNew)));
ref.add(new StringRefAddr("instanceKey",
(instanceKey == null ? null : instanceKey.toString())));
ref.add(new StringRefAddr("dataSourceName", getDataSourceName()));
ref.add(new StringRefAddr("defaultAutoCommit",
String.valueOf(isDefaultAutoCommit())));
ref.add(new StringRefAddr("defaultMaxActive",
String.valueOf(getDefaultMaxActive())));
ref.add(new StringRefAddr("defaultMaxIdle",
String.valueOf(getDefaultMaxIdle())));
ref.add(new StringRefAddr("defaultMaxWait",
String.valueOf(getDefaultMaxWait())));
ref.add(new StringRefAddr("defaultReadOnly",
String.valueOf(isDefaultReadOnly())));
ref.add(new StringRefAddr("description", getDescription()));
byte[] ser = null;
// BinaryRefAddr does not allow null byte[].
if (jndiEnvironment != null) {
try {
ser = serialize(jndiEnvironment);
ref.add(new BinaryRefAddr("jndiEnvironment", ser));
} catch (IOException ioe) {
throw new NamingException("An IOException prevented "
+ "serializing the jndiEnvironment properties.");
}
}
ref.add(new StringRefAddr("loginTimeout",
String.valueOf(getLoginTimeout())));
if (perUserDefaultAutoCommit != null) {
try {
ser = serialize((Serializable) perUserDefaultAutoCommit);
ref.add(new BinaryRefAddr("perUserDefaultAutoCommit", ser));
} catch (IOException ioe) {
throw new NamingException("An IOException prevented "
+ "serializing the perUserDefaultAutoCommit "
+ "properties.");
}
}
if (perUserMaxActive != null) {
try {
ser = serialize((Serializable) perUserMaxActive);
ref.add(new BinaryRefAddr("perUserMaxActive", ser));
} catch (IOException ioe) {
throw new NamingException("An IOException prevented "
+ "serializing the perUserMaxActive properties.");
}
}
if (perUserMaxIdle != null) {
try {
ser = serialize((Serializable) perUserMaxIdle);
ref.add(new BinaryRefAddr("perUserMaxIdle", ser));
} catch (IOException ioe) {
throw new NamingException("An IOException prevented "
+ "serializing the perUserMaxIdle properties.");
}
}
if (perUserMaxWait != null) {
try {
ser = serialize((Serializable) perUserMaxWait);
ref.add(new BinaryRefAddr("perUserMaxWait", ser));
} catch (IOException ioe) {
throw new NamingException("An IOException prevented "
+ "serializing the perUserMaxWait properties.");
}
}
if (perUserDefaultReadOnly != null) {
try {
ser = serialize((Serializable) perUserDefaultReadOnly);
ref.add(new BinaryRefAddr("perUserDefaultReadOnly", ser));
} catch (IOException ioe) {
throw new NamingException("An IOException prevented "
+ "serializing the perUserDefaultReadOnly properties.");
}
}
ref.add(new StringRefAddr("testOnBorrow",
String.valueOf(getTestOnBorrow())));
ref.add(new StringRefAddr("testOnReturn",
String.valueOf(getTestOnReturn())));
ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis",
String.valueOf(getTimeBetweenEvictionRunsMillis())));
ref.add(new StringRefAddr("numTestsPerEvictionRun",
String.valueOf(getNumTestsPerEvictionRun())));
ref.add(new StringRefAddr("minEvictableIdleTimeMillis",
String.valueOf(getMinEvictableIdleTimeMillis())));
ref.add(new StringRefAddr("testWhileIdle",
String.valueOf(getTestWhileIdle())));
ref.add(new StringRefAddr("validationQuery", getValidationQuery()));
return ref;
}
/**
* Converts a object to a byte array for storage/serialization.
*
* @param obj The Serializable to convert.
* @return A byte[] with the converted Serializable.
* @exception IOException, if conversion to a byte[] fails.
*/
private static byte[] serialize(Serializable obj) throws IOException {
byte[] byteArray = null;
ByteArrayOutputStream baos = null;
ObjectOutputStream out = null;
try {
// These objects are closed in the finally.
baos = new ByteArrayOutputStream();
out = new ObjectOutputStream(baos);
out.writeObject(obj);
byteArray = baos.toByteArray();
} finally {
if (out != null) {
out.close();
}
}
return byteArray;
}
// ----------------------------------------------------------------------
// ObjectFactory implementation
/**
* implements ObjectFactory to create an instance of this class
*/
public Object getObjectInstance(Object refObj, Name name,
Context context, Hashtable env)
throws Exception {
// The spec says to return null if we can't create an instance
// of the reference
Jdbc2PoolDataSource ds = null;
if (refObj instanceof Reference) {
Reference ref = (Reference) refObj;
if (ref.getClassName().equals(getClass().getName())) {
RefAddr ra = ref.get("isNew");
if (ra != null && ra.getContent() != null) {
isNew = Boolean.valueOf(ra.getContent().toString())
.booleanValue();
}
ra = ref.get("instanceKey");
if (ra != null && ra.getContent() != null) {
instanceKey = new Integer(ra.getContent().toString());
}
ra = ref.get("dataSourceName");
if (ra != null && ra.getContent() != null) {
setDataSourceName(ra.getContent().toString());
}
ra = ref.get("defaultAutoCommit");
if (ra != null && ra.getContent() != null) {
setDefaultAutoCommit(Boolean.valueOf(
ra.getContent().toString()).booleanValue());
}
ra = ref.get("defaultMaxActive");
if (ra != null && ra.getContent() != null) {
setDefaultMaxActive(
Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("defaultMaxIdle");
if (ra != null && ra.getContent() != null) {
setDefaultMaxIdle(
Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("defaultMaxWait");
if (ra != null && ra.getContent() != null) {
setDefaultMaxWait(
Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("defaultReadOnly");
if (ra != null && ra.getContent() != null) {
setDefaultReadOnly(Boolean.valueOf(
ra.getContent().toString()).booleanValue());
}
ra = ref.get("description");
if (ra != null && ra.getContent() != null) {
setDescription(ra.getContent().toString());
}
ra = ref.get("jndiEnvironment");
if (ra != null && ra.getContent() != null) {
byte[] serialized = (byte[]) ra.getContent();
jndiEnvironment = (Properties) deserialize(serialized);
}
ra = ref.get("loginTimeout");
if (ra != null && ra.getContent() != null) {
setLoginTimeout(
Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("perUserDefaultAutoCommit");
if (ra != null && ra.getContent() != null) {
byte[] serialized = (byte[]) ra.getContent();
perUserDefaultAutoCommit = (Map) deserialize(serialized);
}
ra = ref.get("perUserMaxActive");
if (ra != null && ra.getContent() != null) {
byte[] serialized = (byte[]) ra.getContent();
perUserMaxActive = (Map) deserialize(serialized);
}
ra = ref.get("perUserMaxIdle");
if (ra != null && ra.getContent() != null) {
byte[] serialized = (byte[]) ra.getContent();
perUserMaxIdle = (Map) deserialize(serialized);
}
ra = ref.get("perUserMaxWait");
if (ra != null && ra.getContent() != null) {
byte[] serialized = (byte[]) ra.getContent();
perUserMaxWait = (Map) deserialize(serialized);
}
ra = ref.get("perUserDefaultReadOnly");
if (ra != null && ra.getContent() != null) {
byte[] serialized = (byte[]) ra.getContent();
perUserDefaultReadOnly = (Map) deserialize(serialized);
}
ra = ref.get("testOnBorrow");
if (ra != null && ra.getContent() != null) {
setTestOnBorrow(Boolean.valueOf(ra.getContent().toString())
.booleanValue());
}
ra = ref.get("testOnReturn");
if (ra != null && ra.getContent() != null) {
setTestOnReturn(Boolean.valueOf(ra.getContent().toString())
.booleanValue());
}
ra = ref.get("timeBetweenEvictionRunsMillis");
if (ra != null && ra.getContent() != null) {
setTimeBetweenEvictionRunsMillis(
Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("numTestsPerEvictionRun");
if (ra != null && ra.getContent() != null) {
setNumTestsPerEvictionRun(
Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("minEvictableIdleTimeMillis");
if (ra != null && ra.getContent() != null) {
setMinEvictableIdleTimeMillis(
Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("testWhileIdle");
if (ra != null && ra.getContent() != null) {
setTestWhileIdle(Boolean.valueOf(ra.getContent().toString())
.booleanValue());
}
ra = ref.get("validationQuery");
if (ra != null && ra.getContent() != null) {
setValidationQuery(ra.getContent().toString());
}
ds = this;
}
}
return ds;
}
private final Object deserialize(byte[] data) throws Exception {
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new ByteArrayInputStream(data));
return in.readObject();
} finally {
try {
in.close();
} catch (IOException ex) {
}
}
}
}