SharedPoolDataSource.java
/*
* 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.dbcp2.datasources;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import org.apache.commons.pool2.KeyedObjectPool;
import org.apache.commons.pool2.KeyedPooledObjectFactory;
import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
/**
* <p>
* A pooling {@code DataSource} appropriate for deployment within J2EE environment. There are many configuration
* options, most of which are defined in the parent class. All users (based on user name) share a single maximum number
* of Connections in this data source.
* </p>
*
* <p>
* User passwords can be changed without re-initializing the data source. When a
* {@code getConnection(user name, password)} request is processed with a password that is different from those
* used to create connections in the pool associated with {@code user name}, an attempt is made to create a new
* connection using the supplied password and if this succeeds, idle connections created using the old password are
* destroyed and new connections are created using the new password.
* </p>
*
* @since 2.0
*/
public class SharedPoolDataSource extends InstanceKeyDataSource {
private static final long serialVersionUID = -1458539734480586454L;
/**
* Max total defaults to {@link GenericKeyedObjectPoolConfig#DEFAULT_MAX_TOTAL}.
*/
private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
/**
* Maps user credentials to pooled connection with credentials.
*/
private transient KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
/**
* A {@link KeyedPooledObjectFactory} that creates {@link PoolableConnection}s.
*/
private transient KeyedCPDSConnectionFactory factory;
/**
* Default no-argument constructor for Serialization
*/
public SharedPoolDataSource() {
// empty.
}
/**
* Closes pool being maintained by this data source.
*/
@Override
public void close() throws SQLException {
if (pool != null) {
pool.close();
}
InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
}
@Override
protected PooledConnectionManager getConnectionManager(final UserPassKey userPassKey) {
return factory;
}
/**
* Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
*
* @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
*/
public int getMaxTotal() {
return this.maxTotal;
}
/**
* Gets the number of active connections in the pool.
*
* @return The number of active connections in the pool.
*/
public int getNumActive() {
return pool == null ? 0 : pool.getNumActive();
}
/**
* Gets the number of idle connections in the pool.
*
* @return The number of idle connections in the pool.
*/
public int getNumIdle() {
return pool == null ? 0 : pool.getNumIdle();
}
@Override
protected PooledConnectionAndInfo getPooledConnectionAndInfo(final String userName, final String userPassword)
throws SQLException {
synchronized (this) {
if (pool == null) {
try {
registerPool(userName, userPassword);
} catch (final NamingException e) {
throw new SQLException("registerPool failed", e);
}
}
}
try {
return pool.borrowObject(new UserPassKey(userName, userPassword));
} catch (final Exception e) {
throw new SQLException("Could not retrieve connection info from pool", e);
}
}
/**
* Creates a new {@link Reference} to a {@link SharedPoolDataSource}.
*/
@Override
public Reference getReference() throws NamingException {
final Reference ref = new Reference(getClass().getName(), SharedPoolDataSourceFactory.class.getName(), null);
ref.add(new StringRefAddr("instanceKey", getInstanceKey()));
return ref;
}
/**
* Deserializes an instance from an ObjectInputStream.
*
* @param in The source ObjectInputStream.
* @throws IOException Any of the usual Input/Output related exceptions.
* @throws ClassNotFoundException A class of a serialized object cannot be found.
*/
private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.pool = readObjectImpl();
}
private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> readObjectImpl() throws IOException, ClassNotFoundException {
try {
return ((SharedPoolDataSource) new SharedPoolDataSourceFactory().getObjectInstance(getReference(), null, null, null)).pool;
} catch (final NamingException e) {
throw new IOException("NamingException: " + e);
}
}
private void registerPool(final String userName, final String password) throws NamingException, SQLException {
final ConnectionPoolDataSource cpds = testCPDS(userName, password);
// Create an object pool to contain our PooledConnections
factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), getValidationQueryTimeoutDuration(), isRollbackAfterValidation());
factory.setMaxConn(getMaxConnDuration());
final GenericKeyedObjectPoolConfig<PooledConnectionAndInfo> config = new GenericKeyedObjectPoolConfig<>();
config.setBlockWhenExhausted(getDefaultBlockWhenExhausted());
config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName());
config.setLifo(getDefaultLifo());
config.setMaxIdlePerKey(getDefaultMaxIdle());
config.setMaxTotal(getMaxTotal());
config.setMaxTotalPerKey(getDefaultMaxTotal());
config.setMaxWait(getDefaultMaxWait());
config.setMinEvictableIdleDuration(getDefaultMinEvictableIdleDuration());
config.setMinIdlePerKey(getDefaultMinIdle());
config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
config.setSoftMinEvictableIdleDuration(getDefaultSoftMinEvictableIdleDuration());
config.setTestOnCreate(getDefaultTestOnCreate());
config.setTestOnBorrow(getDefaultTestOnBorrow());
config.setTestOnReturn(getDefaultTestOnReturn());
config.setTestWhileIdle(getDefaultTestWhileIdle());
config.setTimeBetweenEvictionRuns(getDefaultDurationBetweenEvictionRuns());
final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> tmpPool = new GenericKeyedObjectPool<>(factory, config);
factory.setPool(tmpPool);
pool = tmpPool;
}
/**
* Sets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
*
* @param maxTotal
* {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
*/
public void setMaxTotal(final int maxTotal) {
assertInitializationAllowed();
this.maxTotal = maxTotal;
}
@Override
protected void setupDefaults(final Connection connection, final String userName) throws SQLException {
final Boolean defaultAutoCommit = isDefaultAutoCommit();
if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit) {
connection.setAutoCommit(defaultAutoCommit);
}
final int defaultTransactionIsolation = getDefaultTransactionIsolation();
if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
connection.setTransactionIsolation(defaultTransactionIsolation);
}
final Boolean defaultReadOnly = isDefaultReadOnly();
if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly) {
connection.setReadOnly(defaultReadOnly);
}
}
@Override
protected void toStringFields(final StringBuilder builder) {
super.toStringFields(builder);
builder.append(", maxTotal=");
builder.append(maxTotal);
}
}