CPDSConnectionFactory.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.sql.Connection;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.sql.Statement;
- import java.time.Duration;
- 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.commons.dbcp2.Utils;
- import org.apache.commons.pool2.ObjectPool;
- import org.apache.commons.pool2.PooledObject;
- import org.apache.commons.pool2.PooledObjectFactory;
- import org.apache.commons.pool2.impl.DefaultPooledObject;
- /**
- * A {@link PooledObjectFactory} that creates {@link org.apache.commons.dbcp2.PoolableConnection PoolableConnection}s.
- *
- * @since 2.0
- */
- final 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 Duration validationQueryTimeoutDuration;
- private final boolean rollbackAfterValidation;
- private ObjectPool<PooledConnectionAndInfo> pool;
- private UserPassKey userPassKey;
- private Duration maxConnDuration = Duration.ofMillis(-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<>());
- /**
- * 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 validationQueryTimeoutDuration
- * Timeout Duration 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.10.0
- */
- public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
- final Duration validationQueryTimeoutDuration, final boolean rollbackAfterValidation, final String userName,
- final char[] userPassword) {
- this.cpds = cpds;
- this.validationQuery = validationQuery;
- this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
- this.userPassKey = new UserPassKey(userName, 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 validationQueryTimeoutDuration
- * 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.10.0
- */
- public CPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery, final Duration validationQueryTimeoutDuration,
- final boolean rollbackAfterValidation, final String userName, final String userPassword) {
- this(cpds, validationQuery, validationQueryTimeoutDuration, rollbackAfterValidation, userName, Utils.toCharArray(userPassword));
- }
- /**
- * 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
- * @deprecated Use {@link #CPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean, String, char[])}.
- */
- @Deprecated
- 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.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
- this.userPassKey = new UserPassKey(userName, 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
- * @deprecated Use {@link #CPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean, String, String)}.
- */
- @Deprecated
- 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));
- }
- @Override
- public void activateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
- validateLifetime(p);
- }
- /**
- * 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.userPassKey.getUserName())) {
- return;
- }
- }
- try {
- pool.close();
- } catch (final Exception ex) {
- throw new SQLException("Error closing connection pool", ex);
- }
- }
- /**
- * 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();
- }
- }
- /**
- * Closes the PooledConnection and stops listening for events from it.
- */
- @Override
- public void destroyObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
- doDestroyObject(p.getObject());
- }
- private void doDestroyObject(final PooledConnectionAndInfo pci) throws SQLException {
- final PooledConnection pc = pci.getPooledConnection();
- pc.removeConnectionEventListener(this);
- pcMap.remove(pc);
- pc.close();
- }
- /**
- * (Testing API) Gets the value of password for the default user.
- *
- * @return value of password.
- */
- char[] getPasswordCharArray() {
- return userPassKey.getPasswordCharArray();
- }
- /**
- * Returns the object pool used to pool connections created by this factory.
- *
- * @return ObjectPool managing pooled connections
- */
- public ObjectPool<PooledConnectionAndInfo> getPool() {
- return pool;
- }
- /**
- * 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);
- }
- }
- @Override
- public synchronized PooledObject<PooledConnectionAndInfo> makeObject() throws SQLException {
- PooledConnection pc = null;
- if (userPassKey.getUserName() == null) {
- pc = cpds.getPooledConnection();
- } else {
- pc = cpds.getPooledConnection(userPassKey.getUserName(), userPassKey.getPassword());
- }
- 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);
- final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pc, userPassKey);
- pcMap.put(pc, pci);
- return new DefaultPooledObject<>(pci);
- }
- @Override
- public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
- validateLifetime(p);
- }
- /**
- * Sets the maximum Duration of a connection after which the connection will always fail activation,
- * passivation and validation.
- *
- * @param maxConnDuration
- * A value of zero or less indicates an infinite lifetime. The default value is -1 milliseconds.
- * @since 2.10.0
- */
- public void setMaxConn(final Duration maxConnDuration) {
- this.maxConnDuration = maxConnDuration;
- }
- /**
- * Sets the maximum lifetime in milliseconds of a connection after which the connection will always fail activation,
- * passivation and validation.
- *
- * @param maxConnDuration
- * A value of zero or less indicates an infinite lifetime. The default value is -1 milliseconds.
- * @since 2.9.0
- * @deprecated Use {@link #setMaxConn(Duration)}.
- */
- @Deprecated
- public void setMaxConnLifetime(final Duration maxConnDuration) {
- this.maxConnDuration = maxConnDuration;
- }
- /**
- * 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.
- * @deprecated Use {@link #setMaxConn(Duration)}.
- */
- @Deprecated
- public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
- setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis));
- }
- /**
- * Sets the database password used when creating new connections.
- *
- * @param userPassword
- * new password
- */
- public synchronized void setPassword(final char[] userPassword) {
- this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
- }
- /**
- * Sets the database password used when creating new connections.
- *
- * @param userPassword
- * new password
- */
- @Override
- public synchronized void setPassword(final String userPassword) {
- this.userPassKey = new UserPassKey(userPassKey.getUserName(), userPassword);
- }
- /**
- *
- * @param pool
- * the {@link ObjectPool} in which to pool those {@link Connection}s
- */
- public void setPool(final ObjectPool<PooledConnectionAndInfo> pool) {
- this.pool = pool;
- }
- /**
- * @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(", validationQueryTimeoutDuration=");
- builder.append(validationQueryTimeoutDuration);
- builder.append(", rollbackAfterValidation=");
- builder.append(rollbackAfterValidation);
- builder.append(", pool=");
- builder.append(pool);
- builder.append(", maxConnDuration=");
- builder.append(maxConnDuration);
- builder.append(", validatingSet=");
- builder.append(validatingSet);
- builder.append(", pcMap=");
- builder.append(pcMap);
- builder.append("]");
- return builder.toString();
- }
- private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
- Utils.validateLifetime(p, maxConnDuration);
- }
- @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) {
- Duration timeoutDuration = validationQueryTimeoutDuration;
- if (timeoutDuration.isNegative()) {
- timeoutDuration = Duration.ZERO;
- }
- try {
- conn = pconn.getConnection();
- valid = conn.isValid((int) timeoutDuration.getSeconds());
- } catch (final SQLException e) {
- valid = false;
- } finally {
- Utils.closeQuietly((AutoCloseable) 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);
- valid = rset.next();
- if (rollbackAfterValidation) {
- conn.rollback();
- }
- } catch (final Exception e) {
- valid = false;
- } finally {
- Utils.closeQuietly((AutoCloseable) rset);
- Utils.closeQuietly((AutoCloseable) stmt);
- Utils.closeQuietly((AutoCloseable) conn);
- validatingSet.remove(pconn);
- }
- }
- return valid;
- }
- }