org.apache.commons.dbcp.datasources.PerUserPoolDataSource Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.dbcp.datasources;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import org.apache.commons.dbcp.SQLNestedException;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
/**
* A pooling DataSource
appropriate for deployment within
* J2EE environment. There are many configuration options, most of which are
* defined in the parent class. This datasource uses individual pools per
* user, and some properties can be set specifically for a given 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 users with read-only connections.
*
* User passwords can be changed without re-initializing the datasource.
* When a getConnection(username, password)
request is processed
* with a password that is different from those used to create connections in the
* pool associated with username
, an attempt is made to create a
* new connection using the supplied password and if this succeeds, the existing
* pool is cleared and a new pool is created for connections using the new password.
*
*
* @author John D. McNally
* @version $Revision: 907288 $ $Date: 2010-02-06 14:42:58 -0500 (Sat, 06 Feb 2010) $
*/
public class PerUserPoolDataSource
extends InstanceKeyDataSource {
private static final long serialVersionUID = -3104731034410444060L;
private int defaultMaxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE;
private int defaultMaxIdle = GenericObjectPool.DEFAULT_MAX_IDLE;
private int defaultMaxWait = (int)Math.min(Integer.MAX_VALUE,
GenericObjectPool.DEFAULT_MAX_WAIT);
Map perUserDefaultAutoCommit = null;
Map perUserDefaultTransactionIsolation = null;
Map perUserMaxActive = null;
Map perUserMaxIdle = null;
Map perUserMaxWait = null;
Map perUserDefaultReadOnly = null;
/**
* Map to keep track of Pools for a given user
*/
private transient Map /* */ managers = new HashMap();
/**
* Default no-arg constructor for Serialization
*/
public PerUserPoolDataSource() {
}
/**
* Close pool(s) being maintained by this datasource.
*/
public void close() {
for (Iterator poolIter = managers.values().iterator();
poolIter.hasNext();) {
try {
((CPDSConnectionFactory) poolIter.next()).getPool().close();
} catch (Exception closePoolException) {
//ignore and try to close others.
}
}
InstanceKeyObjectFactory.removeInstance(instanceKey);
}
// -------------------------------------------------------------------
// Properties
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or non-positive for no limit.
* This value is used for any username which is not specified
* in perUserMaxConnections.
*/
public int getDefaultMaxActive() {
return (this.defaultMaxActive);
}
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or non-positive for no limit.
* This value is used for any username which is not specified
* in perUserMaxConnections. The default is 8.
*/
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 negative for no limit.
* This value is used for any username which is not specified
* in perUserMaxIdle.
*/
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 negative for no limit.
* This value is used for any username which is not specified
* in perUserMaxIdle. The default is 8.
*/
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;
}
/**
* 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 isolation level of connections when returned from getConnection.
* If null, the username will use the value of defaultTransactionIsolation.
*/
public Integer getPerUserDefaultTransactionIsolation(String username) {
Integer value = null;
if (perUserDefaultTransactionIsolation != null) {
value = (Integer) perUserDefaultTransactionIsolation.get(username);
}
return value;
}
/**
* The isolation level of connections when returned from getConnection.
* Valid values are the constants defined in Connection.
*/
public void setPerUserDefaultTransactionIsolation(String username,
Integer value) {
assertInitializationAllowed();
if (perUserDefaultTransactionIsolation == null) {
perUserDefaultTransactionIsolation = new HashMap();
}
perUserDefaultTransactionIsolation.put(username, value);
}
/**
* The maximum number of active connections that can be allocated from
* this pool at the same time, or non-positive 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 non-positive 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 negative 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 negative 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);
}
// ----------------------------------------------------------------------
// 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) {
ObjectPool pool = getPool(getPoolKey(username,password));
return (pool == null) ? 0 : 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) {
ObjectPool pool = getPool(getPoolKey(username,password));
return (pool == null) ? 0 : pool.getNumIdle();
}
// ----------------------------------------------------------------------
// Inherited abstract methods
protected PooledConnectionAndInfo
getPooledConnectionAndInfo(String username, String password)
throws SQLException {
final PoolKey key = getPoolKey(username,password);
ObjectPool pool;
PooledConnectionManager manager;
synchronized(this) {
manager = (PooledConnectionManager) managers.get(key);
if (manager == null) {
try {
registerPool(username, password);
manager = (PooledConnectionManager) managers.get(key);
} catch (NamingException e) {
throw new SQLNestedException("RegisterPool failed", e);
}
}
pool = ((CPDSConnectionFactory) manager).getPool();
}
PooledConnectionAndInfo info = null;
try {
info = (PooledConnectionAndInfo) pool.borrowObject();
}
catch (NoSuchElementException ex) {
throw new SQLNestedException(
"Could not retrieve connection info from pool", ex);
}
catch (Exception e) {
// See if failure is due to CPDSConnectionFactory authentication failure
try {
testCPDS(username, password);
} catch (Exception ex) {
throw (SQLException) new SQLException(
"Could not retrieve connection info from pool").initCause(ex);
}
// New password works, so kill the old pool, create a new one, and borrow
manager.closePool(username);
synchronized (this) {
managers.remove(key);
}
try {
registerPool(username, password);
pool = getPool(key);
} catch (NamingException ne) {
throw new SQLNestedException("RegisterPool failed", ne);
}
try {
info = (PooledConnectionAndInfo)((ObjectPool) pool).borrowObject();
} catch (Exception ex) {
throw (SQLException) new SQLException(
"Could not retrieve connection info from pool").initCause(ex);
}
}
return info;
}
protected void setupDefaults(Connection con, String username)
throws SQLException {
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();
}
}
int defaultTransactionIsolation = getDefaultTransactionIsolation();
if (username != null) {
Integer userMax = getPerUserDefaultTransactionIsolation(username);
if (userMax != null) {
defaultTransactionIsolation = userMax.intValue();
}
}
if (con.getAutoCommit() != defaultAutoCommit) {
con.setAutoCommit(defaultAutoCommit);
}
if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
con.setTransactionIsolation(defaultTransactionIsolation);
}
if (con.isReadOnly() != defaultReadOnly) {
con.setReadOnly(defaultReadOnly);
}
}
protected PooledConnectionManager getConnectionManager(UserPassKey upkey) {
return (PooledConnectionManager) managers.get(getPoolKey(
upkey.getUsername(), upkey.getPassword()));
}
/**
* Returns a PerUserPoolDataSource
{@link Reference}.
*
* @since 1.2.2
*/
public Reference getReference() throws NamingException {
Reference ref = new Reference(getClass().getName(),
PerUserPoolDataSourceFactory.class.getName(), null);
ref.add(new StringRefAddr("instanceKey", instanceKey));
return ref;
}
private PoolKey getPoolKey(String username, String password) {
return new PoolKey(getDataSourceName(), username);
}
private synchronized void registerPool(
String username, String password)
throws javax.naming.NamingException, SQLException {
ConnectionPoolDataSource cpds = testCPDS(username, password);
Integer userMax = getPerUserMaxActive(username);
int maxActive = (userMax == null) ?
getDefaultMaxActive() : userMax.intValue();
userMax = getPerUserMaxIdle(username);
int maxIdle = (userMax == null) ?
getDefaultMaxIdle() : userMax.intValue();
userMax = getPerUserMaxWait(username);
int maxWait = (userMax == null) ?
getDefaultMaxWait() : 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(whenExhaustedAction(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)
CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, pool, getValidationQuery(),
isRollbackAfterValidation(), username, password);
Object old = managers.put(getPoolKey(username,password), factory);
if (old != null) {
throw new IllegalStateException("Pool already contains an entry for this user/password: "+username);
}
}
/**
* Supports Serialization interface.
*
* @param in a java.io.ObjectInputStream
value
* @exception IOException if an error occurs
* @exception ClassNotFoundException if an error occurs
*/
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
try
{
in.defaultReadObject();
PerUserPoolDataSource oldDS = (PerUserPoolDataSource)
new PerUserPoolDataSourceFactory()
.getObjectInstance(getReference(), null, null, null);
this.managers = oldDS.managers;
}
catch (NamingException e)
{
throw new IOException("NamingException: " + e);
}
}
/**
* Returns the object pool associated with the given PoolKey.
*
* @param key PoolKey identifying the pool
* @return the GenericObjectPool pooling connections for the username and datasource
* specified by the PoolKey
*/
private GenericObjectPool getPool(PoolKey key) {
CPDSConnectionFactory mgr = (CPDSConnectionFactory) managers.get(key);
return mgr == null ? null : (GenericObjectPool) mgr.getPool();
}
}