You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/07/02 22:47:25 UTC

[commons-dbcp] branch master updated: Replace Exception with SQLException in some method signatures (preserves binary compatibility, not source).

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-dbcp.git


The following commit(s) were added to refs/heads/master by this push:
     new 67d2c666 Replace Exception with SQLException in some method signatures (preserves binary compatibility, not source).
67d2c666 is described below

commit 67d2c666d8e9824c25c083f6341ff00d76591ae7
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 18:47:16 2022 -0400

    Replace Exception with SQLException in some method signatures (preserves
    binary compatibility, not source).
---
 src/changes/changes.xml                            |    3 +
 .../commons/dbcp2/PoolableConnectionFactory.java   |    6 +-
 .../apache/commons/dbcp2/PoolingConnection.java    |    8 +-
 .../dbcp2/cpdsadapter/DriverAdapterCPDS.java       | 1628 ++++++++++----------
 .../dbcp2/cpdsadapter/PooledConnectionImpl.java    | 1516 +++++++++---------
 .../dbcp2/datasources/CPDSConnectionFactory.java   |  996 ++++++------
 .../datasources/InstanceKeyDataSourceFactory.java  |    5 +-
 .../datasources/KeyedCPDSConnectionFactory.java    |  780 +++++-----
 .../dbcp2/datasources/SharedPoolDataSource.java    |  460 +++---
 .../managed/PoolableManagedConnectionFactory.java  |  221 +--
 10 files changed, 2813 insertions(+), 2810 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 0233572f..f7c7627f 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -104,6 +104,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add Utils.getDisconnectionSqlCodes() and Utils.DISCONNECTION_SQL_CODES.
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Replace Exception with SQLException in some method signatures (preserves binary compatibility, not source).
+      </action>
       <!-- UPDATE -->
       <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
         Bump actions/cache from 2.1.6 to 3.0.4 #147, #176.
diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
index dd2234ca..80aa601d 100644
--- a/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
@@ -137,7 +137,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
     }
 
     @Override
