This commit is contained in:
2024-11-30 19:03:49 +08:00
commit 1e6763c160
3806 changed files with 737676 additions and 0 deletions

View File

@@ -0,0 +1,426 @@
/*
* 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.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import org.apache.tomcat.dbcp.dbcp2.Utils;
import org.apache.tomcat.dbcp.pool2.ObjectPool;
import org.apache.tomcat.dbcp.pool2.PooledObject;
import org.apache.tomcat.dbcp.pool2.PooledObjectFactory;
import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
/**
* A {@link PooledObjectFactory} that creates
* {@link org.apache.tomcat.dbcp.dbcp2.PoolableConnection PoolableConnection}s.
*
* @since 2.0
*/
class CPDSConnectionFactory
implements PooledObjectFactory<PooledConnectionAndInfo>, ConnectionEventListener, PooledConnectionManager {
private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but I have no record of the underlying PooledConnection.";
private final ConnectionPoolDataSource cpds;
private final String validationQuery;
private final int validationQueryTimeoutSeconds;
private final boolean rollbackAfterValidation;
private ObjectPool<PooledConnectionAndInfo> pool;
private final String userName;
private char[] userPassword;
private long maxConnLifetimeMillis = -1;
/**
* Map of PooledConnections for which close events are ignored. Connections are muted when they are being validated.
*/
private final Set<PooledConnection> validatingSet = Collections
.newSetFromMap(new ConcurrentHashMap<PooledConnection, Boolean>());
/**
* Map of PooledConnectionAndInfo instances
*/
private final Map<PooledConnection, PooledConnectionAndInfo> pcMap = new ConcurrentHashMap<>();
/**
* Creates a new {@code PoolableConnectionFactory}.
*
* @param cpds
* the ConnectionPoolDataSource from which to obtain PooledConnection's
* @param validationQuery
* a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
* row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
* connections.
* @param validationQueryTimeoutSeconds
* Timeout in seconds before validation fails
* @param rollbackAfterValidation
* whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
* @param userName
* The user name to use to create connections
* @param userPassword
* The password to use to create connections
* @since 2.4.0
*/
public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation, final String userName,
final char[] userPassword) {
this.cpds = cpds;
this.validationQuery = validationQuery;
this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
this.userName = userName;
this.userPassword = userPassword;
this.rollbackAfterValidation = rollbackAfterValidation;
}
/**
* Creates a new {@code PoolableConnectionFactory}.
*
* @param cpds
* the ConnectionPoolDataSource from which to obtain PooledConnection's
* @param validationQuery
* a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
* row. May be {@code null} in which case {@link Connection#isValid(int)} will be used to validate
* connections.
* @param validationQueryTimeoutSeconds
* Timeout in seconds before validation fails
* @param rollbackAfterValidation
* whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
* @param userName
* The user name to use to create connections
* @param userPassword
* The password to use to create connections
*/
public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation, final String userName,
final String userPassword) {
this(cpds, validationQuery, validationQueryTimeoutSeconds, rollbackAfterValidation, userName,
Utils.toCharArray(userPassword));
}
/**
* (Testing API) Gets the value of password for the default user.
*
* @return value of password.
*/
char[] getPasswordCharArray() {
return userPassword;
}
/**
* Returns the object pool used to pool connections created by this factory.
*
* @return ObjectPool managing pooled connections
*/
public ObjectPool<PooledConnectionAndInfo> getPool() {
return pool;
}
/**
*
* @param pool
* the {@link ObjectPool} in which to pool those {@link Connection}s
*/
public void setPool(final ObjectPool<PooledConnectionAndInfo> pool) {
this.pool = pool;
}
@Override
public synchronized PooledObject<PooledConnectionAndInfo> makeObject() {
PooledConnectionAndInfo pci;
try {
PooledConnection pc = null;
if (userName == null) {
pc = cpds.getPooledConnection();
} else {
pc = cpds.getPooledConnection(userName, Utils.toString(userPassword));
}
if (pc == null) {
throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
}
// should we add this object as a listener or the pool.
// consider the validateObject method in decision
pc.addConnectionEventListener(this);
pci = new PooledConnectionAndInfo(pc, userName, userPassword);
pcMap.put(pc, pci);
} catch (final SQLException e) {
throw new RuntimeException(e.getMessage());
}
return new DefaultPooledObject<>(pci);
}
/**
* Closes the PooledConnection and stops listening for events from it.
*/
@Override
public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
doDestroyObject(p.getObject());
}
private void doDestroyObject(final PooledConnectionAndInfo pci) throws Exception {
final PooledConnection pc = pci.getPooledConnection();
pc.removeConnectionEventListener(this);
pcMap.remove(pc);
pc.close();
}
@Override
public boolean validateObject(final PooledObject<PooledConnectionAndInfo> p) {
try {
validateLifetime(p);
} catch (final Exception e) {
return false;
}
boolean valid = false;
final PooledConnection pconn = p.getObject().getPooledConnection();
Connection conn = null;
validatingSet.add(pconn);
if (null == validationQuery) {
int timeoutSeconds = validationQueryTimeoutSeconds;
if (timeoutSeconds < 0) {
timeoutSeconds = 0;
}
try {
conn = pconn.getConnection();
valid = conn.isValid(timeoutSeconds);
} catch (final SQLException e) {
valid = false;
} finally {
Utils.closeQuietly(conn);
validatingSet.remove(pconn);
}
} else {
Statement stmt = null;
ResultSet rset = null;
// logical Connection from the PooledConnection must be closed
// before another one can be requested and closing it will
// generate an event. Keep track so we know not to return
// the PooledConnection
validatingSet.add(pconn);
try {
conn = pconn.getConnection();
stmt = conn.createStatement();
rset = stmt.executeQuery(validationQuery);
if (rset.next()) {
valid = true;
} else {
valid = false;
}
if (rollbackAfterValidation) {
conn.rollback();
}
} catch (final Exception e) {
valid = false;
} finally {
Utils.closeQuietly(rset);
Utils.closeQuietly(stmt);
Utils.closeQuietly(conn);
validatingSet.remove(pconn);
}
}
return valid;
}
@Override
public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
validateLifetime(p);
}
@Override
public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
validateLifetime(p);
}
// ***********************************************************************
// java.sql.ConnectionEventListener implementation
// ***********************************************************************
/**
* This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the
* user calls the close() method of this connection object. What we need to do here is to release this
* PooledConnection from our pool...
*/
@Override
public void connectionClosed(final ConnectionEvent event) {
final PooledConnection pc = (PooledConnection) event.getSource();
// if this event occurred because we were validating, ignore it
// otherwise return the connection to the pool.
if (!validatingSet.contains(pc)) {
final PooledConnectionAndInfo pci = pcMap.get(pc);
if (pci == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
try {
pool.returnObject(pci);
} catch (final Exception e) {
System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL");
pc.removeConnectionEventListener(this);
try {
doDestroyObject(pci);
} catch (final Exception e2) {
System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
e2.printStackTrace();
}
}
}
}
/**
* If a fatal error occurs, close the underlying physical connection so as not to be returned in the future
*/
@Override
public void connectionErrorOccurred(final ConnectionEvent event) {
final PooledConnection pc = (PooledConnection) event.getSource();
if (null != event.getSQLException()) {
System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")");
}
pc.removeConnectionEventListener(this);
final PooledConnectionAndInfo pci = pcMap.get(pc);
if (pci == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
try {
pool.invalidateObject(pci);
} catch (final Exception e) {
System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
e.printStackTrace();
}
}
// ***********************************************************************
// PooledConnectionManager implementation
// ***********************************************************************
/**
* Invalidates the PooledConnection in the pool. The CPDSConnectionFactory closes the connection and pool counters
* are updated appropriately. Also closes the pool. This ensures that all idle connections are closed and
* connections that are checked out are closed on return.
*/
@Override
public void invalidate(final PooledConnection pc) throws SQLException {
final PooledConnectionAndInfo pci = pcMap.get(pc);
if (pci == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
try {
pool.invalidateObject(pci); // Destroy instance and update pool counters
pool.close(); // Clear any other instances in this pool and kill others as they come back
} catch (final Exception ex) {
throw new SQLException("Error invalidating connection", ex);
}
}
/**
* Sets the database password used when creating new connections.
*
* @param userPassword
* new password
*/
public synchronized void setPassword(final char[] userPassword) {
this.userPassword = Utils.clone(userPassword);
}
/**
* Sets the database password used when creating new connections.
*
* @param userPassword
* new password
*/
@Override
public synchronized void setPassword(final String userPassword) {
this.userPassword = Utils.toCharArray(userPassword);
}
/**
* Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
* passivation and validation.
*
* @param maxConnLifetimeMillis
* A value of zero or less indicates an infinite lifetime. The default value is -1.
*/
public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
this.maxConnLifetimeMillis = maxConnLifetimeMillis;
}
/**
* Verifies that the user name matches the user whose connections are being managed by this factory and closes the
* pool if this is the case; otherwise does nothing.
*/
@Override
public void closePool(final String userName) throws SQLException {
synchronized (this) {
if (userName == null || !userName.equals(this.userName)) {
return;
}
}
try {
pool.close();
} catch (final Exception ex) {
throw new SQLException("Error closing connection pool", ex);
}
}
private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
if (maxConnLifetimeMillis > 0) {
final long lifetime = System.currentTimeMillis() - p.getCreateTime();
if (lifetime > maxConnLifetimeMillis) {
throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", Long.valueOf(lifetime),
Long.valueOf(maxConnLifetimeMillis)));
}
}
}
/**
* @since 2.6.0
*/
@Override
public synchronized String toString() {
final StringBuilder builder = new StringBuilder(super.toString());
builder.append("[cpds=");
builder.append(cpds);
builder.append(", validationQuery=");
builder.append(validationQuery);
builder.append(", validationQueryTimeoutSeconds=");
builder.append(validationQueryTimeoutSeconds);
builder.append(", rollbackAfterValidation=");
builder.append(rollbackAfterValidation);
builder.append(", pool=");
builder.append(pool);
builder.append(", maxConnLifetimeMillis=");
builder.append(maxConnLifetimeMillis);
builder.append(", validatingSet=");
builder.append(validatingSet);
builder.append(", pcMap=");
builder.append(pcMap);
builder.append("]");
return builder.toString();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,340 @@
/*
* 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
import org.apache.tomcat.dbcp.dbcp2.ListException;
/**
* A JNDI ObjectFactory which creates <code>SharedPoolDataSource</code>s or <code>PerUserPoolDataSource</code>s
*
* @since 2.0
*/
abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
private static final Map<String, InstanceKeyDataSource> instanceMap = new ConcurrentHashMap<>();
static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
int max = 0;
final Iterator<String> iterator = instanceMap.keySet().iterator();
while (iterator.hasNext()) {
final String s = iterator.next();
if (s != null) {
try {
max = Math.max(max, Integer.parseInt(s));
} catch (final NumberFormatException e) {
// no sweat, ignore those keys
}
}
}
final String instanceKey = String.valueOf(max + 1);
// Put a placeholder here for now, so other instances will not
// take our key. We will replace with a pool when ready.
instanceMap.put(instanceKey, ds);
return instanceKey;
}
static void removeInstance(final String key) {
if (key != null) {
instanceMap.remove(key);
}
}
/**
* Closes all pools associated with this class.
*
* @throws Exception
* a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()}
* @see InstanceKeyDataSource#close()
* @see ListException
* @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by
* {@link InstanceKeyDataSource#close()}.
*/
public static void closeAll() throws Exception {
// Get iterator to loop over all instances of this data source.
final List<Throwable> exceptionList = new ArrayList<>(instanceMap.size());
final Iterator<Entry<String, InstanceKeyDataSource>> instanceIterator = instanceMap.entrySet().iterator();
while (instanceIterator.hasNext()) {
// Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
final Entry<String, InstanceKeyDataSource> next = instanceIterator.next();
if (next != null) {
@SuppressWarnings("resource")
final InstanceKeyDataSource value = next.getValue();
if (value != null) {
try {
value.close();
} catch (final Exception e) {
exceptionList.add(e);
}
}
}
}
instanceMap.clear();
if (!exceptionList.isEmpty()) {
throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
}
}
/**
* Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource
*/
@Override
public Object getObjectInstance(final Object refObj, final Name name, final Context context,
final Hashtable<?, ?> env) throws IOException, ClassNotFoundException {
// The spec says to return null if we can't create an instance
// of the reference
Object obj = null;
if (refObj instanceof Reference) {
final Reference ref = (Reference) refObj;
if (isCorrectClass(ref.getClassName())) {
final RefAddr refAddr = ref.get("instanceKey");
if (refAddr != null && refAddr.getContent() != null) {
// object was bound to JNDI via Referenceable API.
obj = instanceMap.get(refAddr.getContent());
} else {
// Tomcat JNDI creates a Reference out of server.xml
// <ResourceParam> configuration and passes it to an
// instance of the factory given in server.xml.
String key = null;
if (name != null) {
key = name.toString();
obj = instanceMap.get(key);
}
if (obj == null) {
final InstanceKeyDataSource ds = getNewInstance(ref);
setCommonProperties(ref, ds);
obj = ds;
if (key != null) {
instanceMap.put(key, ds);
}
}
}
}
}
return obj;
}
private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds)
throws IOException, ClassNotFoundException {
RefAddr refAddr = ref.get("dataSourceName");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDataSourceName(refAddr.getContent().toString());
}
refAddr = ref.get("description");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDescription(refAddr.getContent().toString());
}
refAddr = ref.get("jndiEnvironment");
if (refAddr != null && refAddr.getContent() != null) {
final byte[] serialized = (byte[]) refAddr.getContent();
ikds.setJndiEnvironment((Properties) deserialize(serialized));
}
refAddr = ref.get("loginTimeout");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setLoginTimeout(Integer.parseInt(refAddr.getContent().toString()));
}
// Pool properties
refAddr = ref.get("blockWhenExhausted");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultBlockWhenExhausted(Boolean.valueOf(refAddr.getContent().toString()).booleanValue());
}
refAddr = ref.get("evictionPolicyClassName");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultEvictionPolicyClassName(refAddr.getContent().toString());
}
// Pool properties
refAddr = ref.get("lifo");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultLifo(Boolean.valueOf(refAddr.getContent().toString()).booleanValue());
}
refAddr = ref.get("maxIdlePerKey");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultMaxIdle(Integer.parseInt(refAddr.getContent().toString()));
}
refAddr = ref.get("maxTotalPerKey");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultMaxTotal(Integer.parseInt(refAddr.getContent().toString()));
}
refAddr = ref.get("maxWaitMillis");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultMaxWaitMillis(Long.parseLong(refAddr.getContent().toString()));
}
refAddr = ref.get("minEvictableIdleTimeMillis");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultMinEvictableIdleTimeMillis(Long.parseLong(refAddr.getContent().toString()));
}
refAddr = ref.get("minIdlePerKey");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultMinIdle(Integer.parseInt(refAddr.getContent().toString()));
}
refAddr = ref.get("numTestsPerEvictionRun");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultNumTestsPerEvictionRun(Integer.parseInt(refAddr.getContent().toString()));
}
refAddr = ref.get("softMinEvictableIdleTimeMillis");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultSoftMinEvictableIdleTimeMillis(Long.parseLong(refAddr.getContent().toString()));
}
refAddr = ref.get("testOnCreate");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultTestOnCreate(Boolean.valueOf(refAddr.getContent().toString()).booleanValue());
}
refAddr = ref.get("testOnBorrow");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultTestOnBorrow(Boolean.valueOf(refAddr.getContent().toString()).booleanValue());
}
refAddr = ref.get("testOnReturn");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultTestOnReturn(Boolean.valueOf(refAddr.getContent().toString()).booleanValue());
}
refAddr = ref.get("testWhileIdle");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultTestWhileIdle(Boolean.valueOf(refAddr.getContent().toString()).booleanValue());
}
refAddr = ref.get("timeBetweenEvictionRunsMillis");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultTimeBetweenEvictionRunsMillis(Long.parseLong(refAddr.getContent().toString()));
}
// Connection factory properties
refAddr = ref.get("validationQuery");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setValidationQuery(refAddr.getContent().toString());
}
refAddr = ref.get("validationQueryTimeout");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setValidationQueryTimeout(Integer.parseInt(refAddr.getContent().toString()));
}
refAddr = ref.get("rollbackAfterValidation");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setRollbackAfterValidation(Boolean.valueOf(refAddr.getContent().toString()).booleanValue());
}
refAddr = ref.get("maxConnLifetimeMillis");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setMaxConnLifetimeMillis(Long.parseLong(refAddr.getContent().toString()));
}
// Connection properties
refAddr = ref.get("defaultAutoCommit");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultAutoCommit(Boolean.valueOf(refAddr.getContent().toString()));
}
refAddr = ref.get("defaultTransactionIsolation");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultTransactionIsolation(Integer.parseInt(refAddr.getContent().toString()));
}
refAddr = ref.get("defaultReadOnly");
if (refAddr != null && refAddr.getContent() != null) {
ikds.setDefaultReadOnly(Boolean.valueOf(refAddr.getContent().toString()));
}
}
/**
* @param className
* The class name to test.
*
* @return true if and only if className is the value returned from getClass().getName().toString()
*/
protected abstract boolean isCorrectClass(String className);
/**
* Creates an instance of the subclass and sets any properties contained in the Reference.
*
* @param ref
* The properties to be set on the created DataSource
*
* @return A configured DataSource of the appropriate type.
*
* @throws ClassNotFoundException
* If a class cannot be found during the deserialization of a configuration parameter.
* @throws IOException
* If an I/O error occurs during the deserialization of a configuration parameter.
*/
protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;
/**
* Deserializes the provided byte array to create an object.
*
* @param data
* Data to deserialize to create the configuration parameter.
*
* @return The Object created by deserializing the data.
*
* @throws ClassNotFoundException
* If a class cannot be found during the deserialization of a configuration parameter.
* @throws IOException
* If an I/O error occurs during the deserialization of a configuration parameter.
*/
protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new ByteArrayInputStream(data));
return in.readObject();
} finally {
if (in != null) {
try {
in.close();
} catch (final IOException ex) {
// ignore
}
}
}
}
}

