/*
* 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.tomcat.dbcp.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.tomcat.dbcp.pool2.KeyedObjectPool;
import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPool;
import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPoolConfig;
/**
* <p>A pooling <code>DataSource</code> appropriate for deployment within
* J2EE environment. There are many configuration options, most of which are
* defined in the parent class. All users (based on username) share a single
* maximum number of Connections in this datasource.</p>
*
* <p>User passwords can be changed without re-initializing the datasource.
* When a <code>getConnection(username, password)</code> request is processed
* with a password that is different from those used to create connections in the
* pool associated with <code>username</code>, 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>
*
* @author John D. McNally
* @since 2.0
*/
public class SharedPoolDataSource extends InstanceKeyDataSource {
private static final long serialVersionUID = -1458539734480586454L;
// Pool properties
private int maxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
private transient KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> pool = null;
private transient KeyedCPDSConnectionFactory factory = null;
/**
* Default no-arg constructor for Serialization
*/
public SharedPoolDataSource() {
}
/**
* Close pool being maintained by this datasource.
*/
@Override
public void close() throws Exception {
if (pool != null) {
pool.close();
}
InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
}
// -------------------------------------------------------------------
// Properties
/**
* @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
*/
public int getMaxTotal() {
return this.maxTotal;
}
/**
* Set {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
* @param maxTotal The max total value
*/
public void setMaxTotal(final int maxTotal) {
assertInitializationAllowed();
this.maxTotal = maxTotal;
}
// ----------------------------------------------------------------------
// Instrumentation Methods
/**
* @return the number of active connections in the pool.
*/
public int getNumActive() {
return pool == null ? 0 : pool.getNumActive();
}
/**
* @return the number of idle connections in the pool.
*/
public int getNumIdle() {
return pool == null ? 0 : pool.getNumIdle();
}
// ----------------------------------------------------------------------
// Inherited abstract methods
@Override
protected PooledConnectionAndInfo
getPooledConnectionAndInfo(final String username, final String password)
throws SQLException {
synchronized(this) {
if (pool == null) {
try {
registerPool(username, password);
} catch (final NamingException e) {
throw new SQLException("RegisterPool failed", e);
}
}
}
PooledConnectionAndInfo info = null;
final UserPassKey key = new UserPassKey(username, password);
try {
info = pool.borrowObject(key);
}
catch (final Exception e) {
throw new SQLException(
"Could not retrieve connection info from pool", e);
}
return info;
}
@Override
protected PooledConnectionManager getConnectionManager(final UserPassKey upkey) {
return factory;
}
/**
* @return a <code>SharedPoolDataSource</code> {@link Reference}.
* @throws NamingException Should not occur
*/
@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;
}
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(),
getValidationQueryTimeout(), isRollbackAfterValidation());
factory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis());
final GenericKeyedObjectPoolConfig config =
new GenericKeyedObjectPoolConfig();
config.setBlockWhenExhausted(getDefaultBlockWhenExhausted());
config.setEvictionPolicyClassName(getDefaultEvictionPolicyClassName());
config.setLifo(getDefaultLifo());
config.setMaxIdlePerKey(getDefaultMaxIdle());
config.setMaxTotal(getMaxTotal());
config.setMaxTotalPerKey(getDefaultMaxTotal());
config.setMaxWaitMillis(getDefaultMaxWaitMillis());
config.setMinEvictableIdleTimeMillis(
getDefaultMinEvictableIdleTimeMillis());
config.setMinIdlePerKey(getDefaultMinIdle());
config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
config.setSoftMinEvictableIdleTimeMillis(
getDefaultSoftMinEvictableIdleTimeMillis());
config.setTestOnCreate(getDefaultTestOnCreate());
config.setTestOnBorrow(getDefaultTestOnBorrow());
config.setTestOnReturn(getDefaultTestOnReturn());
config.setTestWhileIdle(getDefaultTestWhileIdle());
config.setTimeBetweenEvictionRunsMillis(
getDefaultTimeBetweenEvictionRunsMillis());
final KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> tmpPool =
new GenericKeyedObjectPool<>(factory, config);
factory.setPool(tmpPool);
pool = tmpPool;
}
@Override
protected void setupDefaults(final Connection con, final String username) throws SQLException {
final Boolean defaultAutoCommit = isDefaultAutoCommit();
if (defaultAutoCommit != null &&
con.getAutoCommit() != defaultAutoCommit.booleanValue()) {
con.setAutoCommit(defaultAutoCommit.booleanValue());
}
final int defaultTransactionIsolation = getDefaultTransactionIsolation();
if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
con.setTransactionIsolation(defaultTransactionIsolation);
}
final Boolean defaultReadOnly = isDefaultReadOnly();
if (defaultReadOnly != null &&
con.isReadOnly() != defaultReadOnly.booleanValue()) {
con.setReadOnly(defaultReadOnly.booleanValue());
}
}
/**
* Supports Serialization interface.
*
* @param in a <code>java.io.ObjectInputStream</code> value
* @throws IOException if an error occurs
* @throws ClassNotFoundException if an error occurs
*/
private void readObject(final ObjectInputStream in)
throws IOException, ClassNotFoundException {
try
{
in.defaultReadObject();
final SharedPoolDataSource oldDS = (SharedPoolDataSource)
new SharedPoolDataSourceFactory()
.getObjectInstance(getReference(), null, null, null);
this.pool = oldDS.pool;
}
catch (final NamingException e)
{
throw new IOException("NamingException: " + e);
}
}
}