-    public void destroyObject(final PooledObject<PoolableConnection> p) throws Exception {
+    public void destroyObject(final PooledObject<PoolableConnection> p) throws SQLException {
         p.getObject().reallyClose();
     }
 
@@ -145,7 +145,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
      * @since 2.9.0
      */
     @Override
-    public void destroyObject(final PooledObject<PoolableConnection> p, final DestroyMode mode) throws Exception {
+    public void destroyObject(final PooledObject<PoolableConnection> p, final DestroyMode mode) throws SQLException {
         if (mode == DestroyMode.ABANDONED) {
             p.getObject().getInnermostDelegate().abort(Runnable::run);
         } else {
@@ -744,7 +744,7 @@ public class PoolableConnectionFactory implements PooledObjectFactory<PoolableCo
         conn.validate(validationQuery, validationQueryTimeoutDuration);
     }
 
-    private void validateLifetime(final PooledObject<PoolableConnection> p) throws Exception {
+    private void validateLifetime(final PooledObject<PoolableConnection> p) throws LifetimeExceededException {
         if (maxConnDuration.compareTo(Duration.ZERO) > 0) {
             final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now());
             if (lifetimeDuration.compareTo(maxConnDuration) > 0) {
diff --git a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java
index fb1a7915..18f68aa5 100644
--- a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java
+++ b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java
@@ -86,7 +86,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      */
     @Override
     public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         pooledObject.getObject().activate();
     }
 
@@ -292,7 +292,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      */
     @Override
     public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         pooledObject.getObject().getInnermostDelegate().close();
     }
 
@@ -337,7 +337,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      */
     @SuppressWarnings("resource")
     @Override
-    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws Exception {
+    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException {
         if (null == key) {
             throw new IllegalArgumentException("Prepared statement key is null or invalid.");
         }
@@ -374,7 +374,7 @@ public class PoolingConnection extends DelegatingConnection<Connection>
      */
     @Override
     public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
+            throws SQLException {
         @SuppressWarnings("resource")
         final DelegatingPreparedStatement dps = pooledObject.getObject();
         dps.clearParameters();
diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java
index 17df35e5..881181bf 100644
--- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java
+++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/DriverAdapterCPDS.java
@@ -1,814 +1,814 @@
-/*
- * 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.cpdsadapter;
-
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.time.Duration;
-import java.util.Hashtable;
-import java.util.Properties;
-import java.util.logging.Logger;
-
-import javax.naming.Context;
-import javax.naming.Name;
-import javax.naming.NamingException;
-import javax.naming.RefAddr;
-import javax.naming.Reference;
-import javax.naming.Referenceable;
-import javax.naming.StringRefAddr;
-import javax.naming.spi.ObjectFactory;
-import javax.sql.ConnectionPoolDataSource;
-import javax.sql.PooledConnection;
-
-import org.apache.commons.dbcp2.Constants;
-import org.apache.commons.dbcp2.DelegatingPreparedStatement;
-import org.apache.commons.dbcp2.PStmtKey;
-import org.apache.commons.dbcp2.Utils;
-import org.apache.commons.pool2.KeyedObjectPool;
-import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
-import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
-import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
-
-/**
- * <p>
- * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but
- * still include a {@link java.sql.DriverManager} implementation. {@code ConnectionPoolDataSource}s are not used
- * within general applications. They are used by {@code DataSource} implementations that pool
- * {@code Connection}s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
- * will normally provide some method of initializing the {@code ConnectionPoolDataSource} whose attributes are
- * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical
- * connections to the database, when the pooling {@code DataSource} needs to create a new physical connection.
- * </p>
- * <p>
- * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any
- * bean and then attached directly to a pooling {@code DataSource}. {@code Jdbc2PoolDataSource} can use the
- * {@code ConnectionPoolDataSource} with or without the use of JNDI.
- * </p>
- * <p>
- * The DriverAdapterCPDS also provides {@code PreparedStatement} pooling which is not generally available in jdbc2
- * {@code ConnectionPoolDataSource} implementation, but is addressed within the jdbc3 specification. The
- * {@code PreparedStatement} pool in DriverAdapterCPDS has been in the dbcp package for some time, but it has not
- * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled
- * with the poolPreparedStatements attribute.
- * </p>
- * <p>
- * The <a href="package-summary.html">package documentation</a> contains an example using catalina and JNDI. The
- * <a href="../datasources/package-summary.html">datasources package documentation</a> shows how to use
- * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI.
- * </p>
- *
- * @since 2.0
- */
-public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory {
-
-    private static final long serialVersionUID = -4820523787212147844L;
-
-    private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, "
-        + "further initialization is not allowed.";
-
-    static {
-        // Attempt to prevent deadlocks - see DBCP - 272
-        DriverManager.getDrivers();
-    }
-
-    /** Description */
-    private String description;
-
-    /** Url name */
-    private String url;
-
-    /** User name */
-    private String userName;
-
-    /** User password */
-    private char[] userPassword;
-
-    /** Driver class name */
-    private String driver;
-
-    /** Login TimeOut in seconds */
-    private int loginTimeout;
-
-    /** Log stream. NOT USED */
-    private transient PrintWriter logWriter;
-
-    // PreparedStatement pool properties
-    private boolean poolPreparedStatements;
-    private int maxIdle = 10;
-    private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
-    private int numTestsPerEvictionRun = -1;
-    private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
-
-    private int maxPreparedStatements = -1;
-
-    /** Whether or not getConnection has been called */
-    private volatile boolean getConnectionCalled;
-
-    /** Connection properties passed to JDBC Driver */
-    private Properties connectionProperties;
-
-    /**
-     * Controls access to the underlying connection
-     */
-    private boolean accessToUnderlyingConnectionAllowed;
-
-    /**
-     * Default no-argument constructor for Serialization
-     */
-    public DriverAdapterCPDS() {
-    }
-
-    /**
-     * Throws an IllegalStateException, if a PooledConnection has already been requested.
-     */
-    private void assertInitializationAllowed() throws IllegalStateException {
-        if (getConnectionCalled) {
-            throw new IllegalStateException(GET_CONNECTION_CALLED);
-        }
-    }
-
-    private boolean getBooleanContentString(final RefAddr ra) {
-        return Boolean.parseBoolean(getStringContent(ra));
-    }
-
-    /**
-     * Gets the connection properties passed to the JDBC driver.
-     *
-     * @return the JDBC connection properties used when creating connections.
-     */
-    public Properties getConnectionProperties() {
-        return connectionProperties;
-    }
-
-    /**
-     * Gets the value of description. This property is here for use by the code which will deploy this data source. It
-     * is not used internally.
-     *
-     * @return value of description, may be null.
-     * @see #setDescription(String)
-     */
-    public String getDescription() {
-        return description;
-    }
-
-    /**
-     * Gets the driver class name.
-     *
-     * @return value of driver.
-     */
-    public String getDriver() {
-        return driver;
-    }
-
-    /**
-     * Gets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
-     * idle object evictor thread will be run.
-     *
-     * @return the value of the evictor thread timer
-     * @see #setDurationBetweenEvictionRuns(Duration)
-     * @since 2.9.0
-     */
-    public Duration getDurationBetweenEvictionRuns() {
-        return durationBetweenEvictionRuns;
-    }
-
-    private int getIntegerStringContent(final RefAddr ra) {
-        return Integer.parseInt(getStringContent(ra));
-    }
-
-    /**
-     * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database. NOT
-     * USED.
-     */
-    @Override
-    public int getLoginTimeout() {
-        return loginTimeout;
-    }
-
-    /**
-     * Gets the log writer for this data source. NOT USED.
-     */
-    @Override
-    public PrintWriter getLogWriter() {
-        return logWriter;
-    }
-
-    /**
-     * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
-     * negative for no limit.
-     *
-     * @return the value of maxIdle
-     */
-    public int getMaxIdle() {
-        return maxIdle;
-    }
-
-    /**
-     * Gets the maximum number of prepared statements.
-     *
-     * @return maxPrepartedStatements value
-     */
-    public int getMaxPreparedStatements() {
-        return maxPreparedStatements;
-    }
-
-    /**
-     * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
-     * idle object evictor (if any).
-     *
-     * @see #setMinEvictableIdleDuration
-     * @see #setDurationBetweenEvictionRuns
-     * @return the minimum amount of time a statement may sit idle in the pool.
-     * @since 2.9.0
-     */
-    public Duration getMinEvictableIdleDuration() {
-        return minEvictableIdleDuration;
-    }
-
-    /**
-     * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
-     * idle object evictor (if any).
-     *
-     * @see #setMinEvictableIdleTimeMillis
-     * @see #setTimeBetweenEvictionRunsMillis
-     * @return the minimum amount of time a statement may sit idle in the pool.
-     * @deprecated USe {@link #getMinEvictableIdleDuration()}.
-     */
-    @Deprecated
-    public int getMinEvictableIdleTimeMillis() {
-        return (int) minEvictableIdleDuration.toMillis();
-    }
-
-    /**
-     * Gets the number of statements to examine during each run of the idle object evictor thread (if any.)
-     *
-     * @see #setNumTestsPerEvictionRun
-     * @see #setTimeBetweenEvictionRunsMillis
-     * @return the number of statements to examine during each run of the idle object evictor thread (if any.)
-     */
-    public int getNumTestsPerEvictionRun() {
-        return numTestsPerEvictionRun;
-    }
-
-    /**
-     * Implements {@link ObjectFactory} to create an instance of this class
-     */
-    @Override
-    public Object getObjectInstance(final Object refObj, final Name name, final Context context,
-        final Hashtable<?, ?> env) throws Exception {
-        // The spec says to return null if we can't create an instance
-        // of the reference
-        DriverAdapterCPDS cpds = null;
-        if (refObj instanceof Reference) {
-            final Reference ref = (Reference) refObj;
-            if (ref.getClassName().equals(getClass().getName())) {
-                RefAddr ra = ref.get("description");
-                if (isNotEmpty(ra)) {
-                    setDescription(getStringContent(ra));
-                }
-
-                ra = ref.get("driver");
-                if (isNotEmpty(ra)) {
-                    setDriver(getStringContent(ra));
-                }
-                ra = ref.get("url");
-                if (isNotEmpty(ra)) {
-                    setUrl(getStringContent(ra));
-                }
-                ra = ref.get(Constants.KEY_USER);
-                if (isNotEmpty(ra)) {
-                    setUser(getStringContent(ra));
-                }
-                ra = ref.get(Constants.KEY_PASSWORD);
-                if (isNotEmpty(ra)) {
-                    setPassword(getStringContent(ra));
-                }
-
-                ra = ref.get("poolPreparedStatements");
-                if (isNotEmpty(ra)) {
-                    setPoolPreparedStatements(getBooleanContentString(ra));
-                }
-                ra = ref.get("maxIdle");
-                if (isNotEmpty(ra)) {
-                    setMaxIdle(getIntegerStringContent(ra));
-                }
-
-                ra = ref.get("timeBetweenEvictionRunsMillis");
-                if (isNotEmpty(ra)) {
-                    setTimeBetweenEvictionRunsMillis(getIntegerStringContent(ra));
-                }
-
-                ra = ref.get("numTestsPerEvictionRun");
-                if (isNotEmpty(ra)) {
-                    setNumTestsPerEvictionRun(getIntegerStringContent(ra));
-                }
-
-                ra = ref.get("minEvictableIdleTimeMillis");
-                if (isNotEmpty(ra)) {
-                    setMinEvictableIdleTimeMillis(getIntegerStringContent(ra));
-                }
-
-                ra = ref.get("maxPreparedStatements");
-                if (isNotEmpty(ra)) {
-                    setMaxPreparedStatements(getIntegerStringContent(ra));
-                }
-
-                ra = ref.get("accessToUnderlyingConnectionAllowed");
-                if (isNotEmpty(ra)) {
-                    setAccessToUnderlyingConnectionAllowed(getBooleanContentString(ra));
-                }
-
-                cpds = this;
-            }
-        }
-        return cpds;
-    }
-
-    @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    /**
-     * Gets the value of password for the default user.
-     *
-     * @return value of password.
-     */
-    public String getPassword() {
-        return Utils.toString(userPassword);
-    }
-
-    /**
-     * Gets the value of password for the default user.
-     *
-     * @return value of password.
-     * @since 2.4.0
-     */
-    public char[] getPasswordCharArray() {
-        return userPassword == null ? null : userPassword.clone();
-    }
-
-    /**
-     * Attempts to establish a database connection using the default user and password.
-     */
-    @Override
-    public PooledConnection getPooledConnection() throws SQLException {
-        return getPooledConnection(getUser(), getPassword());
-    }
-
-    /**
-     * Attempts to establish a database connection.
-     *
-     * @param pooledUserName name to be used for the connection
-     * @param pooledUserPassword password to be used fur the connection
-     */
-    @Override
-    public PooledConnection getPooledConnection(final String pooledUserName, final String pooledUserPassword)
-        throws SQLException {
-        getConnectionCalled = true;
-        PooledConnectionImpl pooledConnection = null;
-        // Workaround for buggy WebLogic 5.1 class loader - ignore the exception upon first invocation.
-        try {
-            if (connectionProperties != null) {
-                update(connectionProperties, Constants.KEY_USER, pooledUserName);
-                update(connectionProperties, Constants.KEY_PASSWORD, pooledUserPassword);
-                pooledConnection = new PooledConnectionImpl(
-                    DriverManager.getConnection(getUrl(), connectionProperties));
-            } else {
-                pooledConnection = new PooledConnectionImpl(
-                    DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword));
-            }
-            pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
-        } catch (final ClassCircularityError e) {
-            if (connectionProperties != null) {
-                pooledConnection = new PooledConnectionImpl(
-                    DriverManager.getConnection(getUrl(), connectionProperties));
-            } else {
-                pooledConnection = new PooledConnectionImpl(
-                    DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword));
-            }
-            pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
-        }
-        KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = null;
-        if (isPoolPreparedStatements()) {
-            final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
-            config.setMaxTotalPerKey(Integer.MAX_VALUE);
-            config.setBlockWhenExhausted(false);
-            config.setMaxWait(Duration.ZERO);
-            config.setMaxIdlePerKey(getMaxIdle());
-            if (getMaxPreparedStatements() <= 0) {
-                // Since there is no limit, create a prepared statement pool with an eviction thread;
-                // evictor settings are the same as the connection pool settings.
-                config.setTimeBetweenEvictionRuns(getDurationBetweenEvictionRuns());
-                config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
-                config.setMinEvictableIdleTime(getMinEvictableIdleDuration());
-            } else {
-                // Since there is a limit, create a prepared statement pool without an eviction thread;
-                // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
-                // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method
-                config.setMaxTotal(getMaxPreparedStatements());
-                config.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
-                config.setNumTestsPerEvictionRun(0);
-                config.setMinEvictableIdleTime(Duration.ZERO);
-            }
-            stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config);
-            pooledConnection.setStatementPool(stmtPool);
-        }
-        return pooledConnection;
-    }
-
-    /**
-     * Implements {@link Referenceable}.
-     */
-    @Override
-    public Reference getReference() throws NamingException {
-        // this class implements its own factory
-        final String factory = getClass().getName();
-
-        final Reference ref = new Reference(getClass().getName(), factory, null);
-
-        ref.add(new StringRefAddr("description", getDescription()));
-        ref.add(new StringRefAddr("driver", getDriver()));
-        ref.add(new StringRefAddr("loginTimeout", String.valueOf(getLoginTimeout())));
-        ref.add(new StringRefAddr(Constants.KEY_PASSWORD, getPassword()));
-        ref.add(new StringRefAddr(Constants.KEY_USER, getUser()));
-        ref.add(new StringRefAddr("url", getUrl()));
-
-        ref.add(new StringRefAddr("poolPreparedStatements", String.valueOf(isPoolPreparedStatements())));
-        ref.add(new StringRefAddr("maxIdle", String.valueOf(getMaxIdle())));
-        ref.add(new StringRefAddr("numTestsPerEvictionRun", String.valueOf(getNumTestsPerEvictionRun())));
-        ref.add(new StringRefAddr("maxPreparedStatements", String.valueOf(getMaxPreparedStatements())));
-        //
-        // Pair of current and deprecated.
-        ref.add(new StringRefAddr("durationBetweenEvictionRuns", String.valueOf(getDurationBetweenEvictionRuns())));
-        ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", String.valueOf(getTimeBetweenEvictionRunsMillis())));
-        //
-        // Pair of current and deprecated.
-        ref.add(new StringRefAddr("minEvictableIdleDuration", String.valueOf(getMinEvictableIdleDuration())));
-        ref.add(new StringRefAddr("minEvictableIdleTimeMillis", String.valueOf(getMinEvictableIdleTimeMillis())));
-
-        return ref;
-    }
-
-    private String getStringContent(final RefAddr ra) {
-        return ra.getContent().toString();
-    }
-
-    /**
-     * Gets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no
-     * idle object evictor thread will be run.
-     *
-     * @return the value of the evictor thread timer
-     * @see #setDurationBetweenEvictionRuns(Duration)
-     * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
-     */
-    @Deprecated
-    public long getTimeBetweenEvictionRunsMillis() {
-        return durationBetweenEvictionRuns.toMillis();
-    }
-
-    /**
-     * Gets the value of url used to locate the database for this datasource.
-     *
-     * @return value of url.
-     */
-    public String getUrl() {
-        return url;
-    }
-
-    /**
-     * Gets the value of default user (login or user name).
-     *
-     * @return value of user.
-     */
-    public String getUser() {
-        return userName;
-    }
-
-    /**
-     * Returns the value of the accessToUnderlyingConnectionAllowed property.
-     *
-     * @return true if access to the underlying is allowed, false otherwise.
-     */
-    public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
-        return this.accessToUnderlyingConnectionAllowed;
-    }
-
-    private boolean isNotEmpty(final RefAddr ra) {
-        return ra != null && ra.getContent() != null;
-    }
-
-    /**
-     * Whether to toggle the pooling of {@code PreparedStatement}s
-     *
-     * @return value of poolPreparedStatements.
-     */
-    public boolean isPoolPreparedStatements() {
-        return poolPreparedStatements;
-    }
-
-    /**
-     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
-     * the underlying connection. (Default: false)
-     *
-     * @param allow Access to the underlying connection is granted when true.
-     */
-    public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
-        this.accessToUnderlyingConnectionAllowed = allow;
-    }
-
-    /**
-     * Sets the connection properties passed to the JDBC driver.
-     * <p>
-     * If {@code props} contains "user" and/or "password" properties, the corresponding instance properties are
-     * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()}
-     * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when
-     * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or
-     * {@link #setPassword(String)} overwrite the values of these properties if {@code connectionProperties} is not
-     * null.
-     * </p>
-     *
-     * @param props Connection properties to use when creating new connections.
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setConnectionProperties(final Properties props) {
-        assertInitializationAllowed();
-        connectionProperties = props;
-        if (connectionProperties != null) {
-            if (connectionProperties.containsKey(Constants.KEY_USER)) {
-                setUser(connectionProperties.getProperty(Constants.KEY_USER));
-            }
-            if (connectionProperties.containsKey(Constants.KEY_PASSWORD)) {
-                setPassword(connectionProperties.getProperty(Constants.KEY_PASSWORD));
-            }
-        }
-    }
-
-    /**
-     * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is
-     * not used internally.
-     *
-     * @param description Value to assign to description.
-     */
-    public void setDescription(final String description) {
-        this.description = description;
-    }
-
-    /**
-     * Sets the driver class name. Setting the driver class name cause the driver to be registered with the
-     * DriverManager.
-     *
-     * @param driver Value to assign to driver.
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     * @throws ClassNotFoundException if the class cannot be located
-     */
-    public void setDriver(final String driver) throws ClassNotFoundException {
-        assertInitializationAllowed();
-        this.driver = driver;
-        // make sure driver is registered
-        Class.forName(driver);
-    }
-
-    /**
-     * Sets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
-     * idle object evictor thread will be run.
-     *
-     * @param durationBetweenEvictionRuns The duration to sleep between runs of the idle object evictor
-     *        thread. When non-positive, no idle object evictor thread will be run.
-     * @see #getDurationBetweenEvictionRuns()
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     * @since 2.9.0
-     */
-    public void setDurationBetweenEvictionRuns(final Duration durationBetweenEvictionRuns) {
-        assertInitializationAllowed();
-        this.durationBetweenEvictionRuns = durationBetweenEvictionRuns;
-    }
-
-    /**
-     * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. NOT
-     * USED.
-     */
-    @Override
-    public void setLoginTimeout(final int seconds) {
-        this.loginTimeout = seconds;
-    }
-
-    /**
-     * Sets the log writer for this data source. NOT USED.
-     */
-    @Override
-    public void setLogWriter(final PrintWriter logWriter) {
-        this.logWriter = logWriter;
-    }
-
-    /**
-     * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
-     * negative for no limit.
-     *
-     * @param maxIdle The maximum number of statements that can remain idle
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setMaxIdle(final int maxIdle) {
-        assertInitializationAllowed();
-        this.maxIdle = maxIdle;
-    }
-
-    /**
-     * Sets the maximum number of prepared statements.
-     *
-     * @param maxPreparedStatements the new maximum number of prepared statements
-     */
-    public void setMaxPreparedStatements(final int maxPreparedStatements) {
-        this.maxPreparedStatements = maxPreparedStatements;
-    }
-
-    /**
-     * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
-     * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
-     *
-     * @param minEvictableIdleDuration minimum time to set in milliseconds.
-     * @see #getMinEvictableIdleDuration()
-     * @see #setDurationBetweenEvictionRuns(Duration)
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called.
-     * @since 2.9.0
-     */
-    public void setMinEvictableIdleDuration(final Duration minEvictableIdleDuration) {
-        assertInitializationAllowed();
-        this.minEvictableIdleDuration = minEvictableIdleDuration;
-    }
-
-    /**
-     * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
-     * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
-     *
-     * @param minEvictableIdleTimeMillis minimum time to set in milliseconds.
-     * @see #getMinEvictableIdleDuration()
-     * @see #setDurationBetweenEvictionRuns(Duration)
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     * @deprecated Use {@link #setMinEvictableIdleDuration(Duration)}.
-     */
-    @Deprecated
-    public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) {
-        assertInitializationAllowed();
-        this.minEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
-    }
-
-    /**
-     * Sets the number of statements to examine during each run of the idle object evictor thread (if any).
-     * <p>
-     * When a negative value is supplied,
-     * {@code ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})} tests will be run.
-     * I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the idle objects will be tested per run.
-     * </p>
-     *
-     * @param numTestsPerEvictionRun number of statements to examine per run
-     * @see #getNumTestsPerEvictionRun()
-     * @see #setDurationBetweenEvictionRuns(Duration)
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
-        assertInitializationAllowed();
-        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
-    }
-
-    /**
-     * Sets the value of password for the default user.
-     *
-     * @param userPassword Value to assign to password.
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setPassword(final char[] userPassword) {
-        assertInitializationAllowed();
-        this.userPassword = Utils.clone(userPassword);
-        update(connectionProperties, Constants.KEY_PASSWORD, Utils.toString(this.userPassword));
-    }
-
-    /**
-     * Sets the value of password for the default user.
-     *
-     * @param userPassword Value to assign to password.
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setPassword(final String userPassword) {
-        assertInitializationAllowed();
-        this.userPassword = Utils.toCharArray(userPassword);
-        update(connectionProperties, Constants.KEY_PASSWORD, userPassword);
-    }
-
-    /**
-     * Whether to toggle the pooling of {@code PreparedStatement}s
-     *
-     * @param poolPreparedStatements true to pool statements.
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setPoolPreparedStatements(final boolean poolPreparedStatements) {
-        assertInitializationAllowed();
-        this.poolPreparedStatements = poolPreparedStatements;
-    }
-
-    /**
-     * Sets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no
-     * idle object evictor thread will be run.
-     *
-     * @param timeBetweenEvictionRunsMillis The number of milliseconds to sleep between runs of the idle object evictor
-     *        thread. When non-positive, no idle object evictor thread will be run.
-     * @see #getDurationBetweenEvictionRuns()
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}.
-     */
-    @Deprecated
-    public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
-        assertInitializationAllowed();
-        this.durationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis);
-    }
-
-    /**
-     * Sets the value of URL string used to locate the database for this datasource.
-     *
-     * @param url Value to assign to url.
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setUrl(final String url) {
-        assertInitializationAllowed();
-        this.url = url;
-    }
-
-    /**
-     * Sets the value of default user (login or user name).
-     *
-     * @param userName Value to assign to user.
-     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
-     */
-    public void setUser(final String userName) {
-        assertInitializationAllowed();
-        this.userName = userName;
-        update(connectionProperties, Constants.KEY_USER, userName);
-    }
-
-    /**
-     * Does not print the userName and userPassword field nor the 'user' or 'password' in the connectionProperties.
-     *
-     * @since 2.6.0
-     */
-    @Override
-    public synchronized String toString() {
-        final StringBuilder builder = new StringBuilder(super.toString());
-        builder.append("[description=");
-        builder.append(description);
-        builder.append(", url=");
-        // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string
-        // is not in a legal URL format?
-        builder.append(url);
-        builder.append(", driver=");
-        builder.append(driver);
-        builder.append(", loginTimeout=");
-        builder.append(loginTimeout);
-        builder.append(", poolPreparedStatements=");
-        builder.append(poolPreparedStatements);
-        builder.append(", maxIdle=");
-        builder.append(maxIdle);
-        builder.append(", timeBetweenEvictionRunsMillis=");
-        builder.append(durationBetweenEvictionRuns);
-        builder.append(", numTestsPerEvictionRun=");
-        builder.append(numTestsPerEvictionRun);
-        builder.append(", minEvictableIdleTimeMillis=");
-        builder.append(minEvictableIdleDuration);
-        builder.append(", maxPreparedStatements=");
-        builder.append(maxPreparedStatements);
-        builder.append(", getConnectionCalled=");
-        builder.append(getConnectionCalled);
-        builder.append(", connectionProperties=");
-        builder.append(Utils.cloneWithoutCredentials(connectionProperties));
-        builder.append(", accessToUnderlyingConnectionAllowed=");
-        builder.append(accessToUnderlyingConnectionAllowed);
-        builder.append("]");
-        return builder.toString();
-    }
-
-    private void update(final Properties properties, final String key, final String value) {
-        if (properties != null && key != null) {
-            if (value == null) {
-                properties.remove(key);
-            } else {
-                properties.setProperty(key, value);
-            }
-        }
-    }
-}
+/*
+ * 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.cpdsadapter;
+
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.time.Duration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.naming.StringRefAddr;
+import javax.naming.spi.ObjectFactory;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.PooledConnection;
+
+import org.apache.commons.dbcp2.Constants;
+import org.apache.commons.dbcp2.DelegatingPreparedStatement;
+import org.apache.commons.dbcp2.PStmtKey;
+import org.apache.commons.dbcp2.Utils;
+import org.apache.commons.pool2.KeyedObjectPool;
+import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
+
+/**
+ * <p>
+ * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but
+ * still include a {@link java.sql.DriverManager} implementation. {@code ConnectionPoolDataSource}s are not used
+ * within general applications. They are used by {@code DataSource} implementations that pool
+ * {@code Connection}s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
+ * will normally provide some method of initializing the {@code ConnectionPoolDataSource} whose attributes are
+ * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical
+ * connections to the database, when the pooling {@code DataSource} needs to create a new physical connection.
+ * </p>
+ * <p>
+ * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any
+ * bean and then attached directly to a pooling {@code DataSource}. {@code Jdbc2PoolDataSource} can use the
+ * {@code ConnectionPoolDataSource} with or without the use of JNDI.
+ * </p>
+ * <p>
+ * The DriverAdapterCPDS also provides {@code PreparedStatement} pooling which is not generally available in jdbc2
+ * {@code ConnectionPoolDataSource} implementation, but is addressed within the jdbc3 specification. The
+ * {@code PreparedStatement} pool in DriverAdapterCPDS has been in the dbcp package for some time, but it has not
+ * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled
+ * with the poolPreparedStatements attribute.
+ * </p>
+ * <p>
+ * The <a href="package-summary.html">package documentation</a> contains an example using catalina and JNDI. The
+ * <a href="../datasources/package-summary.html">datasources package documentation</a> shows how to use
+ * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI.
+ * </p>
+ *
+ * @since 2.0
+ */
+public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory {
+
+    private static final long serialVersionUID = -4820523787212147844L;
+
+    private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, "
+        + "further initialization is not allowed.";
+
+    static {
+        // Attempt to prevent deadlocks - see DBCP - 272
+        DriverManager.getDrivers();
+    }
+
+    /** Description */
+    private String description;
+
+    /** Url name */
+    private String url;
+
+    /** User name */
+    private String userName;
+
+    /** User password */
+    private char[] userPassword;
+
+    /** Driver class name */
+    private String driver;
+
+    /** Login TimeOut in seconds */
+    private int loginTimeout;
+
+    /** Log stream. NOT USED */
+    private transient PrintWriter logWriter;
+
+    // PreparedStatement pool properties
+    private boolean poolPreparedStatements;
+    private int maxIdle = 10;
+    private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
+    private int numTestsPerEvictionRun = -1;
+    private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
+
+    private int maxPreparedStatements = -1;
+
+    /** Whether or not getConnection has been called */
+    private volatile boolean getConnectionCalled;
+
+    /** Connection properties passed to JDBC Driver */
+    private Properties connectionProperties;
+
+    /**
+     * Controls access to the underlying connection
+     */
+    private boolean accessToUnderlyingConnectionAllowed;
+
+    /**
+     * Default no-argument constructor for Serialization
+     */
+    public DriverAdapterCPDS() {
+    }
+
+    /**
+     * Throws an IllegalStateException, if a PooledConnection has already been requested.
+     */
+    private void assertInitializationAllowed() throws IllegalStateException {
+        if (getConnectionCalled) {
+            throw new IllegalStateException(GET_CONNECTION_CALLED);
+        }
+    }
+
+    private boolean getBooleanContentString(final RefAddr ra) {
+        return Boolean.parseBoolean(getStringContent(ra));
+    }
+
+    /**
+     * Gets the connection properties passed to the JDBC driver.
+     *
+     * @return the JDBC connection properties used when creating connections.
+     */
+    public Properties getConnectionProperties() {
+        return connectionProperties;
+    }
+
+    /**
+     * Gets the value of description. This property is here for use by the code which will deploy this data source. It
+     * is not used internally.
+     *
+     * @return value of description, may be null.
+     * @see #setDescription(String)
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Gets the driver class name.
+     *
+     * @return value of driver.
+     */
+    public String getDriver() {
+        return driver;
+    }
+
+    /**
+     * Gets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
+     * idle object evictor thread will be run.
+     *
+     * @return the value of the evictor thread timer
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @since 2.9.0
+     */
+    public Duration getDurationBetweenEvictionRuns() {
+        return durationBetweenEvictionRuns;
+    }
+
+    private int getIntegerStringContent(final RefAddr ra) {
+        return Integer.parseInt(getStringContent(ra));
+    }
+
+    /**
+     * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database. NOT
+     * USED.
+     */
+    @Override
+    public int getLoginTimeout() {
+        return loginTimeout;
+    }
+
+    /**
+     * Gets the log writer for this data source. NOT USED.
+     */
+    @Override
+    public PrintWriter getLogWriter() {
+        return logWriter;
+    }
+
+    /**
+     * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
+     * negative for no limit.
+     *
+     * @return the value of maxIdle
+     */
+    public int getMaxIdle() {
+        return maxIdle;
+    }
+
+    /**
+     * Gets the maximum number of prepared statements.
+     *
+     * @return maxPrepartedStatements value
+     */
+    public int getMaxPreparedStatements() {
+        return maxPreparedStatements;
+    }
+
+    /**
+     * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
+     * idle object evictor (if any).
+     *
+     * @see #setMinEvictableIdleDuration
+     * @see #setDurationBetweenEvictionRuns
+     * @return the minimum amount of time a statement may sit idle in the pool.
+     * @since 2.9.0
+     */
+    public Duration getMinEvictableIdleDuration() {
+        return minEvictableIdleDuration;
+    }
+
+    /**
+     * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
+     * idle object evictor (if any).
+     *
+     * @see #setMinEvictableIdleTimeMillis
+     * @see #setTimeBetweenEvictionRunsMillis
+     * @return the minimum amount of time a statement may sit idle in the pool.
+     * @deprecated USe {@link #getMinEvictableIdleDuration()}.
+     */
+    @Deprecated
+    public int getMinEvictableIdleTimeMillis() {
+        return (int) minEvictableIdleDuration.toMillis();
+    }
+
+    /**
+     * Gets the number of statements to examine during each run of the idle object evictor thread (if any.)
+     *
+     * @see #setNumTestsPerEvictionRun
+     * @see #setTimeBetweenEvictionRunsMillis
+     * @return the number of statements to examine during each run of the idle object evictor thread (if any.)
+     */
+    public int getNumTestsPerEvictionRun() {
+        return numTestsPerEvictionRun;
+    }
+
+    /**
+     * Implements {@link ObjectFactory} to create an instance of this class
+     */
+    @Override
+    public Object getObjectInstance(final Object refObj, final Name name, final Context context,
+        final Hashtable<?, ?> env) throws ClassNotFoundException {
+        // The spec says to return null if we can't create an instance
+        // of the reference
+        DriverAdapterCPDS cpds = null;
+        if (refObj instanceof Reference) {
+            final Reference ref = (Reference) refObj;
+            if (ref.getClassName().equals(getClass().getName())) {
+                RefAddr ra = ref.get("description");
+                if (isNotEmpty(ra)) {
+                    setDescription(getStringContent(ra));
+                }
+
+                ra = ref.get("driver");
+                if (isNotEmpty(ra)) {
+                    setDriver(getStringContent(ra));
+                }
+                ra = ref.get("url");
+                if (isNotEmpty(ra)) {
+                    setUrl(getStringContent(ra));
+                }
+                ra = ref.get(Constants.KEY_USER);
+                if (isNotEmpty(ra)) {
+                    setUser(getStringContent(ra));
+                }
+                ra = ref.get(Constants.KEY_PASSWORD);
+                if (isNotEmpty(ra)) {
+                    setPassword(getStringContent(ra));
+                }
+
+                ra = ref.get("poolPreparedStatements");
+                if (isNotEmpty(ra)) {
+                    setPoolPreparedStatements(getBooleanContentString(ra));
+                }
+                ra = ref.get("maxIdle");
+                if (isNotEmpty(ra)) {
+                    setMaxIdle(getIntegerStringContent(ra));
+                }
+
+                ra = ref.get("timeBetweenEvictionRunsMillis");
+                if (isNotEmpty(ra)) {
+                    setTimeBetweenEvictionRunsMillis(getIntegerStringContent(ra));
+                }
+
+                ra = ref.get("numTestsPerEvictionRun");
+                if (isNotEmpty(ra)) {
+                    setNumTestsPerEvictionRun(getIntegerStringContent(ra));
+                }
+
+                ra = ref.get("minEvictableIdleTimeMillis");
+                if (isNotEmpty(ra)) {
+                    setMinEvictableIdleTimeMillis(getIntegerStringContent(ra));
+                }
+
+                ra = ref.get("maxPreparedStatements");
+                if (isNotEmpty(ra)) {
+                    setMaxPreparedStatements(getIntegerStringContent(ra));
+                }
+
+                ra = ref.get("accessToUnderlyingConnectionAllowed");
+                if (isNotEmpty(ra)) {
+                    setAccessToUnderlyingConnectionAllowed(getBooleanContentString(ra));
+                }
+
+                cpds = this;
+            }
+        }
+        return cpds;
+    }
+
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    /**
+     * Gets the value of password for the default user.
+     *
+     * @return value of password.
+     */
+    public String getPassword() {
+        return Utils.toString(userPassword);
+    }
+
+    /**
+     * Gets the value of password for the default user.
+     *
+     * @return value of password.
+     * @since 2.4.0
+     */
+    public char[] getPasswordCharArray() {
+        return userPassword == null ? null : userPassword.clone();
+    }
+
+    /**
+     * Attempts to establish a database connection using the default user and password.
+     */
+    @Override
+    public PooledConnection getPooledConnection() throws SQLException {
+        return getPooledConnection(getUser(), getPassword());
+    }
+
+    /**
+     * Attempts to establish a database connection.
+     *
+     * @param pooledUserName name to be used for the connection
+     * @param pooledUserPassword password to be used fur the connection
+     */
+    @Override
+    public PooledConnection getPooledConnection(final String pooledUserName, final String pooledUserPassword)
+        throws SQLException {
+        getConnectionCalled = true;
+        PooledConnectionImpl pooledConnection = null;
+        // Workaround for buggy WebLogic 5.1 class loader - ignore the exception upon first invocation.
+        try {
+            if (connectionProperties != null) {
+                update(connectionProperties, Constants.KEY_USER, pooledUserName);
+                update(connectionProperties, Constants.KEY_PASSWORD, pooledUserPassword);
+                pooledConnection = new PooledConnectionImpl(
+                    DriverManager.getConnection(getUrl(), connectionProperties));
+            } else {
+                pooledConnection = new PooledConnectionImpl(
+                    DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword));
+            }
+            pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
+        } catch (final ClassCircularityError e) {
+            if (connectionProperties != null) {
+                pooledConnection = new PooledConnectionImpl(
+                    DriverManager.getConnection(getUrl(), connectionProperties));
+            } else {
+                pooledConnection = new PooledConnectionImpl(
+                    DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword));
+            }
+            pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
+        }
+        KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = null;
+        if (isPoolPreparedStatements()) {
+            final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
+            config.setMaxTotalPerKey(Integer.MAX_VALUE);
+            config.setBlockWhenExhausted(false);
+            config.setMaxWait(Duration.ZERO);
+            config.setMaxIdlePerKey(getMaxIdle());
+            if (getMaxPreparedStatements() <= 0) {
+                // Since there is no limit, create a prepared statement pool with an eviction thread;
+                // evictor settings are the same as the connection pool settings.
+                config.setTimeBetweenEvictionRuns(getDurationBetweenEvictionRuns());
+                config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
+                config.setMinEvictableIdleTime(getMinEvictableIdleDuration());
+            } else {
+                // Since there is a limit, create a prepared statement pool without an eviction thread;
+                // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
+                // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method
+                config.setMaxTotal(getMaxPreparedStatements());
+                config.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
+                config.setNumTestsPerEvictionRun(0);
+                config.setMinEvictableIdleTime(Duration.ZERO);
+            }
+            stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config);
+            pooledConnection.setStatementPool(stmtPool);
+        }
+        return pooledConnection;
+    }
+
+    /**
+     * Implements {@link Referenceable}.
+     */
+    @Override
+    public Reference getReference() throws NamingException {
+        // this class implements its own factory
+        final String factory = getClass().getName();
+
+        final Reference ref = new Reference(getClass().getName(), factory, null);
+
+        ref.add(new StringRefAddr("description", getDescription()));
+        ref.add(new StringRefAddr("driver", getDriver()));
+        ref.add(new StringRefAddr("loginTimeout", String.valueOf(getLoginTimeout())));
+        ref.add(new StringRefAddr(Constants.KEY_PASSWORD, getPassword()));
+        ref.add(new StringRefAddr(Constants.KEY_USER, getUser()));
+        ref.add(new StringRefAddr("url", getUrl()));
+
+        ref.add(new StringRefAddr("poolPreparedStatements", String.valueOf(isPoolPreparedStatements())));
+        ref.add(new StringRefAddr("maxIdle", String.valueOf(getMaxIdle())));
+        ref.add(new StringRefAddr("numTestsPerEvictionRun", String.valueOf(getNumTestsPerEvictionRun())));
+        ref.add(new StringRefAddr("maxPreparedStatements", String.valueOf(getMaxPreparedStatements())));
+        //
+        // Pair of current and deprecated.
+        ref.add(new StringRefAddr("durationBetweenEvictionRuns", String.valueOf(getDurationBetweenEvictionRuns())));
+        ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", String.valueOf(getTimeBetweenEvictionRunsMillis())));
+        //
+        // Pair of current and deprecated.
+        ref.add(new StringRefAddr("minEvictableIdleDuration", String.valueOf(getMinEvictableIdleDuration())));
+        ref.add(new StringRefAddr("minEvictableIdleTimeMillis", String.valueOf(getMinEvictableIdleTimeMillis())));
+
+        return ref;
+    }
+
+    private String getStringContent(final RefAddr ra) {
+        return ra.getContent().toString();
+    }
+
+    /**
+     * Gets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no
+     * idle object evictor thread will be run.
+     *
+     * @return the value of the evictor thread timer
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
+     */
+    @Deprecated
+    public long getTimeBetweenEvictionRunsMillis() {
+        return durationBetweenEvictionRuns.toMillis();
+    }
+
+    /**
+     * Gets the value of url used to locate the database for this datasource.
+     *
+     * @return value of url.
+     */
+    public String getUrl() {
+        return url;
+    }
+
+    /**
+     * Gets the value of default user (login or user name).
+     *
+     * @return value of user.
+     */
+    public String getUser() {
+        return userName;
+    }
+
+    /**
+     * Returns the value of the accessToUnderlyingConnectionAllowed property.
+     *
+     * @return true if access to the underlying is allowed, false otherwise.
+     */
+    public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
+        return this.accessToUnderlyingConnectionAllowed;
+    }
+
+    private boolean isNotEmpty(final RefAddr ra) {
+        return ra != null && ra.getContent() != null;
+    }
+
+    /**
+     * Whether to toggle the pooling of {@code PreparedStatement}s
+     *
+     * @return value of poolPreparedStatements.
+     */
+    public boolean isPoolPreparedStatements() {
+        return poolPreparedStatements;
+    }
+
+    /**
+     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
+     * the underlying connection. (Default: false)
+     *
+     * @param allow Access to the underlying connection is granted when true.
+     */
+    public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
+        this.accessToUnderlyingConnectionAllowed = allow;
+    }
+
+    /**
+     * Sets the connection properties passed to the JDBC driver.
+     * <p>
+     * If {@code props} contains "user" and/or "password" properties, the corresponding instance properties are
+     * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()}
+     * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when
+     * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or
+     * {@link #setPassword(String)} overwrite the values of these properties if {@code connectionProperties} is not
+     * null.
+     * </p>
+     *
+     * @param props Connection properties to use when creating new connections.
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setConnectionProperties(final Properties props) {
+        assertInitializationAllowed();
+        connectionProperties = props;
+        if (connectionProperties != null) {
+            if (connectionProperties.containsKey(Constants.KEY_USER)) {
+                setUser(connectionProperties.getProperty(Constants.KEY_USER));
+            }
+            if (connectionProperties.containsKey(Constants.KEY_PASSWORD)) {
+                setPassword(connectionProperties.getProperty(Constants.KEY_PASSWORD));
+            }
+        }
+    }
+
+    /**
+     * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is
+     * not used internally.
+     *
+     * @param description Value to assign to description.
+     */
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    /**
+     * Sets the driver class name. Setting the driver class name cause the driver to be registered with the
+     * DriverManager.
+     *
+     * @param driver Value to assign to driver.
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     * @throws ClassNotFoundException if the class cannot be located
+     */
+    public void setDriver(final String driver) throws ClassNotFoundException {
+        assertInitializationAllowed();
+        this.driver = driver;
+        // make sure driver is registered
+        Class.forName(driver);
+    }
+
+    /**
+     * Sets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
+     * idle object evictor thread will be run.
+     *
+     * @param durationBetweenEvictionRuns The duration to sleep between runs of the idle object evictor
+     *        thread. When non-positive, no idle object evictor thread will be run.
+     * @see #getDurationBetweenEvictionRuns()
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     * @since 2.9.0
+     */
+    public void setDurationBetweenEvictionRuns(final Duration durationBetweenEvictionRuns) {
+        assertInitializationAllowed();
+        this.durationBetweenEvictionRuns = durationBetweenEvictionRuns;
+    }
+
+    /**
+     * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. NOT
+     * USED.
+     */
+    @Override
+    public void setLoginTimeout(final int seconds) {
+        this.loginTimeout = seconds;
+    }
+
+    /**
+     * Sets the log writer for this data source. NOT USED.
+     */
+    @Override
+    public void setLogWriter(final PrintWriter logWriter) {
+        this.logWriter = logWriter;
+    }
+
+    /**
+     * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
+     * negative for no limit.
+     *
+     * @param maxIdle The maximum number of statements that can remain idle
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setMaxIdle(final int maxIdle) {
+        assertInitializationAllowed();
+        this.maxIdle = maxIdle;
+    }
+
+    /**
+     * Sets the maximum number of prepared statements.
+     *
+     * @param maxPreparedStatements the new maximum number of prepared statements
+     */
+    public void setMaxPreparedStatements(final int maxPreparedStatements) {
+        this.maxPreparedStatements = maxPreparedStatements;
+    }
+
+    /**
+     * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
+     * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
+     *
+     * @param minEvictableIdleDuration minimum time to set in milliseconds.
+     * @see #getMinEvictableIdleDuration()
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called.
+     * @since 2.9.0
+     */
+    public void setMinEvictableIdleDuration(final Duration minEvictableIdleDuration) {
+        assertInitializationAllowed();
+        this.minEvictableIdleDuration = minEvictableIdleDuration;
+    }
+
+    /**
+     * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
+     * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
+     *
+     * @param minEvictableIdleTimeMillis minimum time to set in milliseconds.
+     * @see #getMinEvictableIdleDuration()
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     * @deprecated Use {@link #setMinEvictableIdleDuration(Duration)}.
+     */
+    @Deprecated
+    public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) {
+        assertInitializationAllowed();
+        this.minEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
+    }
+
+    /**
+     * Sets the number of statements to examine during each run of the idle object evictor thread (if any).
+     * <p>
+     * When a negative value is supplied,
+     * {@code ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})} tests will be run.
+     * I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the idle objects will be tested per run.
+     * </p>
+     *
+     * @param numTestsPerEvictionRun number of statements to examine per run
+     * @see #getNumTestsPerEvictionRun()
+     * @see #setDurationBetweenEvictionRuns(Duration)
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
+        assertInitializationAllowed();
+        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
+    }
+
+    /**
+     * Sets the value of password for the default user.
+     *
+     * @param userPassword Value to assign to password.
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setPassword(final char[] userPassword) {
+        assertInitializationAllowed();
+        this.userPassword = Utils.clone(userPassword);
+        update(connectionProperties, Constants.KEY_PASSWORD, Utils.toString(this.userPassword));
+    }
+
+    /**
+     * Sets the value of password for the default user.
+     *
+     * @param userPassword Value to assign to password.
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setPassword(final String userPassword) {
+        assertInitializationAllowed();
+        this.userPassword = Utils.toCharArray(userPassword);
+        update(connectionProperties, Constants.KEY_PASSWORD, userPassword);
+    }
+
+    /**
+     * Whether to toggle the pooling of {@code PreparedStatement}s
+     *
+     * @param poolPreparedStatements true to pool statements.
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setPoolPreparedStatements(final boolean poolPreparedStatements) {
+        assertInitializationAllowed();
+        this.poolPreparedStatements = poolPreparedStatements;
+    }
+
+    /**
+     * Sets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no
+     * idle object evictor thread will be run.
+     *
+     * @param timeBetweenEvictionRunsMillis The number of milliseconds to sleep between runs of the idle object evictor
+     *        thread. When non-positive, no idle object evictor thread will be run.
+     * @see #getDurationBetweenEvictionRuns()
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}.
+     */
+    @Deprecated
+    public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
+        assertInitializationAllowed();
+        this.durationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis);
+    }
+
+    /**
+     * Sets the value of URL string used to locate the database for this datasource.
+     *
+     * @param url Value to assign to url.
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setUrl(final String url) {
+        assertInitializationAllowed();
+        this.url = url;
+    }
+
+    /**
+     * Sets the value of default user (login or user name).
+     *
+     * @param userName Value to assign to user.
+     * @throws IllegalStateException if {@link #getPooledConnection()} has been called
+     */
+    public void setUser(final String userName) {
+        assertInitializationAllowed();
+        this.userName = userName;
+        update(connectionProperties, Constants.KEY_USER, userName);
+    }
+
+    /**
+     * Does not print the userName and userPassword field nor the 'user' or 'password' in the connectionProperties.
+     *
+     * @since 2.6.0
+     */
+    @Override
+    public synchronized String toString() {
+        final StringBuilder builder = new StringBuilder(super.toString());
+        builder.append("[description=");
+        builder.append(description);
+        builder.append(", url=");
+        // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string
+        // is not in a legal URL format?
+        builder.append(url);
+        builder.append(", driver=");
+        builder.append(driver);
+        builder.append(", loginTimeout=");
+        builder.append(loginTimeout);
+        builder.append(", poolPreparedStatements=");
+        builder.append(poolPreparedStatements);
+        builder.append(", maxIdle=");
+        builder.append(maxIdle);
+        builder.append(", timeBetweenEvictionRunsMillis=");
+        builder.append(durationBetweenEvictionRuns);
+        builder.append(", numTestsPerEvictionRun=");
+        builder.append(numTestsPerEvictionRun);
+        builder.append(", minEvictableIdleTimeMillis=");
+        builder.append(minEvictableIdleDuration);
+        builder.append(", maxPreparedStatements=");
+        builder.append(maxPreparedStatements);
+        builder.append(", getConnectionCalled=");
+        builder.append(getConnectionCalled);
+        builder.append(", connectionProperties=");
+        builder.append(Utils.cloneWithoutCredentials(connectionProperties));
+        builder.append(", accessToUnderlyingConnectionAllowed=");
+        builder.append(accessToUnderlyingConnectionAllowed);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    private void update(final Properties properties, final String key, final String value) {
+        if (properties != null && key != null) {
+            if (value == null) {
+                properties.remove(key);
+            } else {
+                properties.setProperty(key, value);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java
index 75100909..d50329f1 100644
--- a/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java
+++ b/src/main/java/org/apache/commons/dbcp2/cpdsadapter/PooledConnectionImpl.java
@@ -1,758 +1,758 @@
-/*
- * 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.cpdsadapter;
-
-import java.sql.CallableStatement;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.sql.ConnectionEvent;
-import javax.sql.ConnectionEventListener;
-import javax.sql.PooledConnection;
-import javax.sql.StatementEventListener;
-
-import org.apache.commons.dbcp2.DelegatingConnection;
-import org.apache.commons.dbcp2.DelegatingPreparedStatement;
-import org.apache.commons.dbcp2.Jdbc41Bridge;
-import org.apache.commons.dbcp2.PStmtKey;
-import org.apache.commons.dbcp2.PoolableCallableStatement;
-import org.apache.commons.dbcp2.PoolablePreparedStatement;
-import org.apache.commons.dbcp2.PoolingConnection.StatementType;
-import org.apache.commons.pool2.KeyedObjectPool;
-import org.apache.commons.pool2.KeyedPooledObjectFactory;
-import org.apache.commons.pool2.PooledObject;
-import org.apache.commons.pool2.impl.DefaultPooledObject;
-
-/**
- * Implementation of PooledConnection that is returned by PooledConnectionDataSource.
- *
- * @since 2.0
- */
-class PooledConnectionImpl
-        implements PooledConnection, KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
-
-    private static final String CLOSED = "Attempted to use PooledConnection after closed() was called.";
-
-    /**
-     * The JDBC database connection that represents the physical db connection.
-     */
-    private Connection connection;
-
-    /**
-     * A DelegatingConnection used to create a PoolablePreparedStatementStub.
-     */
-    private final DelegatingConnection<?> delegatingConnection;
-
-    /**
-     * The JDBC database logical connection.
-     */
-    private Connection logicalConnection;
-
-    /**
-     * ConnectionEventListeners.
-     */
-    private final List<ConnectionEventListener> eventListeners;
-
-    /**
-     * StatementEventListeners.
-     */
-    private final List<StatementEventListener> statementEventListeners = Collections.synchronizedList(new ArrayList<>());
-
-    /**
-     * Flag set to true, once {@link #close()} is called.
-     */
-    private boolean closed;
-
-    /** My pool of {@link PreparedStatement}s. */
-    private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pStmtPool;
-
-    /**
-     * Controls access to the underlying connection.
-     */
-    private boolean accessToUnderlyingConnectionAllowed;
-
-    /**
-     * Wraps the real connection.
-     *
-     * @param connection
-     *            the connection to be wrapped.
-     */
-    PooledConnectionImpl(final Connection connection) {
-        this.connection = connection;
-        if (connection instanceof DelegatingConnection) {
-            this.delegatingConnection = (DelegatingConnection<?>) connection;
-        } else {
-            this.delegatingConnection = new DelegatingConnection<>(connection);
-        }
-        eventListeners = Collections.synchronizedList(new ArrayList<>());
-        closed = false;
-    }
-
-    /**
-     * My {@link KeyedPooledObjectFactory} method for activating {@link PreparedStatement}s.
-     *
-     * @param pooledObject Activates the underlying object.
-     */
-    @Override
-    public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
-        pooledObject.getObject().activate();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void addConnectionEventListener(final ConnectionEventListener listener) {
-        if (!eventListeners.contains(listener)) {
-            eventListeners.add(listener);
-        }
-    }
-
-    @Override
-    public void addStatementEventListener(final StatementEventListener listener) {
-        if (!statementEventListeners.contains(listener)) {
-            statementEventListeners.add(listener);
-        }
-    }
-    /* JDBC_4_ANT_KEY_END */
-
-    /**
-     * Throws an SQLException, if isClosed is true
-     */
-    private void assertOpen() throws SQLException {
-        if (closed) {
-            throw new SQLException(CLOSED);
-        }
-    }
-
-    /**
-     * Closes the physical connection and marks this {@code PooledConnection} so that it may not be used to
-     * generate any more logical {@code Connection}s.
-     *
-     * @throws SQLException
-     *             Thrown when an error occurs or the connection is already closed.
-     */
-    @Override
-    public void close() throws SQLException {
-        assertOpen();
-        closed = true;
-        try {
-            if (pStmtPool != null) {
-                try {
-                    pStmtPool.close();
-                } finally {
-                    pStmtPool = null;
-                }
-            }
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Cannot close connection (return to pool failed)", e);
-        } finally {
-            try {
-                connection.close();
-            } finally {
-                connection = null;
-            }
-        }
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @return a {@link PStmtKey} for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull());
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param autoGeneratedKeys
-     *            A flag indicating whether auto-generated keys should be returned; one of
-     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
-     * @return a key to uniquely identify a prepared statement.
-     */
-    protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param resultSetType
-     *            A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
-     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
-     *            {@code ResultSet.CONCUR_UPDATABLE}.
-     * @return a key to uniquely identify a prepared statement.
-     */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param resultSetType
-     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
-     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
-     *            {@code ResultSet.CONCUR_UPDATABLE}
-     * @param resultSetHoldability
-     *            One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
-     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
-     * @return a key to uniquely identify a prepared statement.
-     */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency, resultSetHoldability);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param resultSetType
-     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
-     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}
-     * @param resultSetConcurrency
-     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
-     *            {@code ResultSet.CONCUR_UPDATABLE}.
-     * @param resultSetHoldability
-     *            One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
-     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
-     * @return a key to uniquely identify a prepared statement.
-     * @since 2.4.0
-     */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability, final StatementType statementType) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency, resultSetHoldability, statementType);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param resultSetType
-     *            A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
-     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
-     * @param resultSetConcurrency
-     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
-     *            {@code ResultSet.CONCUR_UPDATABLE}.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
-     * @return a key to uniquely identify a prepared statement.
-     * @since 2.4.0
-     */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final StatementType statementType) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
-                resultSetConcurrency, statementType);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
-     * @return a key to uniquely identify a prepared statement.
-     */
-    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param statementType
-     *            The SQL statement type, prepared or callable.
-     * @return a key to uniquely identify a prepared statement.
-     */
-    protected PStmtKey createKey(final String sql, final StatementType statementType) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType);
-    }
-
-    /**
-     * Creates a {@link PStmtKey} for the given arguments.
-     *
-     * @param sql
-     *            The SQL statement.
-     * @param columnNames
-     *            An array of column names indicating the columns that should be returned from the inserted row or rows.
-     * @return a key to uniquely identify a prepared statement.
-     */
-    protected PStmtKey createKey(final String sql, final String[] columnNames) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames);
-    }
-
-    /**
-     * My {@link KeyedPooledObjectFactory} method for destroying {@link PreparedStatement}s.
-     *
-     * @param key
-     *            ignored
-     * @param pooledObject
-     *            the wrapped {@link PreparedStatement} to be destroyed.
-     */
-    @Override
-    public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
-        pooledObject.getObject().getInnermostDelegate().close();
-    }
-
-    /**
-     * Closes the physical connection and checks that the logical connection was closed as well.
-     */
-    @Override
-    protected void finalize() throws Throwable {
-        // Closing the Connection ensures that if anyone tries to use it,
-        // an error will occur.
-        try {
-            connection.close();
-        } catch (final Exception ignored) {
-            // ignore
-        }
-
-        // make sure the last connection is marked as closed
-        if (logicalConnection != null && !logicalConnection.isClosed()) {
-            throw new SQLException("PooledConnection was gc'ed, without its last Connection being closed.");
-        }
-    }
-
-    private String getCatalogOrNull() {
-        try {
-            return connection == null ? null : connection.getCatalog();
-        } catch (final SQLException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Returns a JDBC connection.
-     *
-     * @return The database connection.
-     * @throws SQLException
-     *             if the connection is not open or the previous logical connection is still open
-     */
-    @Override
-    public Connection getConnection() throws SQLException {
-        assertOpen();
-        // make sure the last connection is marked as closed
-        if (logicalConnection != null && !logicalConnection.isClosed()) {
-            // should notify pool of error so the pooled connection can
-            // be removed !FIXME!
-            throw new SQLException("PooledConnection was reused, without its previous Connection being closed.");
-        }
-
-        // the spec requires that this return a new Connection instance.
-        logicalConnection = new ConnectionImpl(this, connection, isAccessToUnderlyingConnectionAllowed());
-        return logicalConnection;
-    }
-
-    private String getSchemaOrNull() {
-        try {
-            return connection == null ? null : Jdbc41Bridge.getSchema(connection);
-        } catch (final SQLException e) {
-            return null;
-        }
-    }
-
-    /**
-     * Returns the value of the accessToUnderlyingConnectionAllowed property.
-     *
-     * @return true if access to the underlying is allowed, false otherwise.
-     */
-    public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
-        return this.accessToUnderlyingConnectionAllowed;
-    }
-
-    /**
-     * My {@link KeyedPooledObjectFactory} method for creating {@link PreparedStatement}s.
-     *
-     * @param key
-     *            The key for the {@link PreparedStatement} to be created.
-     */
-    @SuppressWarnings("resource")
-    @Override
-    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws Exception {
-        if (null == key) {
-            throw new IllegalArgumentException("Prepared statement key is null or invalid.");
-        }
-        if (key.getStmtType() == StatementType.PREPARED_STATEMENT) {
-            final PreparedStatement statement = (PreparedStatement) key.createStatement(connection);
-            @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
-            final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool,
-                    delegatingConnection);
-            return new DefaultPooledObject<>(pps);
-        }
-        final CallableStatement statement = (CallableStatement) key.createStatement(connection);
-        @SuppressWarnings("unchecked")
-        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool,
-                (DelegatingConnection<Connection>) delegatingConnection);
-        return new DefaultPooledObject<>(pcs);
-    }
-
-    /**
-     * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original.
-     * @param sql
-     *            The SQL statement.
-     * @return the normalized SQL statement.
-     */
-    protected String normalizeSQL(final String sql) {
-        return sql.trim();
-    }
-
-    /**
-     * Sends a connectionClosed event.
-     */
-    void notifyListeners() {
-        final ConnectionEvent event = new ConnectionEvent(this);
-        final Object[] listeners = eventListeners.toArray();
-        for (final Object listener : listeners) {
-            ((ConnectionEventListener) listener).connectionClosed(event);
-        }
-    }
-
-    /**
-     * My {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s. Currently invokes
-     * {@link PreparedStatement#clearParameters}.
-     *
-     * @param key
-     *            ignored
-     * @param pooledObject
-     *            a wrapped {@link PreparedStatement}
-     */
-    @Override
-    public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
-        @SuppressWarnings("resource")
-        final DelegatingPreparedStatement dps = pooledObject.getObject();
-        dps.clearParameters();
-        dps.passivate();
-    }
-
-    /**
-     * Creates or obtains a {@link CallableStatement} from my pool.
-     *
-     * @param sql
-     *            an SQL statement that may contain one or more '?' parameter placeholders. Typically this statement is
-     *            specified using JDBC call escape syntax.
-     * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement.
-     * @throws SQLException
-     *                Thrown if a database access error occurs or this method is called on a closed connection.
-     * @since 2.4.0
-     */
-    CallableStatement prepareCall(final String sql) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareCall(sql);
-        }
-        try {
-            return (CallableStatement) pStmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareCall from pool failed", e);
-        }
-    }
-
-    /**
-     * Creates or obtains a {@link CallableStatement} from my pool.
-     *
-     * @param sql
-     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
-     *            more '?' parameters.
-     * @param resultSetType
-     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
-     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
-     * @param resultSetConcurrency
-     *            a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
-     *            {@code ResultSet.CONCUR_UPDATABLE}.
-     * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce
-     *         {@code ResultSet} objects with the given type and concurrency.
-     * @throws SQLException
-     *             Thrown if a database access error occurs, this method is called on a closed connection or the given
-     *             parameters are not {@code ResultSet} constants indicating type and concurrency.
-     * @since 2.4.0
-     */
-    CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
-            throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
-        }
-        try {
-            return (CallableStatement) pStmtPool.borrowObject(
-                    createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareCall from pool failed", e);
-        }
-    }
-
-    /**
-     * Creates or obtains a {@link CallableStatement} from my pool.
-     *
-     * @param sql
-     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
-     *            more '?' parameters.
-     * @param resultSetType
-     *            one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY},
-     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
-     * @param resultSetConcurrency
-     *            one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or
-     *            {@code ResultSet.CONCUR_UPDATABLE}.
-     * @param resultSetHoldability
-     *            one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
-     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
-     * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will
-     *         generate {@code ResultSet} objects with the given type, concurrency, and holdability.
-     * @throws SQLException
-     *             Thrown if a database access error occurs, this method is called on a closed connection or the given
-     *             parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability.
-     * @since 2.4.0
-     */
-    CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
-        }
-        try {
-            return (CallableStatement) pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency,
-                    resultSetHoldability, StatementType.CALLABLE_STATEMENT));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareCall from pool failed", e);
-        }
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from my pool.
-     *
-     * @param sql the SQL statement.
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
-     *         the borrow failed.
-     */
-    PreparedStatement prepareStatement(final String sql) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareStatement(sql);
-        }
-        try {
-            return pStmtPool.borrowObject(createKey(sql));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareStatement from pool failed", e);
-        }
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from my pool.
-     *
-     * @param sql
-     *            an SQL statement that may contain one or more '?' IN parameter placeholders.
-     * @param autoGeneratedKeys
-     *            a flag indicating whether auto-generated keys should be returned; one of
-     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
-     *         the borrow failed.
-     * @see Connection#prepareStatement(String, int)
-     */
-    PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareStatement(sql, autoGeneratedKeys);
-        }
-        try {
-            return pStmtPool.borrowObject(createKey(sql, autoGeneratedKeys));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareStatement from pool failed", e);
-        }
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from my pool.
-     *
-     * @param sql
-     *            a {@code String} object that is the SQL statement to be sent to the database; may contain one or
-     *            more '?' IN parameters.
-     * @param resultSetType
-     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
-     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
-     * @param resultSetConcurrency
-     *            a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
-     *            {@code ResultSet.CONCUR_UPDATABLE}.
-     *
-     * @return a {@link PoolablePreparedStatement}.
-     * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
-     *         the borrow failed.
-     * @see Connection#prepareStatement(String, int, int)
-     */
-    PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
-            throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
-        }
-        try {
-            return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareStatement from pool failed", e);
-        }
-    }
-
-    PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
-        }
-        try {
-            return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareStatement from pool failed", e);
-        }
-    }
-
-    PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareStatement(sql, columnIndexes);
-        }
-        try {
-            return pStmtPool.borrowObject(createKey(sql, columnIndexes));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareStatement from pool failed", e);
-        }
-    }
-
-    PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
-        if (pStmtPool == null) {
-            return connection.prepareStatement(sql, columnNames);
-        }
-        try {
-            return pStmtPool.borrowObject(createKey(sql, columnNames));
-        } catch (final RuntimeException e) {
-            throw e;
-        } catch (final Exception e) {
-            throw new SQLException("Borrow prepareStatement from pool failed", e);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void removeConnectionEventListener(final ConnectionEventListener listener) {
-        eventListeners.remove(listener);
-    }
-
-    @Override
-    public void removeStatementEventListener(final StatementEventListener listener) {
-        statementEventListeners.remove(listener);
-    }
-    /* JDBC_4_ANT_KEY_END */
-
-    /**
-     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
-     * the underlying connection. (Default: false.)
-     *
-     * @param allow
-     *            Access to the underlying connection is granted when true.
-     */
-    public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
-        this.accessToUnderlyingConnectionAllowed = allow;
-    }
-
-    public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> statementPool) {
-        pStmtPool = statementPool;
-    }
-
-    /**
-     * @since 2.6.0
-     */
-    @Override
-    public synchronized String toString() {
-        final StringBuilder builder = new StringBuilder(super.toString());
-        builder.append("[connection=");
-        builder.append(connection);
-        builder.append(", delegatingConnection=");
-        builder.append(delegatingConnection);
-        builder.append(", logicalConnection=");
-        builder.append(logicalConnection);
-        builder.append(", eventListeners=");
-        builder.append(eventListeners);
-        builder.append(", statementEventListeners=");
-        builder.append(statementEventListeners);
-        builder.append(", closed=");
-        builder.append(closed);
-        builder.append(", pStmtPool=");
-        builder.append(pStmtPool);
-        builder.append(", accessToUnderlyingConnectionAllowed=");
-        builder.append(accessToUnderlyingConnectionAllowed);
-        builder.append("]");
-        return builder.toString();
-    }
-
-    /**
-     * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s.
-     *
-     * @param key
-     *            Ignored.
-     * @param pooledObject
-     *            Ignored.
-     * @return {@code true}
-     */
-    @Override
-    public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) {
-        return true;
-    }
-}
+/*
+ * 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.cpdsadapter;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.sql.ConnectionEvent;
+import javax.sql.ConnectionEventListener;
+import javax.sql.PooledConnection;
+import javax.sql.StatementEventListener;
+
+import org.apache.commons.dbcp2.DelegatingConnection;
+import org.apache.commons.dbcp2.DelegatingPreparedStatement;
+import org.apache.commons.dbcp2.Jdbc41Bridge;
+import org.apache.commons.dbcp2.PStmtKey;
+import org.apache.commons.dbcp2.PoolableCallableStatement;
+import org.apache.commons.dbcp2.PoolablePreparedStatement;
+import org.apache.commons.dbcp2.PoolingConnection.StatementType;
+import org.apache.commons.pool2.KeyedObjectPool;
+import org.apache.commons.pool2.KeyedPooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+
+/**
+ * Implementation of PooledConnection that is returned by PooledConnectionDataSource.
+ *
+ * @since 2.0
+ */
+class PooledConnectionImpl
+        implements PooledConnection, KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
+
+    private static final String CLOSED = "Attempted to use PooledConnection after closed() was called.";
+
+    /**
+     * The JDBC database connection that represents the physical db connection.
+     */
+    private Connection connection;
+
+    /**
+     * A DelegatingConnection used to create a PoolablePreparedStatementStub.
+     */
+    private final DelegatingConnection<?> delegatingConnection;
+
+    /**
+     * The JDBC database logical connection.
+     */
+    private Connection logicalConnection;
+
+    /**
+     * ConnectionEventListeners.
+     */
+    private final List<ConnectionEventListener> eventListeners;
+
+    /**
+     * StatementEventListeners.
+     */
+    private final List<StatementEventListener> statementEventListeners = Collections.synchronizedList(new ArrayList<>());
+
+    /**
+     * Flag set to true, once {@link #close()} is called.
+     */
+    private boolean closed;
+
+    /** My pool of {@link PreparedStatement}s. */
+    private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pStmtPool;
+
+    /**
+     * Controls access to the underlying connection.
+     */
+    private boolean accessToUnderlyingConnectionAllowed;
+
+    /**
+     * Wraps the real connection.
+     *
+     * @param connection
+     *            the connection to be wrapped.
+     */
+    PooledConnectionImpl(final Connection connection) {
+        this.connection = connection;
+        if (connection instanceof DelegatingConnection) {
+            this.delegatingConnection = (DelegatingConnection<?>) connection;
+        } else {
+            this.delegatingConnection = new DelegatingConnection<>(connection);
+        }
+        eventListeners = Collections.synchronizedList(new ArrayList<>());
+        closed = false;
+    }
+
+    /**
+     * My {@link KeyedPooledObjectFactory} method for activating {@link PreparedStatement}s.
+     *
+     * @param pooledObject Activates the underlying object.
+     */
+    @Override
+    public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
+            throws SQLException {
+        pooledObject.getObject().activate();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addConnectionEventListener(final ConnectionEventListener listener) {
+        if (!eventListeners.contains(listener)) {
+            eventListeners.add(listener);
+        }
+    }
+
+    @Override
+    public void addStatementEventListener(final StatementEventListener listener) {
+        if (!statementEventListeners.contains(listener)) {
+            statementEventListeners.add(listener);
+        }
+    }
+    /* JDBC_4_ANT_KEY_END */
+
+    /**
+     * Throws an SQLException, if isClosed is true
+     */
+    private void assertOpen() throws SQLException {
+        if (closed) {
+            throw new SQLException(CLOSED);
+        }
+    }
+
+    /**
+     * Closes the physical connection and marks this {@code PooledConnection} so that it may not be used to
+     * generate any more logical {@code Connection}s.
+     *
+     * @throws SQLException
+     *             Thrown when an error occurs or the connection is already closed.
+     */
+    @Override
+    public void close() throws SQLException {
+        assertOpen();
+        closed = true;
+        try {
+            if (pStmtPool != null) {
+                try {
+                    pStmtPool.close();
+                } finally {
+                    pStmtPool = null;
+                }
+            }
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Cannot close connection (return to pool failed)", e);
+        } finally {
+            try {
+                connection.close();
+            } finally {
+                connection = null;
+            }
+        }
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @return a {@link PStmtKey} for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull());
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param autoGeneratedKeys
+     *            A flag indicating whether auto-generated keys should be returned; one of
+     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
+     * @return a key to uniquely identify a prepared statement.
+     */
+    protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param resultSetType
+     *            A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     * @return a key to uniquely identify a prepared statement.
+     */
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
+                resultSetConcurrency);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param resultSetType
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}
+     * @param resultSetHoldability
+     *            One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
+     * @return a key to uniquely identify a prepared statement.
+     */
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
+            final int resultSetHoldability) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
+                resultSetConcurrency, resultSetHoldability);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param resultSetType
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}
+     * @param resultSetConcurrency
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     * @param resultSetHoldability
+     *            One of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
+     * @param statementType
+     *            The SQL statement type, prepared or callable.
+     * @return a key to uniquely identify a prepared statement.
+     * @since 2.4.0
+     */
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
+            final int resultSetHoldability, final StatementType statementType) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
+                resultSetConcurrency, resultSetHoldability, statementType);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param resultSetType
+     *            A result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency
+     *            A concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     * @param statementType
+     *            The SQL statement type, prepared or callable.
+     * @return a key to uniquely identify a prepared statement.
+     * @since 2.4.0
+     */
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency,
+            final StatementType statementType) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType,
+                resultSetConcurrency, statementType);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param columnIndexes
+     *            An array of column indexes indicating the columns that should be returned from the inserted row or
+     *            rows.
+     * @return a key to uniquely identify a prepared statement.
+     */
+    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param statementType
+     *            The SQL statement type, prepared or callable.
+     * @return a key to uniquely identify a prepared statement.
+     */
+    protected PStmtKey createKey(final String sql, final StatementType statementType) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType);
+    }
+
+    /**
+     * Creates a {@link PStmtKey} for the given arguments.
+     *
+     * @param sql
+     *            The SQL statement.
+     * @param columnNames
+     *            An array of column names indicating the columns that should be returned from the inserted row or rows.
+     * @return a key to uniquely identify a prepared statement.
+     */
+    protected PStmtKey createKey(final String sql, final String[] columnNames) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames);
+    }
+
+    /**
+     * My {@link KeyedPooledObjectFactory} method for destroying {@link PreparedStatement}s.
+     *
+     * @param key
+     *            ignored
+     * @param pooledObject
+     *            the wrapped {@link PreparedStatement} to be destroyed.
+     */
+    @Override
+    public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
+            throws SQLException {
+        pooledObject.getObject().getInnermostDelegate().close();
+    }
+
+    /**
+     * Closes the physical connection and checks that the logical connection was closed as well.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        // Closing the Connection ensures that if anyone tries to use it,
+        // an error will occur.
+        try {
+            connection.close();
+        } catch (final Exception ignored) {
+            // ignore
+        }
+
+        // make sure the last connection is marked as closed
+        if (logicalConnection != null && !logicalConnection.isClosed()) {
+            throw new SQLException("PooledConnection was gc'ed, without its last Connection being closed.");
+        }
+    }
+
+    private String getCatalogOrNull() {
+        try {
+            return connection == null ? null : connection.getCatalog();
+        } catch (final SQLException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns a JDBC connection.
+     *
+     * @return The database connection.
+     * @throws SQLException
+     *             if the connection is not open or the previous logical connection is still open
+     */
+    @Override
+    public Connection getConnection() throws SQLException {
+        assertOpen();
+        // make sure the last connection is marked as closed
+        if (logicalConnection != null && !logicalConnection.isClosed()) {
+            // should notify pool of error so the pooled connection can
+            // be removed !FIXME!
+            throw new SQLException("PooledConnection was reused, without its previous Connection being closed.");
+        }
+
+        // the spec requires that this return a new Connection instance.
+        logicalConnection = new ConnectionImpl(this, connection, isAccessToUnderlyingConnectionAllowed());
+        return logicalConnection;
+    }
+
+    private String getSchemaOrNull() {
+        try {
+            return connection == null ? null : Jdbc41Bridge.getSchema(connection);
+        } catch (final SQLException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value of the accessToUnderlyingConnectionAllowed property.
+     *
+     * @return true if access to the underlying is allowed, false otherwise.
+     */
+    public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
+        return this.accessToUnderlyingConnectionAllowed;
+    }
+
+    /**
+     * My {@link KeyedPooledObjectFactory} method for creating {@link PreparedStatement}s.
+     *
+     * @param key
+     *            The key for the {@link PreparedStatement} to be created.
+     */
+    @SuppressWarnings("resource")
+    @Override
+    public PooledObject<DelegatingPreparedStatement> makeObject(final PStmtKey key) throws SQLException {
+        if (null == key) {
+            throw new IllegalArgumentException("Prepared statement key is null or invalid.");
+        }
+        if (key.getStmtType() == StatementType.PREPARED_STATEMENT) {
+            final PreparedStatement statement = (PreparedStatement) key.createStatement(connection);
+            @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
+            final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pStmtPool,
+                    delegatingConnection);
+            return new DefaultPooledObject<>(pps);
+        }
+        final CallableStatement statement = (CallableStatement) key.createStatement(connection);
+        @SuppressWarnings("unchecked")
+        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pStmtPool,
+                (DelegatingConnection<Connection>) delegatingConnection);
+        return new DefaultPooledObject<>(pcs);
+    }
+
+    /**
+     * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original.
+     * @param sql
+     *            The SQL statement.
+     * @return the normalized SQL statement.
+     */
+    protected String normalizeSQL(final String sql) {
+        return sql.trim();
+    }
+
+    /**
+     * Sends a connectionClosed event.
+     */
+    void notifyListeners() {
+        final ConnectionEvent event = new ConnectionEvent(this);
+        final Object[] listeners = eventListeners.toArray();
+        for (final Object listener : listeners) {
+            ((ConnectionEventListener) listener).connectionClosed(event);
+        }
+    }
+
+    /**
+     * My {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s. Currently invokes
+     * {@link PreparedStatement#clearParameters}.
+     *
+     * @param key
+     *            ignored
+     * @param pooledObject
+     *            a wrapped {@link PreparedStatement}
+     */
+    @Override
+    public void passivateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
+            throws SQLException {
+        @SuppressWarnings("resource")
+        final DelegatingPreparedStatement dps = pooledObject.getObject();
+        dps.clearParameters();
+        dps.passivate();
+    }
+
+    /**
+     * Creates or obtains a {@link CallableStatement} from my pool.
+     *
+     * @param sql
+     *            an SQL statement that may contain one or more '?' parameter placeholders. Typically this statement is
+     *            specified using JDBC call escape syntax.
+     * @return a default {@code CallableStatement} object containing the pre-compiled SQL statement.
+     * @throws SQLException
+     *                Thrown if a database access error occurs or this method is called on a closed connection.
+     * @since 2.4.0
+     */
+    CallableStatement prepareCall(final String sql) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareCall(sql);
+        }
+        try {
+            return (CallableStatement) pStmtPool.borrowObject(createKey(sql, StatementType.CALLABLE_STATEMENT));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareCall from pool failed", e);
+        }
+    }
+
+    /**
+     * Creates or obtains a {@link CallableStatement} from my pool.
+     *
+     * @param sql
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
+     *            more '?' parameters.
+     * @param resultSetType
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency
+     *            a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     * @return a {@code CallableStatement} object containing the pre-compiled SQL statement that will produce
+     *         {@code ResultSet} objects with the given type and concurrency.
+     * @throws SQLException
+     *             Thrown if a database access error occurs, this method is called on a closed connection or the given
+     *             parameters are not {@code ResultSet} constants indicating type and concurrency.
+     * @since 2.4.0
+     */
+    CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
+            throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareCall(sql, resultSetType, resultSetConcurrency);
+        }
+        try {
+            return (CallableStatement) pStmtPool.borrowObject(
+                    createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareCall from pool failed", e);
+        }
+    }
+
+    /**
+     * Creates or obtains a {@link CallableStatement} from my pool.
+     *
+     * @param sql
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain on or
+     *            more '?' parameters.
+     * @param resultSetType
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     * @param resultSetHoldability
+     *            one of the following {@code ResultSet} constants: {@code ResultSet.HOLD_CURSORS_OVER_COMMIT}
+     *            or {@code ResultSet.CLOSE_CURSORS_AT_COMMIT}.
+     * @return a new {@code CallableStatement} object, containing the pre-compiled SQL statement, that will
+     *         generate {@code ResultSet} objects with the given type, concurrency, and holdability.
+     * @throws SQLException
+     *             Thrown if a database access error occurs, this method is called on a closed connection or the given
+     *             parameters are not {@code ResultSet} constants indicating type, concurrency, and holdability.
+     * @since 2.4.0
+     */
+    CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
+            final int resultSetHoldability) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+        }
+        try {
+            return (CallableStatement) pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency,
+                    resultSetHoldability, StatementType.CALLABLE_STATEMENT));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareCall from pool failed", e);
+        }
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from my pool.
+     *
+     * @param sql the SQL statement.
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
+     *         the borrow failed.
+     */
+    PreparedStatement prepareStatement(final String sql) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareStatement(sql);
+        }
+        try {
+            return pStmtPool.borrowObject(createKey(sql));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareStatement from pool failed", e);
+        }
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from my pool.
+     *
+     * @param sql
+     *            an SQL statement that may contain one or more '?' IN parameter placeholders.
+     * @param autoGeneratedKeys
+     *            a flag indicating whether auto-generated keys should be returned; one of
+     *            {@code Statement.RETURN_GENERATED_KEYS} or {@code Statement.NO_GENERATED_KEYS}.
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
+     *         the borrow failed.
+     * @see Connection#prepareStatement(String, int)
+     */
+    PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareStatement(sql, autoGeneratedKeys);
+        }
+        try {
+            return pStmtPool.borrowObject(createKey(sql, autoGeneratedKeys));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareStatement from pool failed", e);
+        }
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from my pool.
+     *
+     * @param sql
+     *            a {@code String} object that is the SQL statement to be sent to the database; may contain one or
+     *            more '?' IN parameters.
+     * @param resultSetType
+     *            a result set type; one of {@code ResultSet.TYPE_FORWARD_ONLY},
+     *            {@code ResultSet.TYPE_SCROLL_INSENSITIVE}, or {@code ResultSet.TYPE_SCROLL_SENSITIVE}.
+     * @param resultSetConcurrency
+     *            a concurrency type; one of {@code ResultSet.CONCUR_READ_ONLY} or
+     *            {@code ResultSet.CONCUR_UPDATABLE}.
+     *
+     * @return a {@link PoolablePreparedStatement}.
+     * @throws SQLException Thrown if a database access error occurs, this method is called on a closed connection, or
+     *         the borrow failed.
+     * @see Connection#prepareStatement(String, int, int)
+     */
+    PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
+            throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareStatement(sql, resultSetType, resultSetConcurrency);
+        }
+        try {
+            return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareStatement from pool failed", e);
+        }
+    }
+
+    PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
+            final int resultSetHoldability) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+        }
+        try {
+            return pStmtPool.borrowObject(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareStatement from pool failed", e);
+        }
+    }
+
+    PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareStatement(sql, columnIndexes);
+        }
+        try {
+            return pStmtPool.borrowObject(createKey(sql, columnIndexes));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareStatement from pool failed", e);
+        }
+    }
+
+    PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
+        if (pStmtPool == null) {
+            return connection.prepareStatement(sql, columnNames);
+        }
+        try {
+            return pStmtPool.borrowObject(createKey(sql, columnNames));
+        } catch (final RuntimeException e) {
+            throw e;
+        } catch (final Exception e) {
+            throw new SQLException("Borrow prepareStatement from pool failed", e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void removeConnectionEventListener(final ConnectionEventListener listener) {
+        eventListeners.remove(listener);
+    }
+
+    @Override
+    public void removeStatementEventListener(final StatementEventListener listener) {
+        statementEventListeners.remove(listener);
+    }
+    /* JDBC_4_ANT_KEY_END */
+
+    /**
+     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
+     * the underlying connection. (Default: false.)
+     *
+     * @param allow
+     *            Access to the underlying connection is granted when true.
+     */
+    public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
+        this.accessToUnderlyingConnectionAllowed = allow;
+    }
+
+    public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> statementPool) {
+        pStmtPool = statementPool;
+    }
+
+    /**
+     * @since 2.6.0
+     */
+    @Override
+    public synchronized String toString() {
+        final StringBuilder builder = new StringBuilder(super.toString());
+        builder.append("[connection=");
+        builder.append(connection);
+        builder.append(", delegatingConnection=");
+        builder.append(delegatingConnection);
+        builder.append(", logicalConnection=");
+        builder.append(logicalConnection);
+        builder.append(", eventListeners=");
+        builder.append(eventListeners);
+        builder.append(", statementEventListeners=");
+        builder.append(statementEventListeners);
+        builder.append(", closed=");
+        builder.append(closed);
+        builder.append(", pStmtPool=");
+        builder.append(pStmtPool);
+        builder.append(", accessToUnderlyingConnectionAllowed=");
+        builder.append(accessToUnderlyingConnectionAllowed);
+        builder.append("]");
+        return builder.toString();
+    }
+
+    /**
+     * My {@link KeyedPooledObjectFactory} method for validating {@link PreparedStatement}s.
+     *
+     * @param key
+     *            Ignored.
+     * @param pooledObject
+     *            Ignored.
+     * @return {@code true}
+     */
+    @Override
+    public boolean validateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject) {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java
index d7f3b37d..9962fe98 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java
@@ -1,498 +1,498 @@
-/*
- * 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.time.Instant;
-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
- */
-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 Exception {
-        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 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();
-    }
-
-    /**
-     * (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);
-        }
-    }
-
-    // ***********************************************************************
-    // java.sql.ConnectionEventListener implementation
-    // ***********************************************************************
-
-    @Override
-    public synchronized PooledObject<PooledConnectionAndInfo> makeObject() {
-        final PooledConnectionAndInfo pci;
-        try {
-            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);
-            pci = new PooledConnectionAndInfo(pc, userPassKey);
-            pcMap.put(pc, pci);
-        } catch (final SQLException e) {
-            throw new RuntimeException(e.getMessage());
-        }
-        return new DefaultPooledObject<>(pci);
-    }
-
-    @Override
-    public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        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> pooledObject) throws Exception {
-        if (maxConnDuration.compareTo(Duration.ZERO) > 0) {
-            final Duration lifetimeDuration = Duration.between(pooledObject.getCreateInstant(), Instant.now());
-            if (lifetimeDuration.compareTo(maxConnDuration) > 0) {
-                throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, 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;
-    }
-}
+/*
+ * 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.time.Instant;
+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
+ */
+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 Exception {
+        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);
+        }
+    }
+
+    // ***********************************************************************
+    // java.sql.ConnectionEventListener implementation
+    // ***********************************************************************
+
+    @Override
+    public synchronized PooledObject<PooledConnectionAndInfo> makeObject() {
+        final PooledConnectionAndInfo pci;
+        try {
+            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);
+            pci = new PooledConnectionAndInfo(pc, userPassKey);
+            pcMap.put(pc, pci);
+        } catch (final SQLException e) {
+            throw new RuntimeException(e.getMessage());
+        }
+        return new DefaultPooledObject<>(pci);
+    }
+
+    @Override
+    public void passivateObject(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+        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> pooledObject) throws Exception {
+        if (maxConnDuration.compareTo(Duration.ZERO) > 0) {
+            final Duration lifetimeDuration = Duration.between(pooledObject.getCreateInstant(), Instant.now());
+            if (lifetimeDuration.compareTo(maxConnDuration) > 0) {
+                throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, 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;
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
index 23832cb4..f7d9f58a 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
@@ -49,14 +49,13 @@ abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
     /**
      * Closes all pools associated with this class.
      *
-     * @throws Exception
+     * @throws ListException
      *             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 {
+    public static void closeAll() throws ListException {
         // Get iterator to loop over all instances of this data source.
         final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
         for (final Entry<String, InstanceKeyDataSource> next : INSTANCE_MAP.entrySet()) {
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java
index 511b36e8..e2c3f991 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java
@@ -1,390 +1,390 @@
-/*
- * 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.time.Instant;
-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.KeyedObjectPool;
-import org.apache.commons.pool2.KeyedPooledObjectFactory;
-import org.apache.commons.pool2.PooledObject;
-import org.apache.commons.pool2.impl.DefaultPooledObject;
-
-/**
- * A {@link KeyedPooledObjectFactory} that creates {@link org.apache.commons.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 Duration validationQueryTimeoutDuration;
-    private final boolean rollbackAfterValidation;
-    private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
-    private Duration maxConnLifetime = 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 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 Duration to allow for the validation query to complete
-     * @param rollbackAfterValidation
-     *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
-     * @since 2.10.0
-     */
-    public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
-            final Duration validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
-        this.cpds = cpds;
-        this.validationQuery = validationQuery;
-        this.validationQueryTimeoutDuration = validationQueryTimeoutSeconds;
-        this.rollbackAfterValidation = rollbackAfterValidation;
-    }
-
-    /**
-     * Creates 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.
-     * @deprecated Use {@link #KeyedCPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean)}.
-     */
-    @Deprecated
-    public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
-            final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
-        this(cpds, validationQuery, Duration.ofSeconds(validationQueryTimeoutSeconds), rollbackAfterValidation);
-    }
-
-    @Override
-    public void activateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        validateLifetime(p);
-    }
-
-    /**
-     * 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);
-        }
-    }
-
-    /**
-     * 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();
-        }
-    }
-
-    /**
-     * 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 pooledConnection = p.getObject().getPooledConnection();
-        pooledConnection.removeConnectionEventListener(this);
-        pcMap.remove(pooledConnection);
-        pooledConnection.close();
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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);
-        }
-    }
-
-    // ***********************************************************************
-    // java.sql.ConnectionEventListener implementation
-    // ***********************************************************************
-
-    /**
-     * Creates a new {@code PooledConnectionAndInfo} from the given {@code UserPassKey}.
-     *
-     * @param userPassKey
-     *            {@code UserPassKey} containing user credentials
-     * @throws SQLException
-     *             if the connection could not be created.
-     * @see org.apache.commons.pool2.KeyedPooledObjectFactory#makeObject(java.lang.Object)
-     */
-    @Override
-    public synchronized PooledObject<PooledConnectionAndInfo> makeObject(final UserPassKey userPassKey) throws Exception {
-        PooledConnection pooledConnection = null;
-        final String userName = userPassKey.getUserName();
-        final String password = userPassKey.getPassword();
-        if (userName == null) {
-            pooledConnection = cpds.getPooledConnection();
-        } else {
-            pooledConnection = cpds.getPooledConnection(userName, password);
-        }
-
-        if (pooledConnection == 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
-        pooledConnection.addConnectionEventListener(this);
-        final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pooledConnection, userPassKey);
-        pcMap.put(pooledConnection, pci);
-
-        return new DefaultPooledObject<>(pci);
-    }
-
-    @Override
-    public void passivateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        validateLifetime(p);
-    }
-
-    /**
-     * Sets the maximum lifetime 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 milliseconds.
-     * @since 2.10.0
-     */
-    public void setMaxConn(final Duration maxConnLifetimeMillis) {
-        this.maxConnLifetime = maxConnLifetimeMillis;
-    }
-
-    /**
-     * Sets the maximum lifetime 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 milliseconds.
-     * @since 2.9.0
-     * @deprecated Use {@link #setMaxConn(Duration)}.
-     */
-    @Deprecated
-    public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) {
-        this.maxConnLifetime = maxConnLifetimeMillis;
-    }
-
-    /**
-     * 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 #setMaxConnLifetime(Duration)}.
-     */
-    @Deprecated
-    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
-        setMaxConn(Duration.ofMillis(maxConnLifetimeMillis));
-    }
-
-    /**
-     * 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.
-    }
-
-    public void setPool(final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool) {
-        this.pool = pool;
-    }
-
-    private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
-        if (maxConnLifetime.compareTo(Duration.ZERO) > 0) {
-            final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now());
-            if (lifetimeDuration.compareTo(maxConnLifetime) > 0) {
-                throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxConnLifetime));
-            }
-        }
-    }
-
-    /**
-     * Validates a pooled connection.
-     *
-     * @param key
-     *            ignored
-     * @param pooledObject
-     *            wrapped {@code 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 pooledConn = pooledObject.getObject().getPooledConnection();
-        Connection conn = null;
-        validatingSet.add(pooledConn);
-        if (null == validationQuery) {
-            Duration timeoutDuration = validationQueryTimeoutDuration;
-            if (timeoutDuration.isNegative()) {
-                timeoutDuration = Duration.ZERO;
-            }
-            try {
-                conn = pooledConn.getConnection();
-                valid = conn.isValid((int) timeoutDuration.getSeconds());
-            } catch (final SQLException e) {
-                valid = false;
-            } finally {
-                Utils.closeQuietly((AutoCloseable) conn);
-                validatingSet.remove(pooledConn);
-            }
-        } 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(pooledConn);
-            try {
-                conn = pooledConn.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(pooledConn);
-            }
-        }
-        return valid;
-    }
-}
+/*
+ * 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.time.Instant;
+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.KeyedObjectPool;
+import org.apache.commons.pool2.KeyedPooledObjectFactory;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+
+/**
+ * A {@link KeyedPooledObjectFactory} that creates {@link org.apache.commons.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 Duration validationQueryTimeoutDuration;
+    private final boolean rollbackAfterValidation;
+    private KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool;
+    private Duration maxConnLifetime = 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 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 Duration to allow for the validation query to complete
+     * @param rollbackAfterValidation
+     *            whether a rollback should be issued after {@link #validateObject validating} {@link Connection}s.
+     * @since 2.10.0
+     */
+    public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
+            final Duration validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
+        this.cpds = cpds;
+        this.validationQuery = validationQuery;
+        this.validationQueryTimeoutDuration = validationQueryTimeoutSeconds;
+        this.rollbackAfterValidation = rollbackAfterValidation;
+    }
+
+    /**
+     * Creates 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.
+     * @deprecated Use {@link #KeyedCPDSConnectionFactory(ConnectionPoolDataSource, String, Duration, boolean)}.
+     */
+    @Deprecated
+    public KeyedCPDSConnectionFactory(final ConnectionPoolDataSource cpds, final String validationQuery,
+            final int validationQueryTimeoutSeconds, final boolean rollbackAfterValidation) {
+        this(cpds, validationQuery, Duration.ofSeconds(validationQueryTimeoutSeconds), rollbackAfterValidation);
+    }
+
+    @Override
+    public void activateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+        validateLifetime(p);
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    /**
+     * 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();
+        }
+    }
+
+    /**
+     * Closes the PooledConnection and stops listening for events from it.
+     */
+    @Override
+    public void destroyObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws SQLException {
+        final PooledConnection pooledConnection = p.getObject().getPooledConnection();
+        pooledConnection.removeConnectionEventListener(this);
+        pcMap.remove(pooledConnection);
+        pooledConnection.close();
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    // ***********************************************************************
+    // java.sql.ConnectionEventListener implementation
+    // ***********************************************************************
+
+    /**
+     * Creates a new {@code PooledConnectionAndInfo} from the given {@code UserPassKey}.
+     *
+     * @param userPassKey
+     *            {@code UserPassKey} containing user credentials
+     * @throws SQLException
+     *             if the connection could not be created.
+     * @see org.apache.commons.pool2.KeyedPooledObjectFactory#makeObject(java.lang.Object)
+     */
+    @Override
+    public synchronized PooledObject<PooledConnectionAndInfo> makeObject(final UserPassKey userPassKey) throws SQLException {
+        PooledConnection pooledConnection = null;
+        final String userName = userPassKey.getUserName();
+        final String password = userPassKey.getPassword();
+        if (userName == null) {
+            pooledConnection = cpds.getPooledConnection();
+        } else {
+            pooledConnection = cpds.getPooledConnection(userName, password);
+        }
+
+        if (pooledConnection == 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
+        pooledConnection.addConnectionEventListener(this);
+        final PooledConnectionAndInfo pci = new PooledConnectionAndInfo(pooledConnection, userPassKey);
+        pcMap.put(pooledConnection, pci);
+
+        return new DefaultPooledObject<>(pci);
+    }
+
+    @Override
+    public void passivateObject(final UserPassKey key, final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+        validateLifetime(p);
+    }
+
+    /**
+     * Sets the maximum lifetime 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 milliseconds.
+     * @since 2.10.0
+     */
+    public void setMaxConn(final Duration maxConnLifetimeMillis) {
+        this.maxConnLifetime = maxConnLifetimeMillis;
+    }
+
+    /**
+     * Sets the maximum lifetime 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 milliseconds.
+     * @since 2.9.0
+     * @deprecated Use {@link #setMaxConn(Duration)}.
+     */
+    @Deprecated
+    public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) {
+        this.maxConnLifetime = maxConnLifetimeMillis;
+    }
+
+    /**
+     * 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 #setMaxConnLifetime(Duration)}.
+     */
+    @Deprecated
+    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
+        setMaxConn(Duration.ofMillis(maxConnLifetimeMillis));
+    }
+
+    /**
+     * 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.
+    }
+
+    public void setPool(final KeyedObjectPool<UserPassKey, PooledConnectionAndInfo> pool) {
+        this.pool = pool;
+    }
+
+    private void validateLifetime(final PooledObject<PooledConnectionAndInfo> p) throws Exception {
+        if (maxConnLifetime.compareTo(Duration.ZERO) > 0) {
+            final Duration lifetimeDuration = Duration.between(p.getCreateInstant(), Instant.now());
+            if (lifetimeDuration.compareTo(maxConnLifetime) > 0) {
+                throw new Exception(Utils.getMessage("connectionFactory.lifetimeExceeded", lifetimeDuration, maxConnLifetime));
+            }
+        }
+    }
+
+    /**
+     * Validates a pooled connection.
+     *
+     * @param key
+     *            ignored
+     * @param pooledObject
+     *            wrapped {@code 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 pooledConn = pooledObject.getObject().getPooledConnection();
+        Connection conn = null;
+        validatingSet.add(pooledConn);
+        if (null == validationQuery) {
+            Duration timeoutDuration = validationQueryTimeoutDuration;
+            if (timeoutDuration.isNegative()) {
+                timeoutDuration = Duration.ZERO;
+            }
+            try {
+                conn = pooledConn.getConnection();
+                valid = conn.isValid((int) timeoutDuration.getSeconds());
+            } catch (final SQLException e) {
+                valid = false;
+            } finally {
+                Utils.closeQuietly((AutoCloseable) conn);
+                validatingSet.remove(pooledConn);
+            }
+        } 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(pooledConn);
+            try {
+                conn = pooledConn.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(pooledConn);
+            }
+        }
+        return valid;
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java b/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java
index cbee6cda..d8c70b15 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java
@@ -1,230 +1,230 @@
-/*
- * 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.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;
-
-    // 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());
-    }
-
-    @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;
-    }
-
-    // ----------------------------------------------------------------------
-    // 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();
-    }
-
-    @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;
-    }
-
-    /**
-     * Supports Serialization interface.
-     *
-     * @param in
-     *            a {@code java.io.ObjectInputStream} 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);
-        }
-    }
-
-    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.setMinEvictableIdleTime(getDefaultMinEvictableIdleDuration());
-        config.setMinIdlePerKey(getDefaultMinIdle());
-        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
-        config.setSoftMinEvictableIdleTime(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);
-    }
-}
+/*
+ * 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.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;
+
+    // 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());
+    }
+
+    @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;
+    }
+
+    // ----------------------------------------------------------------------
+    // 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();
+    }
+
+    @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;
+    }
+
+    /**
+     * Supports Serialization interface.
+     *
+     * @param in
+     *            a {@code java.io.ObjectInputStream} 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);
+        }
+    }
+
+    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.setMinEvictableIdleTime(getDefaultMinEvictableIdleDuration());
+        config.setMinIdlePerKey(getDefaultMinIdle());
+        config.setNumTestsPerEvictionRun(getDefaultNumTestsPerEvictionRun());
+        config.setSoftMinEvictableIdleTime(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);
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java b/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java
index e8791c0a..7eb81015 100644
--- a/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/managed/PoolableManagedConnectionFactory.java
@@ -1,110 +1,111 @@
-/*
- * 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.managed;
-
-import java.sql.Connection;
-import java.time.Duration;
-
-import javax.management.ObjectName;
-
-import org.apache.commons.dbcp2.Constants;
-import org.apache.commons.dbcp2.DelegatingPreparedStatement;
-import org.apache.commons.dbcp2.PStmtKey;
-import org.apache.commons.dbcp2.PoolableConnection;
-import org.apache.commons.dbcp2.PoolableConnectionFactory;
-import org.apache.commons.dbcp2.PoolingConnection;
-import org.apache.commons.pool2.KeyedObjectPool;
-import org.apache.commons.pool2.PooledObject;
-import org.apache.commons.pool2.impl.DefaultPooledObject;
-import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
-import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
-
-/**
- * A {@link PoolableConnectionFactory} that creates {@link PoolableManagedConnection}s.
- *
- * @since 2.0
- */
-public class PoolableManagedConnectionFactory extends PoolableConnectionFactory {
-
-    /** Transaction registry associated with connections created by this factory */
-    private final TransactionRegistry transactionRegistry;
-
-    /**
-     * Creates a PoolableManagedConnectionFactory and attach it to a connection pool.
-     *
-     * @param connFactory
-     *            XAConnectionFactory
-     * @param dataSourceJmxName
-     *            The data source name.
-     */
-    public PoolableManagedConnectionFactory(final XAConnectionFactory connFactory, final ObjectName dataSourceJmxName) {
-        super(connFactory, dataSourceJmxName);
-        this.transactionRegistry = connFactory.getTransactionRegistry();
-    }
-
-    /**
-     * @return The transaction registry.
-     * @since 2.6.0
-     */
-    public TransactionRegistry getTransactionRegistry() {
-        return transactionRegistry;
-    }
-
-    /**
-     * Uses the configured XAConnectionFactory to create a {@link PoolableManagedConnection}. Throws
-     * {@code IllegalStateException} if the connection factory returns null. Also initializes the connection using
-     * configured initialization SQL (if provided) and sets up a prepared statement pool associated with the
-     * PoolableManagedConnection if statement pooling is enabled.
-     */
-    @SuppressWarnings("resource") // Connection is released elsewhere.
-    @Override
-    public synchronized PooledObject<PoolableConnection> makeObject() throws Exception {
-        Connection conn = getConnectionFactory().createConnection();
-        if (conn == null) {
-            throw new IllegalStateException("Connection factory returned null from createConnection");
-        }
-        initializeConnection(conn);
-        if (getPoolStatements()) {
-            conn = new PoolingConnection(conn);
-            final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
-            config.setMaxTotalPerKey(-1);
-            config.setBlockWhenExhausted(false);
-            config.setMaxWait(Duration.ZERO);
-            config.setMaxIdlePerKey(1);
-            config.setMaxTotal(getMaxOpenPreparedStatements());
-            final ObjectName dataSourceJmxName = getDataSourceJmxName();
-            final long connIndex = getConnectionIndex().getAndIncrement();
-            if (dataSourceJmxName != null) {
-                final StringBuilder base = new StringBuilder(dataSourceJmxName.toString());
-                base.append(Constants.JMX_CONNECTION_BASE_EXT);
-                base.append(connIndex);
-                config.setJmxNameBase(base.toString());
-                config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX);
-            } else {
-                config.setJmxEnabled(false);
-            }
-            final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(
-                    (PoolingConnection) conn, config);
-            ((PoolingConnection) conn).setStatementPool(stmtPool);
-            ((PoolingConnection) conn).setCacheState(getCacheState());
-        }
-        final PoolableManagedConnection pmc = new PoolableManagedConnection(transactionRegistry, conn, getPool(),
-                getDisconnectionSqlCodes(), isFastFailValidation());
-        pmc.setCacheState(getCacheState());
-        return new DefaultPooledObject<>(pmc);
-    }
-}
+/*
+ * 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.managed;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.time.Duration;
+
+import javax.management.ObjectName;
+
+import org.apache.commons.dbcp2.Constants;
+import org.apache.commons.dbcp2.DelegatingPreparedStatement;
+import org.apache.commons.dbcp2.PStmtKey;
+import org.apache.commons.dbcp2.PoolableConnection;
+import org.apache.commons.dbcp2.PoolableConnectionFactory;
+import org.apache.commons.dbcp2.PoolingConnection;
+import org.apache.commons.pool2.KeyedObjectPool;
+import org.apache.commons.pool2.PooledObject;
+import org.apache.commons.pool2.impl.DefaultPooledObject;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
+
+/**
+ * A {@link PoolableConnectionFactory} that creates {@link PoolableManagedConnection}s.
+ *
+ * @since 2.0
+ */
+public class PoolableManagedConnectionFactory extends PoolableConnectionFactory {
+
+    /** Transaction registry associated with connections created by this factory */
+    private final TransactionRegistry transactionRegistry;
+
+    /**
+     * Creates a PoolableManagedConnectionFactory and attach it to a connection pool.
+     *
+     * @param connFactory
+     *            XAConnectionFactory
+     * @param dataSourceJmxName
+     *            The data source name.
+     */
+    public PoolableManagedConnectionFactory(final XAConnectionFactory connFactory, final ObjectName dataSourceJmxName) {
+        super(connFactory, dataSourceJmxName);
+        this.transactionRegistry = connFactory.getTransactionRegistry();
+    }
+
+    /**
+     * @return The transaction registry.
+     * @since 2.6.0
+     */
+    public TransactionRegistry getTransactionRegistry() {
+        return transactionRegistry;
+    }
+
+    /**
+     * Uses the configured XAConnectionFactory to create a {@link PoolableManagedConnection}. Throws
+     * {@code IllegalStateException} if the connection factory returns null. Also initializes the connection using
+     * configured initialization SQL (if provided) and sets up a prepared statement pool associated with the
+     * PoolableManagedConnection if statement pooling is enabled.
+     */
+    @SuppressWarnings("resource") // Connection is released elsewhere.
+    @Override
+    public synchronized PooledObject<PoolableConnection> makeObject() throws SQLException {
+        Connection conn = getConnectionFactory().createConnection();
+        if (conn == null) {
+            throw new IllegalStateException("Connection factory returned null from createConnection");
+        }
+        initializeConnection(conn);
+        if (getPoolStatements()) {
+            conn = new PoolingConnection(conn);
+            final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
+            config.setMaxTotalPerKey(-1);
+            config.setBlockWhenExhausted(false);
+            config.setMaxWait(Duration.ZERO);
+            config.setMaxIdlePerKey(1);
+            config.setMaxTotal(getMaxOpenPreparedStatements());
+            final ObjectName dataSourceJmxName = getDataSourceJmxName();
+            final long connIndex = getConnectionIndex().getAndIncrement();
+            if (dataSourceJmxName != null) {
+                final StringBuilder base = new StringBuilder(dataSourceJmxName.toString());
+                base.append(Constants.JMX_CONNECTION_BASE_EXT);
+                base.append(connIndex);
+                config.setJmxNameBase(base.toString());
+                config.setJmxNamePrefix(Constants.JMX_STATEMENT_POOL_PREFIX);
+            } else {
+                config.setJmxEnabled(false);
+            }
+            final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(
+                    (PoolingConnection) conn, config);
+            ((PoolingConnection) conn).setStatementPool(stmtPool);
+            ((PoolingConnection) conn).setCacheState(getCacheState());
+        }
+        final PoolableManagedConnection pmc = new PoolableManagedConnection(transactionRegistry, conn, getPool(),
+                getDisconnectionSqlCodes(), isFastFailValidation());
+        pmc.setCacheState(getCacheState());
+        return new DefaultPooledObject<>(pmc);
+    }
+}