View File

@@ -0,0 +1,351 @@
/*
* 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.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import org.apache.tomcat.dbcp.dbcp2.Utils;
import org.apache.tomcat.dbcp.pool2.KeyedObjectPool;
import org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory;
import org.apache.tomcat.dbcp.pool2.PooledObject;
import org.apache.tomcat.dbcp.pool2.impl.DefaultPooledObject;
/**
* A {@link KeyedPooledObjectFactory} that creates {@link org.apache.tomcat.dbcp.dbcp2.PoolableConnection
* PoolableConnection}s.
*
* @since 2.0
*/
class KeyedCPDSConnectionFactory implements KeyedPooledObjectFactory<UserPassKey, PooledConnectionAndInfo>,
ConnectionEventListener, PooledConnectionManager {
private static final String NO_KEY_MESSAGE = "close() was called on a Connection, but "
+ "I have no record of the underlying PooledConnection.";
private final ConnectionPoolDataSource cpds;
private final String validationQuery;
private final int validationQueryTimeoutSeconds;
private final boolean rollbackAfterValidation;
private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
private long maxConnLifetimeMillis = -1;
/**
* Map of PooledConnections for which close events are ignored. Connections are muted when they are being validated.
*/
private final Set<PooledConnection> validatingSet = Collections
.newSetFromMap(new ConcurrentHashMap<PooledConnection, Boolean>());
/**
* Map of PooledConnectionAndInfo instances
*/
private final Map<PooledConnection, PooledConnectionAndInfo> pcMap = new ConcurrentHashMap<>();
/**
* Create a new {@code KeyedPoolableConnectionFactory}.
*
* @param cpds
* the ConnectionPoolDataSource from which to obtain PooledConnections
* @param validationQuery
* a query to use to {@link #validateObject validate} {@link Connection}s. Should return at least one
* row. May be {@code null} in which case3 {@link Connection#isValid(int)} will be used to validate
* connections.
* @param validationQueryTimeoutSeconds
* The time, in seconds, to allow for the validation query to complete
* @param rollbackAfterValidation
* whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
*/
public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
this.cpds = cpds;
this.validationQuery = validationQuery;
this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
this.rollbackAfterValidation = rollbackAfterValidation;
}
public void setPool(final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool) {
this.pool = pool;
}
/**
* Returns the keyed object pool used to pool connections created by this factory.
*
* @return KeyedObjectPool managing pooled connections
*/
public KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> getPool() {
return pool;
}
/**
* Creates a new {@link PooledConnectionAndInfo} from the given {@link UserPassKey}.
*
* @param upkey
* {@link UserPassKey} containing user credentials
* @throws SQLException
* if the connection could not be created.
* @see org.apache.tomcat.dbcp.pool2.KeyedPooledObjectFactory#makeObject(java.lang.Object)
*/
@Override
public synchronized PooledObject<PooledConnectionAndInfo> makeObject(final UserPassKey upkey) throws Exception {
PooledConnectionAndInfo pci = null;
PooledConnection pc = null;
final String userName = upkey.getUsername();
final String password = upkey.getPassword();
if (userName == null) {
pc = cpds.getPooledConnection();
} else {
pc = cpds.getPooledConnection(userName, password);
}
if (pc == null) {
throw new IllegalStateException("Connection pool data source returned null from getPooledConnection");
}
// should we add this object as a listener or the pool.
// consider the validateObject method in decision
pc.addConnectionEventListener(this);
pci = new PooledConnectionAndInfo(pc, userName, upkey.getPasswordCharArray());
pcMap.put(pc, pci);
return new DefaultPooledObject<>(pci);
}
/**
* Closes the PooledConnection and stops listening for events from it.
*/
@Override
public void destroyObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
final PooledConnection pc = p.getObject().getPooledConnection();
pc.removeConnectionEventListener(this);
pcMap.remove(pc);
pc.close();
}
/**
* Validates a pooled connection.
*
* @param key
* ignored
* @param pooledObject
* wrapped {@link PooledConnectionAndInfo} containing the connection to validate
* @return true if validation succeeds
*/
@Override
public boolean validateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> pooledObject) {
try {
validateLifetime(pooledObject);
} catch (final Exception e) {
return false;
}
boolean valid = false;
final PooledConnection pconn = pooledObject.getObject().getPooledConnection();
Connection conn = null;
validatingSet.add(pconn);
if (null == validationQuery) {
int timeoutSeconds = validationQueryTimeoutSeconds;
if (timeoutSeconds < 0) {
timeoutSeconds = 0;
}
try {
conn = pconn.getConnection();
valid = conn.isValid(timeoutSeconds);
} catch (final SQLException e) {
valid = false;
} finally {
Utils.closeQuietly(conn);
validatingSet.remove(pconn);
}
} else {
Statement stmt = null;
ResultSet rset = null;
// logical Connection from the PooledConnection must be closed
// before another one can be requested and closing it will
// generate an event. Keep track so we know not to return
// the PooledConnection
validatingSet.add(pconn);
try {
conn = pconn.getConnection();
stmt = conn.createStatement();
rset = stmt.executeQuery(validationQuery);
if (rset.next()) {
valid = true;
} else {
valid = false;
}
if (rollbackAfterValidation) {
conn.rollback();
}
} catch (final Exception e) {
valid = false;
} finally {
Utils.closeQuietly(rset);
Utils.closeQuietly(stmt);
Utils.closeQuietly(conn);
validatingSet.remove(pconn);
}
}
return valid;
}
@Override
public void passivateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
validateLifetime(p);
}
@Override
public void activateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
validateLifetime(p);
}
// ***********************************************************************
// java.sql.ConnectionEventListener implementation
// ***********************************************************************
/**
* This will be called if the Connection returned by the getConnection method came from a PooledConnection, and the
* user calls the close() method of this connection object. What we need to do here is to release this
* PooledConnection from our pool...
*/
@Override
public void connectionClosed(final ConnectionEvent event) {
final PooledConnection pc = (PooledConnection) event.getSource();
// if this event occurred because we were validating, or if this
// connection has been marked for removal, ignore it
// otherwise return the connection to the pool.
if (!validatingSet.contains(pc)) {
final PooledConnectionAndInfo pci = pcMap.get(pc);
if (pci == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
try {
pool.returnObject(pci.getUserPassKey(), pci);
} catch (final Exception e) {
System.err.println("CLOSING DOWN CONNECTION AS IT COULD " + "NOT BE RETURNED TO THE POOL");
pc.removeConnectionEventListener(this);
try {
pool.invalidateObject(pci.getUserPassKey(), pci);
} catch (final Exception e3) {
System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + pci);
e3.printStackTrace();
}
}
}
}
/**
* If a fatal error occurs, close the underlying physical connection so as not to be returned in the future
*/
@Override
public void connectionErrorOccurred(final ConnectionEvent event) {
final PooledConnection pc = (PooledConnection) event.getSource();
if (null != event.getSQLException()) {
System.err.println("CLOSING DOWN CONNECTION DUE TO INTERNAL ERROR (" + event.getSQLException() + ")");
}
pc.removeConnectionEventListener(this);
final PooledConnectionAndInfo info = pcMap.get(pc);
if (info == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
try {
pool.invalidateObject(info.getUserPassKey(), info);
} catch (final Exception e) {
System.err.println("EXCEPTION WHILE DESTROYING OBJECT " + info);
e.printStackTrace();
}
}
// ***********************************************************************
// PooledConnectionManager implementation
// ***********************************************************************
/**
* Invalidates the PooledConnection in the pool. The KeyedCPDSConnectionFactory closes the connection and pool
* counters are updated appropriately. Also clears any idle instances associated with the user name that was used to
* create the PooledConnection. Connections associated with this user are not affected and they will not be
* automatically closed on return to the pool.
*/
@Override
public void invalidate(final PooledConnection pc) throws SQLException {
final PooledConnectionAndInfo info = pcMap.get(pc);
if (info == null) {
throw new IllegalStateException(NO_KEY_MESSAGE);
}
final UserPassKey key = info.getUserPassKey();
try {
pool.invalidateObject(key, info); // Destroy and update pool counters
pool.clear(key); // Remove any idle instances with this key
} catch (final Exception ex) {
throw new SQLException("Error invalidating connection", ex);
}
}
/**
* Does nothing. This factory does not cache user credentials.
*/
@Override
public void setPassword(final String password) {
// Does nothing. This factory does not cache user credentials.
}
/**
* Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
* passivation and validation.
*
* @param maxConnLifetimeMillis
* A value of zero or less indicates an infinite lifetime. The default value is -1.
*/
public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
this.maxConnLifetimeMillis = maxConnLifetimeMillis;
}
/**
* This implementation does not fully close the KeyedObjectPool, as this would affect all users. Instead, it clears
* the pool associated with the given user. This method is not currently used.
*/
@Override
public void closePool(final String userName) throws SQLException {
try {
pool.clear(new UserPassKey(userName));
} catch (final Exception ex) {
throw new SQLException("Error closing connection pool", ex);
}
}
private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
if (maxConnLifetimeMillis > 0) {
final long lifetime = System.currentTimeMillis() - p.getCreateTime();
if (lifetime > maxConnLifetimeMillis) {
throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", Long.valueOf(lifetime),
Long.valueOf(maxConnLifetimeMillis)));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
/*
* 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.util.Map;
import javax.naming.RefAddr;
import javax.naming.Reference;
/**
* A JNDI ObjectFactory which creates <code>SharedPoolDataSource</code>s
*
* @since 2.0
*/
public class PerUserPoolDataSourceFactory extends InstanceKeyDataSourceFactory {
private static final String PER_USER_POOL_CLASSNAME = PerUserPoolDataSource.class.getName();
@Override
protected boolean isCorrectClass(final String className) {
return PER_USER_POOL_CLASSNAME.equals(className);
}
@SuppressWarnings("unchecked") // Avoid warnings on deserialization
@Override
protected InstanceKeyDataSource getNewInstance(final Reference ref) throws IOException, ClassNotFoundException {
final PerUserPoolDataSource pupds = new PerUserPoolDataSource();
RefAddr ra = ref.get("defaultMaxTotal");
if (ra != null && ra.getContent() != null) {
pupds.setDefaultMaxTotal(Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("defaultMaxIdle");
if (ra != null && ra.getContent() != null) {
pupds.setDefaultMaxIdle(Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("defaultMaxWaitMillis");
if (ra != null && ra.getContent() != null) {
pupds.setDefaultMaxWaitMillis(Integer.parseInt(ra.getContent().toString()));
}
ra = ref.get("perUserDefaultAutoCommit");
if (ra != null && ra.getContent() != null) {
final byte[] serialized = (byte[]) ra.getContent();
pupds.setPerUserDefaultAutoCommit((Map<String, Boolean>) deserialize(serialized));
}
ra = ref.get("perUserDefaultTransactionIsolation");
if (ra != null && ra.getContent() != null) {
final byte[] serialized = (byte[]) ra.getContent();
pupds.setPerUserDefaultTransactionIsolation((Map<String, Integer>) deserialize(serialized));
}
ra = ref.get("perUserMaxTotal");
if (ra != null && ra.getContent() != null) {
final byte[] serialized = (byte[]) ra.getContent();
pupds.setPerUserMaxTotal((Map<String, Integer>) deserialize(serialized));
}
ra = ref.get("perUserMaxIdle");
if (ra != null && ra.getContent() != null) {
final byte[] serialized = (byte[]) ra.getContent();
pupds.setPerUserMaxIdle((Map<String, Integer>) deserialize(serialized));
}
ra = ref.get("perUserMaxWaitMillis");
if (ra != null && ra.getContent() != null) {
final byte[] serialized = (byte[]) ra.getContent();
pupds.setPerUserMaxWaitMillis((Map<String, Long>) deserialize(serialized));
}
ra = ref.get("perUserDefaultReadOnly");
if (ra != null && ra.getContent() != null) {
final byte[] serialized = (byte[]) ra.getContent();
pupds.setPerUserDefaultReadOnly((Map<String, Boolean>) deserialize(serialized));
}
return pupds;
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.Serializable;
/**
* @since 2.0
*/
class PoolKey implements Serializable {
private static final long serialVersionUID = 2252771047542484533L;
private final String dataSourceName;
private final String userName;
PoolKey(final String dataSourceName, final String userName) {
this.dataSourceName = dataSourceName;
this.userName = userName;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PoolKey other = (PoolKey) obj;
if (dataSourceName == null) {
if (other.dataSourceName != null) {
return false;
}
} else if (!dataSourceName.equals(other.dataSourceName)) {
return false;
}
if (userName == null) {
if (other.userName != null) {
return false;
}
} else if (!userName.equals(other.userName)) {
return false;
}
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((dataSourceName == null) ? 0 : dataSourceName.hashCode());
result = prime * result + ((userName == null) ? 0 : userName.hashCode());
return result;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer(50);
sb.append("PoolKey(");
sb.append(userName).append(", ").append(dataSourceName);
sb.append(')');
return sb.toString();
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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 javax.sql.PooledConnection;
import org.apache.tomcat.dbcp.dbcp2.Utils;
/**
* Immutable poolable object holding a PooledConnection along with the user name and password used to create the
* connection.
*
* @since 2.0
*/
final class PooledConnectionAndInfo {
private final PooledConnection pooledConnection;
private final char[] userPassword;
private final String userName;
private final UserPassKey upKey;
/**
* @since 2.4.0
*/
PooledConnectionAndInfo(final PooledConnection pc, final String userName, final char[] userPassword) {
this.pooledConnection = pc;
this.userName = userName;
this.userPassword = userPassword;
this.upKey = new UserPassKey(userName, userPassword);
}
/**
* @deprecated Since 2.4.0
*/
@Deprecated
PooledConnectionAndInfo(final PooledConnection pc, final String userName, final String userPassword) {
this(pc, userName, Utils.toCharArray(userPassword));
}
PooledConnection getPooledConnection() {
return pooledConnection;
}
UserPassKey getUserPassKey() {
return upKey;
}
/**
* Gets the value of password.
*
* @return value of password.
*/
String getPassword() {
return Utils.toString(userPassword);
}
/**
* Gets the value of password.
*
* @return value of password.
* @since 2.4.0
*/
char[] getPasswordCharArray() {
return userPassword;
}
/**
* Gets the value of userName.
*
* @return value of userName.
*/
String getUsername() {
return userName;
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.sql.SQLException;
import javax.sql.PooledConnection;
/**
* Methods to manage PoolableConnections and the connection pools that source them.
*
* @since 2.0
*/
interface PooledConnectionManager {
/**
* Closes the PooledConnection and remove it from the connection pool to which it belongs, adjusting pool counters.
*
* @param pc
* PooledConnection to be invalidated
* @throws SQLException
* if an SQL error occurs closing the connection
*/
void invalidate(PooledConnection pc) throws SQLException;
// /**
// * Sets the database password used when creating connections.
// *
// * @param password password used when authenticating to the database
// * @since 3.0.0
// */
// void setPassword(char[] password);
/**
* Sets the database password used when creating connections.
*
* @param password
* password used when authenticating to the database
*/
void setPassword(String password);
/**
* Closes the connection pool associated with the given user.
*
* @param userName
* user name
* @throws SQLException
* if an error occurs closing idle connections in the pool
*/
void closePool(String userName) throws SQLException;
}

View File

@@ -0,0 +1,245 @@
/*
* 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 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)</code> request is processed with a password that is different from those
* used to create connections in the pool associated with <code>user name</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>
*
* @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;
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 Exception {
if (pool != null) {
pool.close();
}
InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
}
// -------------------------------------------------------------------
// Properties
/**
* Gets {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
*
* @return {@link GenericKeyedObjectPool#getMaxTotal()} for this pool.
*/
public int getMaxTotal() {
return this.maxTotal;
}
/**
* 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;
}
// ----------------------------------------------------------------------
// Instrumentation Methods
/**
* 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();
}
// ----------------------------------------------------------------------
// Inherited abstract methods
@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);
}
}
}
PooledConnectionAndInfo info = null;
final UserPassKey key = new UserPassKey(userName, userPassword);
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;
}
/**
* Returns a <code>SharedPoolDataSource</code> {@link Reference}.
*/
@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<PooledConnectionAndInfo> 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 connection, final String userName) throws SQLException {
final Boolean defaultAutoCommit = isDefaultAutoCommit();
if (defaultAutoCommit != null && connection.getAutoCommit() != defaultAutoCommit.booleanValue()) {
connection.setAutoCommit(defaultAutoCommit.booleanValue());
}
final int defaultTransactionIsolation = getDefaultTransactionIsolation();
if (defaultTransactionIsolation != UNKNOWN_TRANSACTIONISOLATION) {
connection.setTransactionIsolation(defaultTransactionIsolation);
}
final Boolean defaultReadOnly = isDefaultReadOnly();
if (defaultReadOnly != null && connection.isReadOnly() != defaultReadOnly.booleanValue()) {
connection.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);
}
}
@Override
protected void toStringFields(final StringBuilder builder) {
super.toStringFields(builder);
builder.append(", maxTotal=");
builder.append(maxTotal);
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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 javax.naming.RefAddr;
import javax.naming.Reference;
/**
* A JNDI ObjectFactory which creates <code>SharedPoolDataSource</code>s
*
* @since 2.0
*/
public class SharedPoolDataSourceFactory extends InstanceKeyDataSourceFactory {
private static final String SHARED_POOL_CLASSNAME = SharedPoolDataSource.class.getName();
@Override
protected boolean isCorrectClass(final String className) {
return SHARED_POOL_CLASSNAME.equals(className);
}
@Override
protected InstanceKeyDataSource getNewInstance(final Reference ref) {
final SharedPoolDataSource spds = new SharedPoolDataSource();
final RefAddr ra = ref.get("maxTotal");
if (ra != null && ra.getContent() != null) {
spds.setMaxTotal(Integer.parseInt(ra.getContent().toString()));
}
return spds;
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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.Serializable;
import org.apache.tomcat.dbcp.dbcp2.Utils;
/**
* <p>
* Holds a user name and password pair. Serves as a poolable object key for the KeyedObjectPool backing a
* SharedPoolDataSource. Two instances with the same user name are considered equal. This ensures that there will be
* only one keyed pool for each user in the pool. The password is used (along with the user name) by the
* KeyedCPDSConnectionFactory when creating new connections.
* </p>
*
* <p>
* {@link InstanceKeyDataSource#getConnection(String, String)} validates that the password used to create a connection
* matches the password provided by the client.
* </p>
*
* @since 2.0
*/
class UserPassKey implements Serializable {
private static final long serialVersionUID = 5142970911626584817L;
private final String userName;
private final char[] userPassword;
/**
* @since 2.4.0
*/
UserPassKey(final String userName) {
this(userName, (char[]) null);
}
/**
* @since 2.4.0
*/
UserPassKey(final String userName, final char[] password) {
this.userName = userName;
this.userPassword = password;
}
UserPassKey(final String userName, final String userPassword) {
this(userName, Utils.toCharArray(userPassword));
}
/**
* Only takes the user name into account.
*/
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final UserPassKey other = (UserPassKey) obj;
if (userName == null) {
if (other.userName != null) {
return false;
}
} else if (!userName.equals(other.userName)) {
return false;
}
return true;
}
/**
* Gets the value of password.
*
* @return value of password.
*/
public String getPassword() {
return Utils.toString(userPassword);
}
/**
* Gets the value of password.
*
* @return value of password.
*/
public char[] getPasswordCharArray() {
return userPassword;
}
/**
* Gets the value of user name.
*
* @return value of user name.
*/
public String getUsername() {
return userName;
}
/**
* Only takes the user name into account.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((userName == null) ? 0 : userName.hashCode());
return result;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer(super.toString());
sb.append("[");
sb.append(userName);
sb.append(']');
return sb.toString();
}
}

View File

@@ -0,0 +1,183 @@
/*
* 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.
*/
/**
* <p>
* This package contains two DataSources: <code>PerUserPoolDataSource</code> and
* <code>SharedPoolDataSource</code> which provide a database connection pool.
* Below are a couple of usage examples. One shows deployment into a JNDI system.
* The other is a simple example initializing the pool using standard java code.
* </p>
*
* <h2>JNDI</h2>
*
* <p>
* Most
* J2EE containers will provide some way of deploying resources into JNDI. The
* method will vary among containers, but once the resource is available via
* JNDI, the application can access the resource in a container independent
* manner. The following example shows deployment into tomcat (catalina).
* </p>
* <p>In server.xml, the following would be added to the &lt;Context&gt; for your
* webapp:
* </p>
*
* <code>
* &lt;Resource name="jdbc/bookstore" auth="Container"
* type="org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolPoolDataSource"/&gt;
* &lt;ResourceParams name="jdbc/bookstore"&gt;
* &lt;parameter&gt;
* &lt;name&gt;factory&lt;/name&gt;
* &lt;value&gt;org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSourceFactory&lt;/value&gt;
* &lt;/parameter&gt;
* &lt;parameter&gt;
* &lt;name&gt;dataSourceName&lt;/name&gt;&lt;value&gt;java:comp/env/jdbc/bookstoreCPDS&lt;/value&gt;
* &lt;/parameter&gt;
* &lt;parameter&gt;
* &lt;name&gt;defaultMaxTotal&lt;/name&gt;&lt;value&gt;30&lt;/value&gt;
* &lt;/parameter&gt;
* &lt;/ResourceParams&gt;
* </code>
*
* <p>
* In web.xml. Note that elements must be given in the order of the dtd
* described in the servlet specification:
* </p>
*
* <code>
* &lt;resource-ref&gt;
* &lt;description&gt;
* Resource reference to a factory for java.sql.Connection
* instances that may be used for talking to a particular
* database that is configured in the server.xml file.
* &lt;/description&gt;
* &lt;res-ref-name&gt;
* jdbc/bookstore
* &lt;/res-ref-name&gt;
* &lt;res-type&gt;
* org.apache.tomcat.dbcp.dbcp2.datasources.PerUserPoolDataSource
* &lt;/res-type&gt;
* &lt;res-auth&gt;
* Container
* &lt;/res-auth&gt;
* &lt;/resource-ref&gt;
* </code>
*
* <p>
* Apache Tomcat deploys all objects configured similarly to above within the
* <strong>java:comp/env</strong> namespace. So the JNDI path given for
* the dataSourceName parameter is valid for a
* <code>ConnectionPoolDataSource</code> that is deployed as given in the
* <a href="../cpdsadapter/package.html">cpdsadapter example</a>
* </p>
*
* <p>
* The <code>DataSource</code> is now available to the application as shown
* below:
* </p>
*
* <code>
*
* Context ctx = new InitialContext();
* DataSource ds = (DataSource)
* ctx.lookup("java:comp/env/jdbc/bookstore");
* Connection con = null;
* try
* {
* con = ds.getConnection();
* ...
* use the connection
* ...
* }
* finally
* {
* if (con != null)
* con.close();
* }
*
* </code>
*
* <p>
* The reference to the <code>DataSource</code> could be maintained, for
* multiple getConnection() requests. Or the <code>DataSource</code> can be
* looked up in different parts of the application code.
* <code>PerUserPoolDataSourceFactory</code> and
* <code>SharedPoolDataSourceFactory</code> will maintain the state of the pool
* between different lookups. This behavior may be different in other
* implementations.
* </p>
*
* <h2>Without JNDI</h2>
*
* <p>
* Connection pooling is useful in applications regardless of whether they run
* in a J2EE environment and a <code>DataSource</code> can be used within a
* simpler environment. The example below shows SharedPoolDataSource using
* DriverAdapterCPDS as the backend source, though any CPDS is applicable.
* </p>
*
* <code>
*
* public class Pool
* {
* private static DataSource ds;
*
* static
* {
* DriverAdapterCPDS cpds = new DriverAdapterCPDS();
* cpds.setDriver("org.gjt.mm.mysql.Driver");
* cpds.setUrl("jdbc:mysql://localhost:3306/bookstore");
* cpds.setUser("foo");
* cpds.setPassword(null);
*
* SharedPoolDataSource tds = new SharedPoolDataSource();
* tds.setConnectionPoolDataSource(cpds);
* tds.setMaxTotal(10);
* tds.setMaxWaitMillis(50);
*
* ds = tds;
* }
*
* public static getConnection()
* {
* return ds.getConnection();
* }
* }
*
* </code>
*
* <p>
* This class can then be used wherever a connection is needed:
* </p>
*
* <code>
* Connection con = null;
* try
* {
* con = Pool.getConnection();
* ...
* use the connection
* ...
* }
* finally
* {
* if (con != null)
* con.close();
* }
* </code>
*/
package org.apache.tomcat.dbcp.dbcp2.datasources;