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 13:24:36 UTC

[commons-dbcp] branch master updated (0014613b -> 74ee0145)

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

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


    from 0014613b Bump h2 from 2.1.212 to 2.1.214 #196
     new 3c9b1e65 Bump maven-pmd-plugin from 3.16.0 to 3.17.0
     new d978d463 Suppress PMD EmptyCatchBlock from ruleset Error Prone
     new 1c09c173 Fix PMD UselessQualifiedThis (Code Style)
     new 1cbcba40 Fix PMD UselessOverridingMethod (Design)
     new d59b4548 Add PMD check to default Maven goal.
     new 316ed1b3 Refactor duplicate code
     new 74ee0145 Bump JaCoCo from 0.8.7 to 0.8.8

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 pom.xml                                            |    7 +-
 src/changes/changes.xml                            |   11 +-
 .../apache/commons/dbcp2/DelegatingConnection.java | 2122 ++++++++--------
 .../org/apache/commons/dbcp2/Jdbc41Bridge.java     |  976 +++----
 .../apache/commons/dbcp2/PoolableConnection.java   |  808 +++---
 .../apache/commons/dbcp2/PoolingConnection.java    | 1246 ++++-----
 .../org/apache/commons/dbcp2/PoolingDriver.java    |  520 ++--
 src/main/java/org/apache/commons/dbcp2/Utils.java  |  412 +--
 .../commons/dbcp2/datasources/CharArray.java       |  169 +-
 .../dbcp2/datasources/InstanceKeyDataSource.java   | 2662 ++++++++++----------
 .../datasources/InstanceKeyDataSourceFactory.java  |  696 ++---
 .../dbcp2/datasources/PerUserPoolDataSource.java   | 2636 +++++++++----------
 .../dbcp2/managed/LocalXAConnectionFactory.java    |  758 +++---
 .../commons/dbcp2/managed/ManagedConnection.java   |  656 ++---
 .../dbcp2/managed/SynchronizationAdapter.java}     |   20 +-
 .../commons/dbcp2/managed/TransactionContext.java  |  414 ++-
 .../dbcp2/managed/TestBasicManagedDataSource.java  |  489 ++--
 .../dbcp2/managed/TestManagedDataSourceInTx.java   |  877 ++++---
 18 files changed, 7737 insertions(+), 7742 deletions(-)
 copy src/{test/java/org/apache/commons/dbcp2/TestUtils.java => main/java/org/apache/commons/dbcp2/managed/SynchronizationAdapter.java} (69%)


[commons-dbcp] 01/07: Bump maven-pmd-plugin from 3.16.0 to 3.17.0

Posted by gg...@apache.org.
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

commit 3c9b1e65494017e873bf67627b9a2e63c4832d1c
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 08:37:37 2022 -0400

    Bump maven-pmd-plugin from 3.16.0 to 3.17.0
    
    Bump pmd from 6.44.0 to 6.47.0
---
 pom.xml                 | 3 ++-
 src/changes/changes.xml | 5 ++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index de39abe7..fd0d11ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -335,6 +335,8 @@
     <commons.japicmp.ignoreMissingClasses>true</commons.japicmp.ignoreMissingClasses>
     <spotbugs.plugin.version>4.7.0.0</spotbugs.plugin.version>
     <spotbugs.impl.version>4.7.0</spotbugs.impl.version>
+    <commons.pmd.version>3.17.0</commons.pmd.version>
+    <commons.pmd-impl.version>6.47.0</commons.pmd-impl.version>
     <!-- Commons Release Plugin -->
     <commons.bc.version>2.9.0</commons.bc.version>
     <commons.release.isDistModule>true</commons.release.isDistModule>
@@ -523,7 +525,6 @@
       </plugin>
       <plugin>
         <artifactId>maven-pmd-plugin</artifactId>
-        <version>3.17.0</version>
         <configuration>
           <targetJdk>${maven.compiler.target}</targetJdk>
         </configuration>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index cbdbc6d5..fd8fe0d9 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -123,9 +123,12 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="update" due-to="Dependabot">
         Bump maven-javadoc-plugin from 3.3.0 to 3.4.0 #131, #184.
       </action>
-      <action dev="ggregory" type="update" due-to="Dependabot">
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
         Bump maven-pmd-plugin from 3.14.0 to 3.17.0 #132, #172, #195.
       </action>
+      <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">
+        Bump pmd from 6.44.0 to 6.47.0.
+      </action>
       <action dev="ggregory" type="update" due-to="Dependabot">
         Bump narayana-jta from 5.12.0.Final to 5.12.7.Final #134, #156, #163, #185, #197.
       </action>


[commons-dbcp] 03/07: Fix PMD UselessQualifiedThis (Code Style)

Posted by gg...@apache.org.
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

commit 1c09c1736ae69440d16cfbb4ced27deaa0d825a2
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 08:58:18 2022 -0400

    Fix PMD UselessQualifiedThis (Code Style)
---
 src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java b/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java
index a2a0211e..aac64a01 100644
--- a/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java
+++ b/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java
@@ -88,8 +88,7 @@ public class TransactionContext {
         try {
             if (!isActive()) {
                 final Transaction transaction = this.transactionRef.get();
-                listener.afterCompletion(TransactionContext.this,
-                        transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED);
+                listener.afterCompletion(this, transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED);
                 return;
             }
             final Synchronization s = new Synchronization() {


[commons-dbcp] 02/07: Suppress PMD EmptyCatchBlock from ruleset Error Prone

Posted by gg...@apache.org.
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

commit d978d4635a18c45fc152b48a109dcae2606e4240
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 08:55:55 2022 -0400

    Suppress PMD EmptyCatchBlock from ruleset Error Prone
---
 .../apache/commons/dbcp2/DelegatingConnection.java | 2122 ++++++++--------
 .../org/apache/commons/dbcp2/Jdbc41Bridge.java     |  976 +++----
 .../apache/commons/dbcp2/PoolableConnection.java   |  808 +++---
 .../apache/commons/dbcp2/PoolingConnection.java    | 1246 ++++-----
 .../org/apache/commons/dbcp2/PoolingDriver.java    |  520 ++--
 src/main/java/org/apache/commons/dbcp2/Utils.java  |  412 +--
 .../dbcp2/datasources/InstanceKeyDataSource.java   | 2662 ++++++++++----------
 .../datasources/InstanceKeyDataSourceFactory.java  |  696 ++---
 .../dbcp2/datasources/PerUserPoolDataSource.java   | 2636 +++++++++----------
 .../dbcp2/managed/LocalXAConnectionFactory.java    |  758 +++---
 .../commons/dbcp2/managed/ManagedConnection.java   |  656 ++---
 .../commons/dbcp2/managed/TransactionContext.java  |  420 +--
 12 files changed, 6956 insertions(+), 6956 deletions(-)

diff --git a/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java b/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java
index 1199c96d..62163a03 100644
--- a/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java
+++ b/src/main/java/org/apache/commons/dbcp2/DelegatingConnection.java
@@ -1,1061 +1,1061 @@
-/*
- * 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;
-
-import java.sql.Array;
-import java.sql.Blob;
-import java.sql.CallableStatement;
-import java.sql.ClientInfoStatus;
-import java.sql.Clob;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.NClob;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLClientInfoException;
-import java.sql.SQLException;
-import java.sql.SQLWarning;
-import java.sql.SQLXML;
-import java.sql.Savepoint;
-import java.sql.Statement;
-import java.sql.Struct;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.concurrent.Executor;
-
-/**
- * A base delegating implementation of {@link Connection}.
- * <p>
- * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active,
- * and call the corresponding method on the "delegate" provided in my constructor.
- * </p>
- * <p>
- * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking
- * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of
- * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout.
- * </p>
- *
- * @param <C>
- *            the Connection type
- *
- * @since 2.0
- */
-public class DelegatingConnection<C extends Connection> extends AbandonedTrace implements Connection {
-
-    private static final Map<String, ClientInfoStatus> EMPTY_FAILED_PROPERTIES = Collections
-            .<String, ClientInfoStatus>emptyMap();
-
-    /** My delegate {@link Connection}. */
-    private volatile C connection;
-
-    private volatile boolean closed;
-
-    private boolean cacheState = true;
-    private Boolean cachedAutoCommit;
-    private Boolean cachedReadOnly;
-    private String cachedCatalog;
-    private String cachedSchema;
-    private Duration defaultQueryTimeoutDuration;
-
-    /**
-     * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
-     *
-     * @param connection the {@link Connection} to delegate all calls to.
-     */
-    public DelegatingConnection(final C connection) {
-        this.connection = connection;
-    }
-
-    @Override
-    public void abort(final Executor executor) throws SQLException {
-        try {
-            Jdbc41Bridge.abort(connection, executor);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    protected void activate() {
-        closed = false;
-        setLastUsed();
-        if (connection instanceof DelegatingConnection) {
-            ((DelegatingConnection<?>) connection).activate();
-        }
-    }
-
-    protected void checkOpen() throws SQLException {
-        if (closed) {
-            if (null != connection) {
-                String label = "";
-                try {
-                    label = connection.toString();
-                } catch (final Exception ex) {
-                    // ignore, leave label empty
-                }
-                throw new SQLException("Connection " + label + " is closed.");
-            }
-            throw new SQLException("Connection is null.");
-        }
-    }
-
-    /**
-     * Clears the cached state. Call when you known that the underlying connection may have been accessed
-     * directly.
-     */
-    public void clearCachedState() {
-        cachedAutoCommit = null;
-        cachedReadOnly = null;
-        cachedSchema = null;
-        cachedCatalog = null;
-        if (connection instanceof DelegatingConnection) {
-            ((DelegatingConnection<?>) connection).clearCachedState();
-        }
-    }
-
-    @Override
-    public void clearWarnings() throws SQLException {
-        checkOpen();
-        try {
-            connection.clearWarnings();
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    /**
-     * Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that
-     * override this method must:
-     * <ol>
-     * <li>Call {@link #passivate()}</li>
-     * <li>Call close (or the equivalent appropriate action) on the wrapped connection</li>
-     * <li>Set {@code closed} to {@code false}</li>
-     * </ol>
-     */
-    @Override
-    public void close() throws SQLException {
-        if (!closed) {
-            closeInternal();
-        }
-    }
-
-    protected final void closeInternal() throws SQLException {
-        try {
-            passivate();
-        } finally {
-            if (connection != null) {
-                boolean connectionIsClosed;
-                try {
-                    connectionIsClosed = connection.isClosed();
-                } catch (final SQLException e) {
-                    // not sure what the state is, so assume the connection is open.
-                    connectionIsClosed = false;
-                }
-                try {
-                    // DBCP-512: Avoid exceptions when closing a connection in mutli-threaded use case.
-                    // Avoid closing again, which should be a no-op, but some drivers like H2 throw an exception when
-                    // closing from multiple threads.
-                    if (!connectionIsClosed) {
-                        connection.close();
-                    }
-                } finally {
-                    closed = true;
-                }
-            } else {
-                closed = true;
-            }
-        }
-    }
-
-    @Override
-    public void commit() throws SQLException {
-        checkOpen();
-        try {
-            connection.commit();
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
-        checkOpen();
-        try {
-            return connection.createArrayOf(typeName, elements);
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public Blob createBlob() throws SQLException {
-        checkOpen();
-        try {
-            return connection.createBlob();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public Clob createClob() throws SQLException {
-        checkOpen();
-        try {
-            return connection.createClob();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public NClob createNClob() throws SQLException {
-        checkOpen();
-        try {
-            return connection.createNClob();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public SQLXML createSQLXML() throws SQLException {
-        checkOpen();
-        try {
-            return connection.createSQLXML();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public Statement createStatement() throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingStatement(this, connection.createStatement()));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingStatement(this, connection.createStatement(resultSetType, resultSetConcurrency)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public Statement createStatement(final int resultSetType, final int resultSetConcurrency,
-        final int resultSetHoldability) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingStatement(this,
-                connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
-        checkOpen();
-        try {
-            return connection.createStruct(typeName, attributes);
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public boolean getAutoCommit() throws SQLException {
-        checkOpen();
-        if (cacheState && cachedAutoCommit != null) {
-            return cachedAutoCommit;
-        }
-        try {
-            cachedAutoCommit = connection.getAutoCommit();
-            return cachedAutoCommit;
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    /**
-     * Returns the state caching flag.
-     *
-     * @return the state caching flag
-     */
-    public boolean getCacheState() {
-        return cacheState;
-    }
-
-    @Override
-    public String getCatalog() throws SQLException {
-        checkOpen();
-        if (cacheState && cachedCatalog != null) {
-            return cachedCatalog;
-        }
-        try {
-            cachedCatalog = connection.getCatalog();
-            return cachedCatalog;
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public Properties getClientInfo() throws SQLException {
-        checkOpen();
-        try {
-            return connection.getClientInfo();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public String getClientInfo(final String name) throws SQLException {
-        checkOpen();
-        try {
-            return connection.getClientInfo(name);
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    /**
-     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
-     * {@code null} means that the driver default will be used.
-     *
-     * @return query timeout limit in seconds; zero means there is no limit.
-     * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
-     */
-    @Deprecated
-    public Integer getDefaultQueryTimeout() {
-        return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds();
-    }
-
-    /**
-     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
-     * {@code null} means that the driver default will be used.
-     *
-     * @return query timeout limit; zero means there is no limit.
-     * @since 2.10.0
-     */
-    public Duration getDefaultQueryTimeoutDuration() {
-        return defaultQueryTimeoutDuration;
-    }
-
-    /**
-     * Returns my underlying {@link Connection}.
-     *
-     * @return my underlying {@link Connection}.
-     */
-    public C getDelegate() {
-        return getDelegateInternal();
-    }
-
-    /**
-     * Gets the delegate connection.
-     *
-     * @return the delegate connection.
-     */
-    protected final C getDelegateInternal() {
-        return connection;
-    }
-
-    @Override
-    public int getHoldability() throws SQLException {
-        checkOpen();
-        try {
-            return connection.getHoldability();
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    /**
-     * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively
-     * invokes this method on my delegate.
-     * <p>
-     * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when
-     * no non-{@code DelegatingConnection} delegate can be found by traversing this chain.
-     * </p>
-     * <p>
-     * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain
-     * a "genuine" {@link Connection}.
-     * </p>
-     *
-     * @return innermost delegate.
-     */
-    public Connection getInnermostDelegate() {
-        return getInnermostDelegateInternal();
-    }
-
-    /**
-     * Although this method is public, it is part of the internal API and should not be used by clients. The signature
-     * of this method may change at any time including in ways that break backwards compatibility.
-     *
-     * @return innermost delegate.
-     */
-    @SuppressWarnings("resource")
-    public final Connection getInnermostDelegateInternal() {
-        Connection conn = connection;
-        while (conn instanceof DelegatingConnection) {
-            conn = ((DelegatingConnection<?>) conn).getDelegateInternal();
-            if (this == conn) {
-                return null;
-            }
-        }
-        return conn;
-    }
-
-    @Override
-    public DatabaseMetaData getMetaData() throws SQLException {
-        checkOpen();
-        try {
-            return new DelegatingDatabaseMetaData(this, connection.getMetaData());
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public int getNetworkTimeout() throws SQLException {
-        checkOpen();
-        try {
-            return Jdbc41Bridge.getNetworkTimeout(connection);
-        } catch (final SQLException e) {
-            handleException(e);
-            return 0;
-        }
-    }
-
-    @Override
-    public String getSchema() throws SQLException {
-        checkOpen();
-        if (cacheState && cachedSchema != null) {
-            return cachedSchema;
-        }
-        try {
-            cachedSchema = Jdbc41Bridge.getSchema(connection);
-            return cachedSchema;
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public int getTransactionIsolation() throws SQLException {
-        checkOpen();
-        try {
-            return connection.getTransactionIsolation();
-        } catch (final SQLException e) {
-            handleException(e);
-            return -1;
-        }
-    }
-
-    @Override
-    public Map<String, Class<?>> getTypeMap() throws SQLException {
-        checkOpen();
-        try {
-            return connection.getTypeMap();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public SQLWarning getWarnings() throws SQLException {
-        checkOpen();
-        try {
-            return connection.getWarnings();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    /**
-     * Handles the given exception by throwing it.
-     *
-     * @param e the exception to throw.
-     * @throws SQLException the exception to throw.
-     */
-    protected void handleException(final SQLException e) throws SQLException {
-        throw e;
-    }
-
-    /**
-     * Handles the given {@code SQLException}.
-     *
-     * @param <T> The throwable type.
-     * @param e   The SQLException
-     * @return the given {@code SQLException}
-     * @since 2.7.0
-     */
-    protected <T extends Throwable> T handleExceptionNoThrow(final T e) {
-        return e;
-    }
-
-    /**
-     * Initializes the given statement with this connection's settings.
-     *
-     * @param <T> The DelegatingStatement type.
-     * @param delegatingStatement The DelegatingStatement to initialize.
-     * @return The given DelegatingStatement.
-     * @throws SQLException if a database access error occurs, this method is called on a closed Statement.
-     */
-    private <T extends DelegatingStatement> T init(final T delegatingStatement) throws SQLException {
-        if (defaultQueryTimeoutDuration != null && defaultQueryTimeoutDuration.getSeconds() != delegatingStatement.getQueryTimeout()) {
-            delegatingStatement.setQueryTimeout((int) defaultQueryTimeoutDuration.getSeconds());
-        }
-        return delegatingStatement;
-    }
-
-    /**
-     * Compares innermost delegate to the given connection.
-     *
-     * @param c
-     *            connection to compare innermost delegate with
-     * @return true if innermost delegate equals {@code c}
-     */
-    @SuppressWarnings("resource")
-    public boolean innermostDelegateEquals(final Connection c) {
-        final Connection innerCon = getInnermostDelegateInternal();
-        if (innerCon == null) {
-            return c == null;
-        }
-        return innerCon.equals(c);
-    }
-
-    @Override
-    public boolean isClosed() throws SQLException {
-        return closed || connection == null || connection.isClosed();
-    }
-
-    protected boolean isClosedInternal() {
-        return closed;
-    }
-
-    @Override
-    public boolean isReadOnly() throws SQLException {
-        checkOpen();
-        if (cacheState && cachedReadOnly != null) {
-            return cachedReadOnly;
-        }
-        try {
-            cachedReadOnly = connection.isReadOnly();
-            return cachedReadOnly;
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    /**
-     * Tests if the connection has not been closed and is still valid.
-     *
-     * @param timeout The duration to wait for the database operation used to validate the connection to complete.
-     * @return See {@link Connection#isValid(int)}.
-     * @throws SQLException See {@link Connection#isValid(int)}.
-     * @see Connection#isValid(int)
-     * @since 2.10.0
-     */
-    public boolean isValid(final Duration timeout) throws SQLException {
-        if (isClosed()) {
-            return false;
-        }
-        try {
-            return connection.isValid((int) timeout.getSeconds());
-        } catch (final SQLException e) {
-            handleException(e);
-            return false;
-        }
-    }
-
-    /**
-     * @deprecated Use {@link #isValid(Duration)}.
-     */
-    @Override
-    @Deprecated
-    public boolean isValid(final int timeoutSeconds) throws SQLException {
-        return isValid(Duration.ofSeconds(timeoutSeconds));
-    }
-
-    @Override
-    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
-        if (iface.isAssignableFrom(getClass())) {
-            return true;
-        }
-        if (iface.isAssignableFrom(connection.getClass())) {
-            return true;
-        }
-        return connection.isWrapperFor(iface);
-    }
-
-    @Override
-    public String nativeSQL(final String sql) throws SQLException {
-        checkOpen();
-        try {
-            return connection.nativeSQL(sql);
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    protected void passivate() throws SQLException {
-        // The JDBC specification requires that a Connection close any open
-        // Statement's when it is closed.
-        // DBCP-288. Not all the traced objects will be statements
-        final List<AbandonedTrace> traces = getTrace();
-        if (traces != null && !traces.isEmpty()) {
-            final List<Exception> thrownList = new ArrayList<>();
-            for (final Object trace : traces) {
-                if (trace instanceof Statement) {
-                    try {
-                        ((Statement) trace).close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
-                } else if (trace instanceof ResultSet) {
-                    // DBCP-265: Need to close the result sets that are
-                    // generated via DatabaseMetaData
-                    try {
-                        ((ResultSet) trace).close();
-                    } catch (final Exception e) {
-                        thrownList.add(e);
-                    }
-                }
-            }
-            clearTrace();
-            if (!thrownList.isEmpty()) {
-                throw new SQLExceptionList(thrownList);
-            }
-        }
-        setLastUsed(Instant.EPOCH);
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public CallableStatement prepareCall(final String sql) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingCallableStatement(this, connection.prepareCall(sql)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
-        throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingCallableStatement(this,
-                connection.prepareCall(sql, resultSetType, resultSetConcurrency)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
-        final int resultSetHoldability) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingCallableStatement(this,
-                connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public PreparedStatement prepareStatement(final String sql) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, autoGeneratedKeys)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
-        throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingPreparedStatement(this,
-                connection.prepareStatement(sql, resultSetType, resultSetConcurrency)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
-        final int resultSetHoldability) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingPreparedStatement(this,
-                connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnIndexes)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
-        checkOpen();
-        try {
-            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnNames)));
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
-        checkOpen();
-        try {
-            connection.releaseSavepoint(savepoint);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void rollback() throws SQLException {
-        checkOpen();
-        try {
-            connection.rollback();
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void rollback(final Savepoint savepoint) throws SQLException {
-        checkOpen();
-        try {
-            connection.rollback(savepoint);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setAutoCommit(final boolean autoCommit) throws SQLException {
-        checkOpen();
-        try {
-            connection.setAutoCommit(autoCommit);
-            if (cacheState) {
-                cachedAutoCommit = connection.getAutoCommit();
-            }
-        } catch (final SQLException e) {
-            cachedAutoCommit = null;
-            handleException(e);
-        }
-    }
-
-    /**
-     * Sets the state caching flag.
-     *
-     * @param cacheState
-     *            The new value for the state caching flag
-     */
-    public void setCacheState(final boolean cacheState) {
-        this.cacheState = cacheState;
-    }
-
-    @Override
-    public void setCatalog(final String catalog) throws SQLException {
-        checkOpen();
-        try {
-            connection.setCatalog(catalog);
-            if (cacheState) {
-                cachedCatalog = connection.getCatalog();
-            }
-        } catch (final SQLException e) {
-            cachedCatalog = null;
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setClientInfo(final Properties properties) throws SQLClientInfoException {
-        try {
-            checkOpen();
-            connection.setClientInfo(properties);
-        } catch (final SQLClientInfoException e) {
-            throw e;
-        } catch (final SQLException e) {
-            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
-        }
-    }
-
-    @Override
-    public void setClientInfo(final String name, final String value) throws SQLClientInfoException {
-        try {
-            checkOpen();
-            connection.setClientInfo(name, value);
-        } catch (final SQLClientInfoException e) {
-            throw e;
-        } catch (final SQLException e) {
-            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
-        }
-    }
-
-    protected void setClosedInternal(final boolean closed) {
-        this.closed = closed;
-    }
-
-    /**
-     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
-     * {@code null} means that the driver default will be used.
-     *
-     * @param defaultQueryTimeoutDuration
-     *            the new query timeout limit Duration; zero means there is no limit.
-     * @since 2.10.0
-     */
-    public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) {
-        this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration;
-    }
-
-    /**
-     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
-     * {@code null} means that the driver default will be used.
-     *
-     * @param defaultQueryTimeoutSeconds
-     *            the new query timeout limit in seconds; zero means there is no limit.
-     * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}.
-     */
-    @Deprecated
-    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
-        this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds);
-    }
-
-    /**
-     * Sets my delegate.
-     *
-     * @param connection
-     *            my delegate.
-     */
-    public void setDelegate(final C connection) {
-        this.connection = connection;
-    }
-
-    @Override
-    public void setHoldability(final int holdability) throws SQLException {
-        checkOpen();
-        try {
-            connection.setHoldability(holdability);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException {
-        checkOpen();
-        try {
-            Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setReadOnly(final boolean readOnly) throws SQLException {
-        checkOpen();
-        try {
-            connection.setReadOnly(readOnly);
-            if (cacheState) {
-                cachedReadOnly = connection.isReadOnly();
-            }
-        } catch (final SQLException e) {
-            cachedReadOnly = null;
-            handleException(e);
-        }
-    }
-
-    @Override
-    public Savepoint setSavepoint() throws SQLException {
-        checkOpen();
-        try {
-            return connection.setSavepoint();
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public Savepoint setSavepoint(final String name) throws SQLException {
-        checkOpen();
-        try {
-            return connection.setSavepoint(name);
-        } catch (final SQLException e) {
-            handleException(e);
-            return null;
-        }
-    }
-
-    @Override
-    public void setSchema(final String schema) throws SQLException {
-        checkOpen();
-        try {
-            Jdbc41Bridge.setSchema(connection, schema);
-            if (cacheState) {
-                cachedSchema = connection.getSchema();
-            }
-        } catch (final SQLException e) {
-            cachedSchema = null;
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setTransactionIsolation(final int level) throws SQLException {
-        checkOpen();
-        try {
-            connection.setTransactionIsolation(level);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    @Override
-    public void setTypeMap(final Map<String, Class<?>> map) throws SQLException {
-        checkOpen();
-        try {
-            connection.setTypeMap(map);
-        } catch (final SQLException e) {
-            handleException(e);
-        }
-    }
-
-    /**
-     * Returns a string representation of the metadata associated with the innermost delegate connection.
-     */
-    @SuppressWarnings("resource")
-    @Override
-    public synchronized String toString() {
-        String str = null;
-
-        final Connection conn = this.getInnermostDelegateInternal();
-        if (conn != null) {
-            try {
-                if (conn.isClosed()) {
-                    str = "connection is closed";
-                } else {
-                    final StringBuilder sb = new StringBuilder();
-                    sb.append(hashCode());
-                    final DatabaseMetaData meta = conn.getMetaData();
-                    if (meta != null) {
-                        sb.append(", URL=");
-                        sb.append(meta.getURL());
-                        sb.append(", ");
-                        sb.append(meta.getDriverName());
-                        str = sb.toString();
-                    }
-                }
-            } catch (final SQLException ex) {
-                // Ignore
-            }
-        }
-        return str != null ? str : super.toString();
-    }
-
-    @Override
-    public <T> T unwrap(final Class<T> iface) throws SQLException {
-        if (iface.isAssignableFrom(getClass())) {
-            return iface.cast(this);
-        }
-        if (iface.isAssignableFrom(connection.getClass())) {
-            return iface.cast(connection);
-        }
-        return connection.unwrap(iface);
-    }
-}
+/*
+ * 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;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.ClientInfoStatus;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+/**
+ * A base delegating implementation of {@link Connection}.
+ * <p>
+ * All of the methods from the {@link Connection} interface simply check to see that the {@link Connection} is active,
+ * and call the corresponding method on the "delegate" provided in my constructor.
+ * </p>
+ * <p>
+ * Extends AbandonedTrace to implement Connection tracking and logging of code which created the Connection. Tracking
+ * the Connection ensures that the AbandonedObjectPool can close this connection and recycle it if its pool of
+ * connections is nearing exhaustion and this connection's last usage is older than the removeAbandonedTimeout.
+ * </p>
+ *
+ * @param <C>
+ *            the Connection type
+ *
+ * @since 2.0
+ */
+public class DelegatingConnection<C extends Connection> extends AbandonedTrace implements Connection {
+
+    private static final Map<String, ClientInfoStatus> EMPTY_FAILED_PROPERTIES = Collections
+            .<String, ClientInfoStatus>emptyMap();
+
+    /** My delegate {@link Connection}. */
+    private volatile C connection;
+
+    private volatile boolean closed;
+
+    private boolean cacheState = true;
+    private Boolean cachedAutoCommit;
+    private Boolean cachedReadOnly;
+    private String cachedCatalog;
+    private String cachedSchema;
+    private Duration defaultQueryTimeoutDuration;
+
+    /**
+     * Creates a wrapper for the Connection which traces this Connection in the AbandonedObjectPool.
+     *
+     * @param connection the {@link Connection} to delegate all calls to.
+     */
+    public DelegatingConnection(final C connection) {
+        this.connection = connection;
+    }
+
+    @Override
+    public void abort(final Executor executor) throws SQLException {
+        try {
+            Jdbc41Bridge.abort(connection, executor);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    protected void activate() {
+        closed = false;
+        setLastUsed();
+        if (connection instanceof DelegatingConnection) {
+            ((DelegatingConnection<?>) connection).activate();
+        }
+    }
+
+    protected void checkOpen() throws SQLException {
+        if (closed) {
+            if (null != connection) {
+                String label = "";
+                try {
+                    label = connection.toString();
+                } catch (final Exception ignored) {
+                    // ignore, leave label empty
+                }
+                throw new SQLException("Connection " + label + " is closed.");
+            }
+            throw new SQLException("Connection is null.");
+        }
+    }
+
+    /**
+     * Clears the cached state. Call when you known that the underlying connection may have been accessed
+     * directly.
+     */
+    public void clearCachedState() {
+        cachedAutoCommit = null;
+        cachedReadOnly = null;
+        cachedSchema = null;
+        cachedCatalog = null;
+        if (connection instanceof DelegatingConnection) {
+            ((DelegatingConnection<?>) connection).clearCachedState();
+        }
+    }
+
+    @Override
+    public void clearWarnings() throws SQLException {
+        checkOpen();
+        try {
+            connection.clearWarnings();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    /**
+     * Closes the underlying connection, and close any Statements that were not explicitly closed. Sub-classes that
+     * override this method must:
+     * <ol>
+     * <li>Call {@link #passivate()}</li>
+     * <li>Call close (or the equivalent appropriate action) on the wrapped connection</li>
+     * <li>Set {@code closed} to {@code false}</li>
+     * </ol>
+     */
+    @Override
+    public void close() throws SQLException {
+        if (!closed) {
+            closeInternal();
+        }
+    }
+
+    protected final void closeInternal() throws SQLException {
+        try {
+            passivate();
+        } finally {
+            if (connection != null) {
+                boolean connectionIsClosed;
+                try {
+                    connectionIsClosed = connection.isClosed();
+                } catch (final SQLException e) {
+                    // not sure what the state is, so assume the connection is open.
+                    connectionIsClosed = false;
+                }
+                try {
+                    // DBCP-512: Avoid exceptions when closing a connection in mutli-threaded use case.
+                    // Avoid closing again, which should be a no-op, but some drivers like H2 throw an exception when
+                    // closing from multiple threads.
+                    if (!connectionIsClosed) {
+                        connection.close();
+                    }
+                } finally {
+                    closed = true;
+                }
+            } else {
+                closed = true;
+            }
+        }
+    }
+
+    @Override
+    public void commit() throws SQLException {
+        checkOpen();
+        try {
+            connection.commit();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
+        checkOpen();
+        try {
+            return connection.createArrayOf(typeName, elements);
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public Blob createBlob() throws SQLException {
+        checkOpen();
+        try {
+            return connection.createBlob();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public Clob createClob() throws SQLException {
+        checkOpen();
+        try {
+            return connection.createClob();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public NClob createNClob() throws SQLException {
+        checkOpen();
+        try {
+            return connection.createNClob();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public SQLXML createSQLXML() throws SQLException {
+        checkOpen();
+        try {
+            return connection.createSQLXML();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public Statement createStatement() throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingStatement(this, connection.createStatement()));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingStatement(this, connection.createStatement(resultSetType, resultSetConcurrency)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public Statement createStatement(final int resultSetType, final int resultSetConcurrency,
+        final int resultSetHoldability) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingStatement(this,
+                connection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public Struct createStruct(final String typeName, final Object[] attributes) throws SQLException {
+        checkOpen();
+        try {
+            return connection.createStruct(typeName, attributes);
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public boolean getAutoCommit() throws SQLException {
+        checkOpen();
+        if (cacheState && cachedAutoCommit != null) {
+            return cachedAutoCommit;
+        }
+        try {
+            cachedAutoCommit = connection.getAutoCommit();
+            return cachedAutoCommit;
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns the state caching flag.
+     *
+     * @return the state caching flag
+     */
+    public boolean getCacheState() {
+        return cacheState;
+    }
+
+    @Override
+    public String getCatalog() throws SQLException {
+        checkOpen();
+        if (cacheState && cachedCatalog != null) {
+            return cachedCatalog;
+        }
+        try {
+            cachedCatalog = connection.getCatalog();
+            return cachedCatalog;
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public Properties getClientInfo() throws SQLException {
+        checkOpen();
+        try {
+            return connection.getClientInfo();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public String getClientInfo(final String name) throws SQLException {
+        checkOpen();
+        try {
+            return connection.getClientInfo(name);
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    /**
+     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * {@code null} means that the driver default will be used.
+     *
+     * @return query timeout limit in seconds; zero means there is no limit.
+     * @deprecated Use {@link #getDefaultQueryTimeoutDuration()}.
+     */
+    @Deprecated
+    public Integer getDefaultQueryTimeout() {
+        return defaultQueryTimeoutDuration == null ? null : (int) defaultQueryTimeoutDuration.getSeconds();
+    }
+
+    /**
+     * Gets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * {@code null} means that the driver default will be used.
+     *
+     * @return query timeout limit; zero means there is no limit.
+     * @since 2.10.0
+     */
+    public Duration getDefaultQueryTimeoutDuration() {
+        return defaultQueryTimeoutDuration;
+    }
+
+    /**
+     * Returns my underlying {@link Connection}.
+     *
+     * @return my underlying {@link Connection}.
+     */
+    public C getDelegate() {
+        return getDelegateInternal();
+    }
+
+    /**
+     * Gets the delegate connection.
+     *
+     * @return the delegate connection.
+     */
+    protected final C getDelegateInternal() {
+        return connection;
+    }
+
+    @Override
+    public int getHoldability() throws SQLException {
+        checkOpen();
+        try {
+            return connection.getHoldability();
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    /**
+     * If my underlying {@link Connection} is not a {@code DelegatingConnection}, returns it, otherwise recursively
+     * invokes this method on my delegate.
+     * <p>
+     * Hence this method will return the first delegate that is not a {@code DelegatingConnection}, or {@code null} when
+     * no non-{@code DelegatingConnection} delegate can be found by traversing this chain.
+     * </p>
+     * <p>
+     * This method is useful when you may have nested {@code DelegatingConnection}s, and you want to make sure to obtain
+     * a "genuine" {@link Connection}.
+     * </p>
+     *
+     * @return innermost delegate.
+     */
+    public Connection getInnermostDelegate() {
+        return getInnermostDelegateInternal();
+    }
+
+    /**
+     * Although this method is public, it is part of the internal API and should not be used by clients. The signature
+     * of this method may change at any time including in ways that break backwards compatibility.
+     *
+     * @return innermost delegate.
+     */
+    @SuppressWarnings("resource")
+    public final Connection getInnermostDelegateInternal() {
+        Connection conn = connection;
+        while (conn instanceof DelegatingConnection) {
+            conn = ((DelegatingConnection<?>) conn).getDelegateInternal();
+            if (this == conn) {
+                return null;
+            }
+        }
+        return conn;
+    }
+
+    @Override
+    public DatabaseMetaData getMetaData() throws SQLException {
+        checkOpen();
+        try {
+            return new DelegatingDatabaseMetaData(this, connection.getMetaData());
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public int getNetworkTimeout() throws SQLException {
+        checkOpen();
+        try {
+            return Jdbc41Bridge.getNetworkTimeout(connection);
+        } catch (final SQLException e) {
+            handleException(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public String getSchema() throws SQLException {
+        checkOpen();
+        if (cacheState && cachedSchema != null) {
+            return cachedSchema;
+        }
+        try {
+            cachedSchema = Jdbc41Bridge.getSchema(connection);
+            return cachedSchema;
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public int getTransactionIsolation() throws SQLException {
+        checkOpen();
+        try {
+            return connection.getTransactionIsolation();
+        } catch (final SQLException e) {
+            handleException(e);
+            return -1;
+        }
+    }
+
+    @Override
+    public Map<String, Class<?>> getTypeMap() throws SQLException {
+        checkOpen();
+        try {
+            return connection.getTypeMap();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        checkOpen();
+        try {
+            return connection.getWarnings();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    /**
+     * Handles the given exception by throwing it.
+     *
+     * @param e the exception to throw.
+     * @throws SQLException the exception to throw.
+     */
+    protected void handleException(final SQLException e) throws SQLException {
+        throw e;
+    }
+
+    /**
+     * Handles the given {@code SQLException}.
+     *
+     * @param <T> The throwable type.
+     * @param e   The SQLException
+     * @return the given {@code SQLException}
+     * @since 2.7.0
+     */
+    protected <T extends Throwable> T handleExceptionNoThrow(final T e) {
+        return e;
+    }
+
+    /**
+     * Initializes the given statement with this connection's settings.
+     *
+     * @param <T> The DelegatingStatement type.
+     * @param delegatingStatement The DelegatingStatement to initialize.
+     * @return The given DelegatingStatement.
+     * @throws SQLException if a database access error occurs, this method is called on a closed Statement.
+     */
+    private <T extends DelegatingStatement> T init(final T delegatingStatement) throws SQLException {
+        if (defaultQueryTimeoutDuration != null && defaultQueryTimeoutDuration.getSeconds() != delegatingStatement.getQueryTimeout()) {
+            delegatingStatement.setQueryTimeout((int) defaultQueryTimeoutDuration.getSeconds());
+        }
+        return delegatingStatement;
+    }
+
+    /**
+     * Compares innermost delegate to the given connection.
+     *
+     * @param c
+     *            connection to compare innermost delegate with
+     * @return true if innermost delegate equals {@code c}
+     */
+    @SuppressWarnings("resource")
+    public boolean innermostDelegateEquals(final Connection c) {
+        final Connection innerCon = getInnermostDelegateInternal();
+        if (innerCon == null) {
+            return c == null;
+        }
+        return innerCon.equals(c);
+    }
+
+    @Override
+    public boolean isClosed() throws SQLException {
+        return closed || connection == null || connection.isClosed();
+    }
+
+    protected boolean isClosedInternal() {
+        return closed;
+    }
+
+    @Override
+    public boolean isReadOnly() throws SQLException {
+        checkOpen();
+        if (cacheState && cachedReadOnly != null) {
+            return cachedReadOnly;
+        }
+        try {
+            cachedReadOnly = connection.isReadOnly();
+            return cachedReadOnly;
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    /**
+     * Tests if the connection has not been closed and is still valid.
+     *
+     * @param timeout The duration to wait for the database operation used to validate the connection to complete.
+     * @return See {@link Connection#isValid(int)}.
+     * @throws SQLException See {@link Connection#isValid(int)}.
+     * @see Connection#isValid(int)
+     * @since 2.10.0
+     */
+    public boolean isValid(final Duration timeout) throws SQLException {
+        if (isClosed()) {
+            return false;
+        }
+        try {
+            return connection.isValid((int) timeout.getSeconds());
+        } catch (final SQLException e) {
+            handleException(e);
+            return false;
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #isValid(Duration)}.
+     */
+    @Override
+    @Deprecated
+    public boolean isValid(final int timeoutSeconds) throws SQLException {
+        return isValid(Duration.ofSeconds(timeoutSeconds));
+    }
+
+    @Override
+    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+        if (iface.isAssignableFrom(getClass())) {
+            return true;
+        }
+        if (iface.isAssignableFrom(connection.getClass())) {
+            return true;
+        }
+        return connection.isWrapperFor(iface);
+    }
+
+    @Override
+    public String nativeSQL(final String sql) throws SQLException {
+        checkOpen();
+        try {
+            return connection.nativeSQL(sql);
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    protected void passivate() throws SQLException {
+        // The JDBC specification requires that a Connection close any open
+        // Statement's when it is closed.
+        // DBCP-288. Not all the traced objects will be statements
+        final List<AbandonedTrace> traces = getTrace();
+        if (traces != null && !traces.isEmpty()) {
+            final List<Exception> thrownList = new ArrayList<>();
+            for (final Object trace : traces) {
+                if (trace instanceof Statement) {
+                    try {
+                        ((Statement) trace).close();
+                    } catch (final Exception e) {
+                        thrownList.add(e);
+                    }
+                } else if (trace instanceof ResultSet) {
+                    // DBCP-265: Need to close the result sets that are
+                    // generated via DatabaseMetaData
+                    try {
+                        ((ResultSet) trace).close();
+                    } catch (final Exception e) {
+                        thrownList.add(e);
+                    }
+                }
+            }
+            clearTrace();
+            if (!thrownList.isEmpty()) {
+                throw new SQLExceptionList(thrownList);
+            }
+        }
+        setLastUsed(Instant.EPOCH);
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public CallableStatement prepareCall(final String sql) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingCallableStatement(this, connection.prepareCall(sql)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
+        throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingCallableStatement(this,
+                connection.prepareCall(sql, resultSetType, resultSetConcurrency)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
+        final int resultSetHoldability) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingCallableStatement(this,
+                connection.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public PreparedStatement prepareStatement(final String sql) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, autoGeneratedKeys)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
+        throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingPreparedStatement(this,
+                connection.prepareStatement(sql, resultSetType, resultSetConcurrency)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
+        final int resultSetHoldability) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingPreparedStatement(this,
+                connection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnIndexes)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @SuppressWarnings("resource") // Caller is responsible for closing the resource.
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
+        checkOpen();
+        try {
+            return init(new DelegatingPreparedStatement(this, connection.prepareStatement(sql, columnNames)));
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public void releaseSavepoint(final Savepoint savepoint) throws SQLException {
+        checkOpen();
+        try {
+            connection.releaseSavepoint(savepoint);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void rollback() throws SQLException {
+        checkOpen();
+        try {
+            connection.rollback();
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void rollback(final Savepoint savepoint) throws SQLException {
+        checkOpen();
+        try {
+            connection.rollback(savepoint);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setAutoCommit(final boolean autoCommit) throws SQLException {
+        checkOpen();
+        try {
+            connection.setAutoCommit(autoCommit);
+            if (cacheState) {
+                cachedAutoCommit = connection.getAutoCommit();
+            }
+        } catch (final SQLException e) {
+            cachedAutoCommit = null;
+            handleException(e);
+        }
+    }
+
+    /**
+     * Sets the state caching flag.
+     *
+     * @param cacheState
+     *            The new value for the state caching flag
+     */
+    public void setCacheState(final boolean cacheState) {
+        this.cacheState = cacheState;
+    }
+
+    @Override
+    public void setCatalog(final String catalog) throws SQLException {
+        checkOpen();
+        try {
+            connection.setCatalog(catalog);
+            if (cacheState) {
+                cachedCatalog = connection.getCatalog();
+            }
+        } catch (final SQLException e) {
+            cachedCatalog = null;
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setClientInfo(final Properties properties) throws SQLClientInfoException {
+        try {
+            checkOpen();
+            connection.setClientInfo(properties);
+        } catch (final SQLClientInfoException e) {
+            throw e;
+        } catch (final SQLException e) {
+            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
+        }
+    }
+
+    @Override
+    public void setClientInfo(final String name, final String value) throws SQLClientInfoException {
+        try {
+            checkOpen();
+            connection.setClientInfo(name, value);
+        } catch (final SQLClientInfoException e) {
+            throw e;
+        } catch (final SQLException e) {
+            throw new SQLClientInfoException("Connection is closed.", EMPTY_FAILED_PROPERTIES, e);
+        }
+    }
+
+    protected void setClosedInternal(final boolean closed) {
+        this.closed = closed;
+    }
+
+    /**
+     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * {@code null} means that the driver default will be used.
+     *
+     * @param defaultQueryTimeoutDuration
+     *            the new query timeout limit Duration; zero means there is no limit.
+     * @since 2.10.0
+     */
+    public void setDefaultQueryTimeout(final Duration defaultQueryTimeoutDuration) {
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutDuration;
+    }
+
+    /**
+     * Sets the default query timeout that will be used for {@link Statement}s created from this connection.
+     * {@code null} means that the driver default will be used.
+     *
+     * @param defaultQueryTimeoutSeconds
+     *            the new query timeout limit in seconds; zero means there is no limit.
+     * @deprecated Use {@link #setDefaultQueryTimeout(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultQueryTimeout(final Integer defaultQueryTimeoutSeconds) {
+        this.defaultQueryTimeoutDuration = defaultQueryTimeoutSeconds == null ? null : Duration.ofSeconds(defaultQueryTimeoutSeconds);
+    }
+
+    /**
+     * Sets my delegate.
+     *
+     * @param connection
+     *            my delegate.
+     */
+    public void setDelegate(final C connection) {
+        this.connection = connection;
+    }
+
+    @Override
+    public void setHoldability(final int holdability) throws SQLException {
+        checkOpen();
+        try {
+            connection.setHoldability(holdability);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setNetworkTimeout(final Executor executor, final int milliseconds) throws SQLException {
+        checkOpen();
+        try {
+            Jdbc41Bridge.setNetworkTimeout(connection, executor, milliseconds);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setReadOnly(final boolean readOnly) throws SQLException {
+        checkOpen();
+        try {
+            connection.setReadOnly(readOnly);
+            if (cacheState) {
+                cachedReadOnly = connection.isReadOnly();
+            }
+        } catch (final SQLException e) {
+            cachedReadOnly = null;
+            handleException(e);
+        }
+    }
+
+    @Override
+    public Savepoint setSavepoint() throws SQLException {
+        checkOpen();
+        try {
+            return connection.setSavepoint();
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public Savepoint setSavepoint(final String name) throws SQLException {
+        checkOpen();
+        try {
+            return connection.setSavepoint(name);
+        } catch (final SQLException e) {
+            handleException(e);
+            return null;
+        }
+    }
+
+    @Override
+    public void setSchema(final String schema) throws SQLException {
+        checkOpen();
+        try {
+            Jdbc41Bridge.setSchema(connection, schema);
+            if (cacheState) {
+                cachedSchema = connection.getSchema();
+            }
+        } catch (final SQLException e) {
+            cachedSchema = null;
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setTransactionIsolation(final int level) throws SQLException {
+        checkOpen();
+        try {
+            connection.setTransactionIsolation(level);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    @Override
+    public void setTypeMap(final Map<String, Class<?>> map) throws SQLException {
+        checkOpen();
+        try {
+            connection.setTypeMap(map);
+        } catch (final SQLException e) {
+            handleException(e);
+        }
+    }
+
+    /**
+     * Returns a string representation of the metadata associated with the innermost delegate connection.
+     */
+    @SuppressWarnings("resource")
+    @Override
+    public synchronized String toString() {
+        String str = null;
+
+        final Connection conn = this.getInnermostDelegateInternal();
+        if (conn != null) {
+            try {
+                if (conn.isClosed()) {
+                    str = "connection is closed";
+                } else {
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append(hashCode());
+                    final DatabaseMetaData meta = conn.getMetaData();
+                    if (meta != null) {
+                        sb.append(", URL=");
+                        sb.append(meta.getURL());
+                        sb.append(", ");
+                        sb.append(meta.getDriverName());
+                        str = sb.toString();
+                    }
+                }
+            } catch (final SQLException ignored) {
+                // Ignore
+            }
+        }
+        return str != null ? str : super.toString();
+    }
+
+    @Override
+    public <T> T unwrap(final Class<T> iface) throws SQLException {
+        if (iface.isAssignableFrom(getClass())) {
+            return iface.cast(this);
+        }
+        if (iface.isAssignableFrom(connection.getClass())) {
+            return iface.cast(connection);
+        }
+        return connection.unwrap(iface);
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java b/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java
index 1dd75476..3ad0e110 100644
--- a/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java
+++ b/src/main/java/org/apache/commons/dbcp2/Jdbc41Bridge.java
@@ -1,488 +1,488 @@
-/*
- * 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;
-
-import java.io.InputStream;
-import java.io.Reader;
-import java.math.BigDecimal;
-import java.net.URL;
-import java.sql.Array;
-import java.sql.Blob;
-import java.sql.Clob;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.Date;
-import java.sql.Ref;
-import java.sql.ResultSet;
-import java.sql.RowId;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.sql.SQLXML;
-import java.sql.Statement;
-import java.sql.Time;
-import java.sql.Timestamp;
-import java.util.concurrent.Executor;
-import java.util.logging.Logger;
-
-import javax.sql.CommonDataSource;
-
-/**
- * Defines bridge methods to JDBC 4.1 (Java 7) methods to allow call sites to operate safely (without
- * {@link AbstractMethodError}) when using a JDBC driver written for JDBC 4.0 (Java 6).
- * <p>
- * There should be no need to this kind of code for JDBC 4.2 in Java 8 due to JDBC's use of default methods.
- * </p>
- * <p>
- * This should probably be moved or at least copied in some form to Apache Commons DbUtils.
- * </p>
- *
- * @since 2.6.0
- */
-public class Jdbc41Bridge {
-
-    /**
-     * Delegates to {@link Connection#abort(Executor)} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Connection#abort(Executor)}, then call {@link Connection#close()}.
-     * </p>
-     *
-     * @param connection
-     *            the receiver
-     * @param executor
-     *            See {@link Connection#abort(Executor)}.
-     * @throws SQLException
-     *             See {@link Connection#abort(Executor)}.
-     * @see Connection#abort(Executor)
-     */
-    public static void abort(final Connection connection, final Executor executor) throws SQLException {
-        try {
-            connection.abort(executor);
-        } catch (final AbstractMethodError e) {
-            connection.close();
-        }
-    }
-
-    /**
-     * Delegates to {@link Statement#closeOnCompletion()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Statement#closeOnCompletion()}, then just check that the connection
-     * is closed to then throw an SQLException.
-     * </p>
-     *
-     * @param statement
-     *            See {@link Statement#closeOnCompletion()}
-     * @throws SQLException
-     *             See {@link Statement#closeOnCompletion()}
-     * @see Statement#closeOnCompletion()
-     */
-    public static void closeOnCompletion(final Statement statement) throws SQLException {
-        try {
-            statement.closeOnCompletion();
-        } catch (final AbstractMethodError e) {
-            if (statement.isClosed()) {
-                throw new SQLException("Statement closed");
-            }
-        }
-    }
-
-    /**
-     * Delegates to {@link DatabaseMetaData#generatedKeyAlwaysReturned()} without throwing a
-     * {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link DatabaseMetaData#generatedKeyAlwaysReturned()}, then return false.
-     * </p>
-     *
-     * @param databaseMetaData
-     *            See {@link DatabaseMetaData#generatedKeyAlwaysReturned()}
-     * @return See {@link DatabaseMetaData#generatedKeyAlwaysReturned()}
-     * @throws SQLException
-     *             See {@link DatabaseMetaData#generatedKeyAlwaysReturned()}
-     * @see DatabaseMetaData#generatedKeyAlwaysReturned()
-     */
-    public static boolean generatedKeyAlwaysReturned(final DatabaseMetaData databaseMetaData) throws SQLException {
-        try {
-            return databaseMetaData.generatedKeyAlwaysReturned();
-        } catch (final AbstractMethodError e) {
-            // do nothing
-            return false;
-        }
-    }
-
-    /**
-     * Delegates to {@link Connection#getNetworkTimeout()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Connection#getNetworkTimeout()}, then return 0.
-     * </p>
-     *
-     * @param connection
-     *            the receiver
-     * @return See {@link Connection#getNetworkTimeout()}
-     * @throws SQLException
-     *             See {@link Connection#getNetworkTimeout()}
-     * @see Connection#getNetworkTimeout()
-     */
-    public static int getNetworkTimeout(final Connection connection) throws SQLException {
-        try {
-            return connection.getNetworkTimeout();
-        } catch (final AbstractMethodError e) {
-            return 0;
-        }
-    }
-
-    /**
-     * Delegates to {@link ResultSet#getObject(int, Class)} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link ResultSet#getObject(int, Class)}, then return 0.
-     * </p>
-     *
-     * @param <T>
-     *            See {@link ResultSet#getObject(int, Class)}
-     * @param resultSet
-     *            See {@link ResultSet#getObject(int, Class)}
-     * @param columnIndex
-     *            See {@link ResultSet#getObject(int, Class)}
-     * @param type
-     *            See {@link ResultSet#getObject(int, Class)}
-     * @return See {@link ResultSet#getObject(int, Class)}
-     * @throws SQLException
-     *             See {@link ResultSet#getObject(int, Class)}
-     * @see ResultSet#getObject(int, Class)
-     */
-    @SuppressWarnings("unchecked")
-    public static <T> T getObject(final ResultSet resultSet, final int columnIndex, final Class<T> type)
-            throws SQLException {
-        try {
-            return resultSet.getObject(columnIndex, type);
-        } catch (final AbstractMethodError e) {
-            if (type == String.class) {
-                return (T) resultSet.getString(columnIndex);
-            }
-            // Numbers
-            if (type == Integer.class) {
-                return (T) Integer.valueOf(resultSet.getInt(columnIndex));
-            }
-            if (type == Long.class) {
-                return (T) Long.valueOf(resultSet.getLong(columnIndex));
-            }
-            if (type == Double.class) {
-                return (T) Double.valueOf(resultSet.getDouble(columnIndex));
-            }
-            if (type == Float.class) {
-                return (T) Float.valueOf(resultSet.getFloat(columnIndex));
-            }
-            if (type == Short.class) {
-                return (T) Short.valueOf(resultSet.getShort(columnIndex));
-            }
-            if (type == BigDecimal.class) {
-                return (T) resultSet.getBigDecimal(columnIndex);
-            }
-            if (type == Byte.class) {
-                return (T) Byte.valueOf(resultSet.getByte(columnIndex));
-            }
-            // Dates
-            if (type == Date.class) {
-                return (T) resultSet.getDate(columnIndex);
-            }
-            if (type == Time.class) {
-                return (T) resultSet.getTime(columnIndex);
-            }
-            if (type == Timestamp.class) {
-                return (T) resultSet.getTimestamp(columnIndex);
-            }
-            // Streams
-            if (type == InputStream.class) {
-                return (T) resultSet.getBinaryStream(columnIndex);
-            }
-            if (type == Reader.class) {
-                return (T) resultSet.getCharacterStream(columnIndex);
-            }
-            // Other
-            if (type == Object.class) {
-                return (T) resultSet.getObject(columnIndex);
-            }
-            if (type == Boolean.class) {
-                return (T) Boolean.valueOf(resultSet.getBoolean(columnIndex));
-            }
-            if (type == Array.class) {
-                return (T) resultSet.getArray(columnIndex);
-            }
-            if (type == Blob.class) {
-                return (T) resultSet.getBlob(columnIndex);
-            }
-            if (type == Clob.class) {
-                return (T) resultSet.getClob(columnIndex);
-            }
-            if (type == Ref.class) {
-                return (T) resultSet.getRef(columnIndex);
-            }
-            if (type == RowId.class) {
-                return (T) resultSet.getRowId(columnIndex);
-            }
-            if (type == SQLXML.class) {
-                return (T) resultSet.getSQLXML(columnIndex);
-            }
-            if (type == URL.class) {
-                return (T) resultSet.getURL(columnIndex);
-            }
-            throw new SQLFeatureNotSupportedException(
-                    String.format("resultSet=%s, columnIndex=%,d, type=%s", resultSet, columnIndex, type));
-        }
-    }
-
-    /**
-     * Delegates to {@link ResultSet#getObject(String, Class)} without throwing an {@link AbstractMethodError}.
-     *
-     * @param <T>
-     *            See {@link ResultSet#getObject(String, Class)}
-     * @param resultSet
-     *            See {@link ResultSet#getObject(String, Class)}
-     * @param columnLabel
-     *            See {@link ResultSet#getObject(String, Class)}
-     * @param type
-     *            See {@link ResultSet#getObject(String, Class)}
-     * @return See {@link ResultSet#getObject(String, Class)}
-     * @throws SQLException
-     *             See {@link ResultSet#getObject(String, Class)}
-     * @see ResultSet#getObject(int, Class)
-     */
-    @SuppressWarnings("unchecked")
-    public static <T> T getObject(final ResultSet resultSet, final String columnLabel, final Class<T> type)
-            throws SQLException {
-        try {
-            return resultSet.getObject(columnLabel, type);
-        } catch (final AbstractMethodError e) {
-            // Numbers
-            if (type == Integer.class) {
-                return (T) Integer.valueOf(resultSet.getInt(columnLabel));
-            }
-            if (type == Long.class) {
-                return (T) Long.valueOf(resultSet.getLong(columnLabel));
-            }
-            if (type == Double.class) {
-                return (T) Double.valueOf(resultSet.getDouble(columnLabel));
-            }
-            if (type == Float.class) {
-                return (T) Float.valueOf(resultSet.getFloat(columnLabel));
-            }
-            if (type == Short.class) {
-                return (T) Short.valueOf(resultSet.getShort(columnLabel));
-            }
-            if (type == BigDecimal.class) {
-                return (T) resultSet.getBigDecimal(columnLabel);
-            }
-            if (type == Byte.class) {
-                return (T) Byte.valueOf(resultSet.getByte(columnLabel));
-            }
-            // Dates
-            if (type == Date.class) {
-                return (T) resultSet.getDate(columnLabel);
-            }
-            if (type == Time.class) {
-                return (T) resultSet.getTime(columnLabel);
-            }
-            if (type == Timestamp.class) {
-                return (T) resultSet.getTimestamp(columnLabel);
-            }
-            // Streams
-            if (type == InputStream.class) {
-                return (T) resultSet.getBinaryStream(columnLabel);
-            }
-            if (type == Reader.class) {
-                return (T) resultSet.getCharacterStream(columnLabel);
-            }
-            // Other
-            if (type == Object.class) {
-                return (T) resultSet.getObject(columnLabel);
-            }
-            if (type == Boolean.class) {
-                return (T) Boolean.valueOf(resultSet.getBoolean(columnLabel));
-            }
-            if (type == Array.class) {
-                return (T) resultSet.getArray(columnLabel);
-            }
-            if (type == Blob.class) {
-                return (T) resultSet.getBlob(columnLabel);
-            }
-            if (type == Clob.class) {
-                return (T) resultSet.getClob(columnLabel);
-            }
-            if (type == Ref.class) {
-                return (T) resultSet.getRef(columnLabel);
-            }
-            if (type == RowId.class) {
-                return (T) resultSet.getRowId(columnLabel);
-            }
-            if (type == SQLXML.class) {
-                return (T) resultSet.getSQLXML(columnLabel);
-            }
-            if (type == URL.class) {
-                return (T) resultSet.getURL(columnLabel);
-            }
-            throw new SQLFeatureNotSupportedException(
-                    String.format("resultSet=%s, columnLabel=%s, type=%s", resultSet, columnLabel, type));
-        }
-    }
-
-    /**
-     * Delegates to {@link CommonDataSource#getParentLogger()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link CommonDataSource#getParentLogger()}, then return null.
-     * </p>
-     *
-     * @param commonDataSource
-     *            See {@link CommonDataSource#getParentLogger()}
-     * @return See {@link CommonDataSource#getParentLogger()}
-     * @throws SQLFeatureNotSupportedException
-     *             See {@link CommonDataSource#getParentLogger()}
-     */
-    public static Logger getParentLogger(final CommonDataSource commonDataSource) throws SQLFeatureNotSupportedException {
-        try {
-            return commonDataSource.getParentLogger();
-        } catch (final AbstractMethodError e) {
-            throw new SQLFeatureNotSupportedException("javax.sql.CommonDataSource#getParentLogger()");
-        }
-    }
-
-    /**
-     * Delegates to {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} without throwing a
-     * {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)},
-     * then return null.
-     * </p>
-     *
-     * @param databaseMetaData
-     *            the receiver
-     * @param catalog
-     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
-     * @param schemaPattern
-     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
-     * @param tableNamePattern
-     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
-     * @param columnNamePattern
-     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
-     * @return See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
-     * @throws SQLException
-     *             See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
-     * @see DatabaseMetaData#getPseudoColumns(String, String, String, String)
-     */
-    public static ResultSet getPseudoColumns(final DatabaseMetaData databaseMetaData, final String catalog,
-            final String schemaPattern, final String tableNamePattern, final String columnNamePattern)
-            throws SQLException {
-        try {
-            return databaseMetaData.getPseudoColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern);
-        } catch (final AbstractMethodError e) {
-            // do nothing
-            return null;
-        }
-    }
-
-    /**
-     * Delegates to {@link Connection#getSchema()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Connection#getSchema()}, then return null.
-     * </p>
-     *
-     * @param connection
-     *            the receiver
-     * @return null for a JDBC 4 driver or a value per {@link Connection#getSchema()}.
-     * @throws SQLException
-     *             See {@link Connection#getSchema()}.
-     * @see Connection#getSchema()
-     */
-    public static String getSchema(final Connection connection) throws SQLException {
-        try {
-            return connection.getSchema();
-        } catch (final AbstractMethodError e) {
-            // do nothing
-            return null;
-        }
-    }
-
-    /**
-     * Delegates to {@link Statement#isCloseOnCompletion()} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Statement#isCloseOnCompletion()}, then just check that the
-     * connection is closed to then throw an SQLException.
-     * </p>
-     *
-     * @param statement
-     *            See {@link Statement#isCloseOnCompletion()}
-     * @return See {@link Statement#isCloseOnCompletion()}
-     * @throws SQLException
-     *             See {@link Statement#isCloseOnCompletion()}
-     * @see Statement#closeOnCompletion()
-     */
-    public static boolean isCloseOnCompletion(final Statement statement) throws SQLException {
-        try {
-            return statement.isCloseOnCompletion();
-        } catch (final AbstractMethodError e) {
-            if (statement.isClosed()) {
-                throw new SQLException("Statement closed");
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Delegates to {@link Connection#setNetworkTimeout(Executor, int)} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Connection#setNetworkTimeout(Executor, int)}, then do nothing.
-     * </p>
-     *
-     * @param connection
-     *            the receiver
-     * @param executor
-     *            See {@link Connection#setNetworkTimeout(Executor, int)}
-     * @param milliseconds
-     *            {@link Connection#setNetworkTimeout(Executor, int)}
-     * @throws SQLException
-     *             {@link Connection#setNetworkTimeout(Executor, int)}
-     * @see Connection#setNetworkTimeout(Executor, int)
-     */
-    public static void setNetworkTimeout(final Connection connection, final Executor executor, final int milliseconds)
-            throws SQLException {
-        try {
-            connection.setNetworkTimeout(executor, milliseconds);
-        } catch (final AbstractMethodError e) {
-            // do nothing
-        }
-    }
-
-    /**
-     * Delegates to {@link Connection#setSchema(String)} without throwing an {@link AbstractMethodError}.
-     * <p>
-     * If the JDBC driver does not implement {@link Connection#setSchema(String)}, then do nothing.
-     * </p>
-     *
-     * @param connection
-     *            the receiver
-     * @param schema
-     *            See {@link Connection#setSchema(String)}.
-     * @throws SQLException
-     *             See {@link Connection#setSchema(String)}.
-     * @see Connection#setSchema(String)
-     */
-    public static void setSchema(final Connection connection, final String schema) throws SQLException {
-        try {
-            connection.setSchema(schema);
-        } catch (final AbstractMethodError e) {
-            // do nothing
-        }
-    }
-
-}
+/*
+ * 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;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.Date;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.concurrent.Executor;
+import java.util.logging.Logger;
+
+import javax.sql.CommonDataSource;
+
+/**
+ * Defines bridge methods to JDBC 4.1 (Java 7) methods to allow call sites to operate safely (without
+ * {@link AbstractMethodError}) when using a JDBC driver written for JDBC 4.0 (Java 6).
+ * <p>
+ * There should be no need to this kind of code for JDBC 4.2 in Java 8 due to JDBC's use of default methods.
+ * </p>
+ * <p>
+ * This should probably be moved or at least copied in some form to Apache Commons DbUtils.
+ * </p>
+ *
+ * @since 2.6.0
+ */
+public class Jdbc41Bridge {
+
+    /**
+     * Delegates to {@link Connection#abort(Executor)} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Connection#abort(Executor)}, then call {@link Connection#close()}.
+     * </p>
+     *
+     * @param connection
+     *            the receiver
+     * @param executor
+     *            See {@link Connection#abort(Executor)}.
+     * @throws SQLException
+     *             See {@link Connection#abort(Executor)}.
+     * @see Connection#abort(Executor)
+     */
+    public static void abort(final Connection connection, final Executor executor) throws SQLException {
+        try {
+            connection.abort(executor);
+        } catch (final AbstractMethodError e) {
+            connection.close();
+        }
+    }
+
+    /**
+     * Delegates to {@link Statement#closeOnCompletion()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Statement#closeOnCompletion()}, then just check that the connection
+     * is closed to then throw an SQLException.
+     * </p>
+     *
+     * @param statement
+     *            See {@link Statement#closeOnCompletion()}
+     * @throws SQLException
+     *             See {@link Statement#closeOnCompletion()}
+     * @see Statement#closeOnCompletion()
+     */
+    public static void closeOnCompletion(final Statement statement) throws SQLException {
+        try {
+            statement.closeOnCompletion();
+        } catch (final AbstractMethodError e) {
+            if (statement.isClosed()) {
+                throw new SQLException("Statement closed");
+            }
+        }
+    }
+
+    /**
+     * Delegates to {@link DatabaseMetaData#generatedKeyAlwaysReturned()} without throwing a
+     * {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link DatabaseMetaData#generatedKeyAlwaysReturned()}, then return false.
+     * </p>
+     *
+     * @param databaseMetaData
+     *            See {@link DatabaseMetaData#generatedKeyAlwaysReturned()}
+     * @return See {@link DatabaseMetaData#generatedKeyAlwaysReturned()}
+     * @throws SQLException
+     *             See {@link DatabaseMetaData#generatedKeyAlwaysReturned()}
+     * @see DatabaseMetaData#generatedKeyAlwaysReturned()
+     */
+    public static boolean generatedKeyAlwaysReturned(final DatabaseMetaData databaseMetaData) throws SQLException {
+        try {
+            return databaseMetaData.generatedKeyAlwaysReturned();
+        } catch (final AbstractMethodError e) {
+            // do nothing
+            return false;
+        }
+    }
+
+    /**
+     * Delegates to {@link Connection#getNetworkTimeout()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Connection#getNetworkTimeout()}, then return 0.
+     * </p>
+     *
+     * @param connection
+     *            the receiver
+     * @return See {@link Connection#getNetworkTimeout()}
+     * @throws SQLException
+     *             See {@link Connection#getNetworkTimeout()}
+     * @see Connection#getNetworkTimeout()
+     */
+    public static int getNetworkTimeout(final Connection connection) throws SQLException {
+        try {
+            return connection.getNetworkTimeout();
+        } catch (final AbstractMethodError e) {
+            return 0;
+        }
+    }
+
+    /**
+     * Delegates to {@link ResultSet#getObject(int, Class)} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link ResultSet#getObject(int, Class)}, then return 0.
+     * </p>
+     *
+     * @param <T>
+     *            See {@link ResultSet#getObject(int, Class)}
+     * @param resultSet
+     *            See {@link ResultSet#getObject(int, Class)}
+     * @param columnIndex
+     *            See {@link ResultSet#getObject(int, Class)}
+     * @param type
+     *            See {@link ResultSet#getObject(int, Class)}
+     * @return See {@link ResultSet#getObject(int, Class)}
+     * @throws SQLException
+     *             See {@link ResultSet#getObject(int, Class)}
+     * @see ResultSet#getObject(int, Class)
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getObject(final ResultSet resultSet, final int columnIndex, final Class<T> type)
+            throws SQLException {
+        try {
+            return resultSet.getObject(columnIndex, type);
+        } catch (final AbstractMethodError e) {
+            if (type == String.class) {
+                return (T) resultSet.getString(columnIndex);
+            }
+            // Numbers
+            if (type == Integer.class) {
+                return (T) Integer.valueOf(resultSet.getInt(columnIndex));
+            }
+            if (type == Long.class) {
+                return (T) Long.valueOf(resultSet.getLong(columnIndex));
+            }
+            if (type == Double.class) {
+                return (T) Double.valueOf(resultSet.getDouble(columnIndex));
+            }
+            if (type == Float.class) {
+                return (T) Float.valueOf(resultSet.getFloat(columnIndex));
+            }
+            if (type == Short.class) {
+                return (T) Short.valueOf(resultSet.getShort(columnIndex));
+            }
+            if (type == BigDecimal.class) {
+                return (T) resultSet.getBigDecimal(columnIndex);
+            }
+            if (type == Byte.class) {
+                return (T) Byte.valueOf(resultSet.getByte(columnIndex));
+            }
+            // Dates
+            if (type == Date.class) {
+                return (T) resultSet.getDate(columnIndex);
+            }
+            if (type == Time.class) {
+                return (T) resultSet.getTime(columnIndex);
+            }
+            if (type == Timestamp.class) {
+                return (T) resultSet.getTimestamp(columnIndex);
+            }
+            // Streams
+            if (type == InputStream.class) {
+                return (T) resultSet.getBinaryStream(columnIndex);
+            }
+            if (type == Reader.class) {
+                return (T) resultSet.getCharacterStream(columnIndex);
+            }
+            // Other
+            if (type == Object.class) {
+                return (T) resultSet.getObject(columnIndex);
+            }
+            if (type == Boolean.class) {
+                return (T) Boolean.valueOf(resultSet.getBoolean(columnIndex));
+            }
+            if (type == Array.class) {
+                return (T) resultSet.getArray(columnIndex);
+            }
+            if (type == Blob.class) {
+                return (T) resultSet.getBlob(columnIndex);
+            }
+            if (type == Clob.class) {
+                return (T) resultSet.getClob(columnIndex);
+            }
+            if (type == Ref.class) {
+                return (T) resultSet.getRef(columnIndex);
+            }
+            if (type == RowId.class) {
+                return (T) resultSet.getRowId(columnIndex);
+            }
+            if (type == SQLXML.class) {
+                return (T) resultSet.getSQLXML(columnIndex);
+            }
+            if (type == URL.class) {
+                return (T) resultSet.getURL(columnIndex);
+            }
+            throw new SQLFeatureNotSupportedException(
+                    String.format("resultSet=%s, columnIndex=%,d, type=%s", resultSet, columnIndex, type));
+        }
+    }
+
+    /**
+     * Delegates to {@link ResultSet#getObject(String, Class)} without throwing an {@link AbstractMethodError}.
+     *
+     * @param <T>
+     *            See {@link ResultSet#getObject(String, Class)}
+     * @param resultSet
+     *            See {@link ResultSet#getObject(String, Class)}
+     * @param columnLabel
+     *            See {@link ResultSet#getObject(String, Class)}
+     * @param type
+     *            See {@link ResultSet#getObject(String, Class)}
+     * @return See {@link ResultSet#getObject(String, Class)}
+     * @throws SQLException
+     *             See {@link ResultSet#getObject(String, Class)}
+     * @see ResultSet#getObject(int, Class)
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getObject(final ResultSet resultSet, final String columnLabel, final Class<T> type)
+            throws SQLException {
+        try {
+            return resultSet.getObject(columnLabel, type);
+        } catch (final AbstractMethodError e) {
+            // Numbers
+            if (type == Integer.class) {
+                return (T) Integer.valueOf(resultSet.getInt(columnLabel));
+            }
+            if (type == Long.class) {
+                return (T) Long.valueOf(resultSet.getLong(columnLabel));
+            }
+            if (type == Double.class) {
+                return (T) Double.valueOf(resultSet.getDouble(columnLabel));
+            }
+            if (type == Float.class) {
+                return (T) Float.valueOf(resultSet.getFloat(columnLabel));
+            }
+            if (type == Short.class) {
+                return (T) Short.valueOf(resultSet.getShort(columnLabel));
+            }
+            if (type == BigDecimal.class) {
+                return (T) resultSet.getBigDecimal(columnLabel);
+            }
+            if (type == Byte.class) {
+                return (T) Byte.valueOf(resultSet.getByte(columnLabel));
+            }
+            // Dates
+            if (type == Date.class) {
+                return (T) resultSet.getDate(columnLabel);
+            }
+            if (type == Time.class) {
+                return (T) resultSet.getTime(columnLabel);
+            }
+            if (type == Timestamp.class) {
+                return (T) resultSet.getTimestamp(columnLabel);
+            }
+            // Streams
+            if (type == InputStream.class) {
+                return (T) resultSet.getBinaryStream(columnLabel);
+            }
+            if (type == Reader.class) {
+                return (T) resultSet.getCharacterStream(columnLabel);
+            }
+            // Other
+            if (type == Object.class) {
+                return (T) resultSet.getObject(columnLabel);
+            }
+            if (type == Boolean.class) {
+                return (T) Boolean.valueOf(resultSet.getBoolean(columnLabel));
+            }
+            if (type == Array.class) {
+                return (T) resultSet.getArray(columnLabel);
+            }
+            if (type == Blob.class) {
+                return (T) resultSet.getBlob(columnLabel);
+            }
+            if (type == Clob.class) {
+                return (T) resultSet.getClob(columnLabel);
+            }
+            if (type == Ref.class) {
+                return (T) resultSet.getRef(columnLabel);
+            }
+            if (type == RowId.class) {
+                return (T) resultSet.getRowId(columnLabel);
+            }
+            if (type == SQLXML.class) {
+                return (T) resultSet.getSQLXML(columnLabel);
+            }
+            if (type == URL.class) {
+                return (T) resultSet.getURL(columnLabel);
+            }
+            throw new SQLFeatureNotSupportedException(
+                    String.format("resultSet=%s, columnLabel=%s, type=%s", resultSet, columnLabel, type));
+        }
+    }
+
+    /**
+     * Delegates to {@link CommonDataSource#getParentLogger()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link CommonDataSource#getParentLogger()}, then return null.
+     * </p>
+     *
+     * @param commonDataSource
+     *            See {@link CommonDataSource#getParentLogger()}
+     * @return See {@link CommonDataSource#getParentLogger()}
+     * @throws SQLFeatureNotSupportedException
+     *             See {@link CommonDataSource#getParentLogger()}
+     */
+    public static Logger getParentLogger(final CommonDataSource commonDataSource) throws SQLFeatureNotSupportedException {
+        try {
+            return commonDataSource.getParentLogger();
+        } catch (final AbstractMethodError e) {
+            throw new SQLFeatureNotSupportedException("javax.sql.CommonDataSource#getParentLogger()");
+        }
+    }
+
+    /**
+     * Delegates to {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)} without throwing a
+     * {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)},
+     * then return null.
+     * </p>
+     *
+     * @param databaseMetaData
+     *            the receiver
+     * @param catalog
+     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
+     * @param schemaPattern
+     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
+     * @param tableNamePattern
+     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
+     * @param columnNamePattern
+     *            See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
+     * @return See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
+     * @throws SQLException
+     *             See {@link DatabaseMetaData#getPseudoColumns(String, String, String, String)}
+     * @see DatabaseMetaData#getPseudoColumns(String, String, String, String)
+     */
+    public static ResultSet getPseudoColumns(final DatabaseMetaData databaseMetaData, final String catalog,
+            final String schemaPattern, final String tableNamePattern, final String columnNamePattern)
+            throws SQLException {
+        try {
+            return databaseMetaData.getPseudoColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern);
+        } catch (final AbstractMethodError e) {
+            // do nothing
+            return null;
+        }
+    }
+
+    /**
+     * Delegates to {@link Connection#getSchema()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Connection#getSchema()}, then return null.
+     * </p>
+     *
+     * @param connection
+     *            the receiver
+     * @return null for a JDBC 4 driver or a value per {@link Connection#getSchema()}.
+     * @throws SQLException
+     *             See {@link Connection#getSchema()}.
+     * @see Connection#getSchema()
+     */
+    public static String getSchema(final Connection connection) throws SQLException {
+        try {
+            return connection.getSchema();
+        } catch (final AbstractMethodError e) {
+            // do nothing
+            return null;
+        }
+    }
+
+    /**
+     * Delegates to {@link Statement#isCloseOnCompletion()} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Statement#isCloseOnCompletion()}, then just check that the
+     * connection is closed to then throw an SQLException.
+     * </p>
+     *
+     * @param statement
+     *            See {@link Statement#isCloseOnCompletion()}
+     * @return See {@link Statement#isCloseOnCompletion()}
+     * @throws SQLException
+     *             See {@link Statement#isCloseOnCompletion()}
+     * @see Statement#closeOnCompletion()
+     */
+    public static boolean isCloseOnCompletion(final Statement statement) throws SQLException {
+        try {
+            return statement.isCloseOnCompletion();
+        } catch (final AbstractMethodError e) {
+            if (statement.isClosed()) {
+                throw new SQLException("Statement closed");
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Delegates to {@link Connection#setNetworkTimeout(Executor, int)} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Connection#setNetworkTimeout(Executor, int)}, then do nothing.
+     * </p>
+     *
+     * @param connection
+     *            the receiver
+     * @param executor
+     *            See {@link Connection#setNetworkTimeout(Executor, int)}
+     * @param milliseconds
+     *            {@link Connection#setNetworkTimeout(Executor, int)}
+     * @throws SQLException
+     *             {@link Connection#setNetworkTimeout(Executor, int)}
+     * @see Connection#setNetworkTimeout(Executor, int)
+     */
+    public static void setNetworkTimeout(final Connection connection, final Executor executor, final int milliseconds)
+            throws SQLException {
+        try {
+            connection.setNetworkTimeout(executor, milliseconds);
+        } catch (final AbstractMethodError ignored) {
+            // do nothing
+        }
+    }
+
+    /**
+     * Delegates to {@link Connection#setSchema(String)} without throwing an {@link AbstractMethodError}.
+     * <p>
+     * If the JDBC driver does not implement {@link Connection#setSchema(String)}, then do nothing.
+     * </p>
+     *
+     * @param connection
+     *            the receiver
+     * @param schema
+     *            See {@link Connection#setSchema(String)}.
+     * @throws SQLException
+     *             See {@link Connection#setSchema(String)}.
+     * @see Connection#setSchema(String)
+     */
+    public static void setSchema(final Connection connection, final String schema) throws SQLException {
+        try {
+            connection.setSchema(schema);
+        } catch (final AbstractMethodError ignored) {
+            // do nothing
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java
index d9e463b6..ed5a2a3d 100644
--- a/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java
+++ b/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java
@@ -1,404 +1,404 @@
-/*
- * 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;
-
-import java.lang.management.ManagementFactory;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.time.Duration;
-import java.util.Collection;
-import java.util.concurrent.Executor;
-
-import javax.management.InstanceAlreadyExistsException;
-import javax.management.MBeanRegistrationException;
-import javax.management.MBeanServer;
-import javax.management.NotCompliantMBeanException;
-import javax.management.ObjectName;
-
-import org.apache.commons.pool2.ObjectPool;
-
-/**
- * A delegating connection that, rather than closing the underlying connection, returns itself to an {@link ObjectPool}
- * when closed.
- *
- * @since 2.0
- */
-public class PoolableConnection extends DelegatingConnection<Connection> implements PoolableConnectionMXBean {
-
-    private static MBeanServer MBEAN_SERVER;
-
-    static {
-        try {
-            MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
-        } catch (final NoClassDefFoundError | Exception ex) {
-            // ignore - JMX not available
-        }
-    }
-
-    /** The pool to which I should return. */
-    private final ObjectPool<PoolableConnection> pool;
-
-    private final ObjectNameWrapper jmxObjectName;
-
-    // Use a prepared statement for validation, retaining the last used SQL to
-    // check if the validation query has changed.
-    private PreparedStatement validationPreparedStatement;
-    private String lastValidationSql;
-
-    /**
-     * Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be
-     * considered broken and not pass validation in the future.
-     */
-    private boolean fatalSqlExceptionThrown;
-
-    /**
-     * SQL_STATE codes considered to signal fatal conditions. Overrides the defaults in
-     * {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
-     */
-    private final Collection<String> disconnectionSqlCodes;
-
-    /** Whether or not to fast fail validation after fatal connection errors */
-    private final boolean fastFailValidation;
-
-    /**
-     *
-     * @param conn
-     *            my underlying connection
-     * @param pool
-     *            the pool to which I should return when closed
-     * @param jmxName
-     *            JMX name
-     */
-    public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
-            final ObjectName jmxName) {
-        this(conn, pool, jmxName, null, true);
-    }
-
-    /**
-     *
-     * @param conn
-     *            my underlying connection
-     * @param pool
-     *            the pool to which I should return when closed
-     * @param jmxObjectName
-     *            JMX name
-     * @param disconnectSqlCodes
-     *            SQL_STATE codes considered fatal disconnection errors
-     * @param fastFailValidation
-     *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
-     *            run query or isValid)
-     */
-    public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
-            final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
-            final boolean fastFailValidation) {
-        super(conn);
-        this.pool = pool;
-        this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName);
-        this.disconnectionSqlCodes = disconnectSqlCodes;
-        this.fastFailValidation = fastFailValidation;
-
-        if (jmxObjectName != null) {
-            try {
-                MBEAN_SERVER.registerMBean(this, jmxObjectName);
-            } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) {
-                // For now, simply skip registration
-            }
-        }
-    }
-
-    /**
-     * Abort my underlying {@link Connection}.
-     *
-     * @since 2.9.0
-     */
-    @Override
-    public void abort(final Executor executor) throws SQLException {
-        if (jmxObjectName != null) {
-            jmxObjectName.unregisterMBean();
-        }
-        super.abort(executor);
-    }
-
-    /**
-     * Returns me to my pool.
-     */
-    @Override
-    public synchronized void close() throws SQLException {
-        if (isClosedInternal()) {
-            // already closed
-            return;
-        }
-
-        boolean isUnderlyingConnectionClosed;
-        try {
-            isUnderlyingConnectionClosed = getDelegateInternal().isClosed();
-        } catch (final SQLException e) {
-            try {
-                pool.invalidateObject(this);
-            } catch (final IllegalStateException ise) {
-                // pool is closed, so close the connection
-                passivate();
-                getInnermostDelegate().close();
-            } catch (final Exception ie) {
-                // DO NOTHING the original exception will be rethrown
-            }
-            throw new SQLException("Cannot close connection (isClosed check failed)", e);
-        }
-
-        /*
-         * Can't set close before this code block since the connection needs to be open when validation runs. Can't set
-         * close after this code block since by then the connection will have been returned to the pool and may have
-         * been borrowed by another thread. Therefore, the close flag is set in passivate().
-         */
-        if (isUnderlyingConnectionClosed) {
-            // Abnormal close: underlying connection closed unexpectedly, so we
-            // must destroy this proxy
-            try {
-                pool.invalidateObject(this);
-            } catch (final IllegalStateException e) {
-                // pool is closed, so close the connection
-                passivate();
-                getInnermostDelegate().close();
-            } catch (final Exception e) {
-                throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
-            }
-        } else {
-            // Normal close: underlying connection is still open, so we
-            // simply need to return this proxy to the pool
-            try {
-                pool.returnObject(this);
-            } catch (final IllegalStateException e) {
-                // pool is closed, so close the connection
-                passivate();
-                getInnermostDelegate().close();
-            } catch (final SQLException | RuntimeException e) {
-                throw e;
-            } catch (final Exception e) {
-                throw new SQLException("Cannot close connection (return to pool failed)", e);
-            }
-        }
-    }
-
-    /**
-     * @return The disconnection SQL codes.
-     * @since 2.6.0
-     */
-    public Collection<String> getDisconnectionSqlCodes() {
-        return disconnectionSqlCodes;
-    }
-
-    /**
-     * Expose the {@link #toString()} method via a bean getter so it can be read as a property via JMX.
-     */
-    @Override
-    public String getToString() {
-        return toString();
-    }
-
-    @Override
-    protected void handleException(final SQLException e) throws SQLException {
-        fatalSqlExceptionThrown |= isFatalException(e);
-        super.handleException(e);
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * This method should not be used by a client to determine whether or not a connection should be return to the
-     * connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool
-     * once it is no longer required.
-     */
-    @Override
-    public boolean isClosed() throws SQLException {
-        if (isClosedInternal()) {
-            return true;
-        }
-
-        if (getDelegateInternal().isClosed()) {
-            // Something has gone wrong. The underlying connection has been
-            // closed without the connection being returned to the pool. Return
-            // it now.
-            close();
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Checks the SQLState of the input exception.
-     * <p>
-     * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the configured list of fatal
-     * exception codes. If this property is not set, codes are compared against the default codes in
-     * {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
-     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
-     * </p>
-     *
-     * @param e SQLException to be examined
-     * @return true if the exception signals a disconnection
-     */
-    boolean isDisconnectionSqlException(final SQLException e) {
-        boolean fatalException = false;
-        final String sqlState = e.getSQLState();
-        if (sqlState != null) {
-            fatalException = disconnectionSqlCodes == null
-                ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.DISCONNECTION_SQL_CODES.contains(sqlState)
-                : disconnectionSqlCodes.contains(sqlState);
-        }
-        return fatalException;
-    }
-
-    /**
-     * Checks the SQLState of the input exception and any nested SQLExceptions it wraps.
-     * <p>
-     * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the
-     * configured list of fatal exception codes. If this property is not set, codes are compared against the default
-     * codes in {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
-     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
-     * </p>
-     *
-     * @param e
-     *            SQLException to be examined
-     * @return true if the exception signals a disconnection
-     */
-    boolean isFatalException(final SQLException e) {
-        boolean fatalException = isDisconnectionSqlException(e);
-        if (!fatalException) {
-            SQLException parentException = e;
-            SQLException nextException = e.getNextException();
-            while (nextException != null && nextException != parentException && !fatalException) {
-                fatalException = isDisconnectionSqlException(nextException);
-                parentException = nextException;
-                nextException = parentException.getNextException();
-            }
-        }
-        return fatalException;
-    }
-
-    /**
-     * @return Whether to fail-fast.
-     * @since 2.6.0
-     */
-    public boolean isFastFailValidation() {
-        return fastFailValidation;
-    }
-
-    @Override
-    protected void passivate() throws SQLException {
-        super.passivate();
-        setClosedInternal(true);
-        if (getDelegateInternal() instanceof PoolingConnection) {
-            ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool();
-        }
-    }
-
-    /**
-     * Actually close my underlying {@link Connection}.
-     */
-    @Override
-    public void reallyClose() throws SQLException {
-        if (jmxObjectName != null) {
-            jmxObjectName.unregisterMBean();
-        }
-
-        if (validationPreparedStatement != null) {
-            Utils.closeQuietly((AutoCloseable) validationPreparedStatement);
-        }
-
-        super.closeInternal();
-    }
-
-    /**
-     * Validates the connection, using the following algorithm:
-     * <ol>
-     * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
-     * thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
-     * <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
-     * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
-     * <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
-     * least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
-     * </ol>
-     *
-     * @param sql
-     *            The validation SQL query.
-     * @param timeoutSeconds
-     *            The validation timeout in seconds.
-     * @throws SQLException
-     *             Thrown when validation fails or an SQLException occurs during validation
-     * @deprecated Use {@link #validate(String, Duration)}.
-     */
-    @Deprecated
-    public void validate(final String sql, final int timeoutSeconds) throws SQLException {
-        validate(sql, Duration.ofSeconds(timeoutSeconds));
-    }
-
-    /**
-     * Validates the connection, using the following algorithm:
-     * <ol>
-     * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
-     * thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
-     * <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
-     * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
-     * <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
-     * least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
-     * </ol>
-     *
-     * @param sql
-     *            The validation SQL query.
-     * @param timeoutDuration
-     *            The validation timeout in seconds.
-     * @throws SQLException
-     *             Thrown when validation fails or an SQLException occurs during validation
-     * @since 2.10.0
-     */
-    public void validate(final String sql, Duration timeoutDuration) throws SQLException {
-        if (fastFailValidation && fatalSqlExceptionThrown) {
-            throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
-        }
-
-        if (sql == null || sql.isEmpty()) {
-            if (timeoutDuration.isNegative()) {
-                timeoutDuration = Duration.ZERO;
-            }
-            if (!isValid(timeoutDuration)) {
-                throw new SQLException("isValid() returned false");
-            }
-            return;
-        }
-
-        if (!sql.equals(lastValidationSql)) {
-            lastValidationSql = sql;
-            // Has to be the innermost delegate else the prepared statement will
-            // be closed when the pooled connection is passivated.
-            validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql);
-        }
-
-        if (timeoutDuration.compareTo(Duration.ZERO) > 0) {
-            validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds());
-        }
-
-        try (ResultSet rs = validationPreparedStatement.executeQuery()) {
-            if (!rs.next()) {
-                throw new SQLException("validationQuery didn't return a row");
-            }
-        } catch (final SQLException sqle) {
-            throw sqle;
-        }
-    }
-}
+/*
+ * 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;
+
+import java.lang.management.ManagementFactory;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Duration;
+import java.util.Collection;
+import java.util.concurrent.Executor;
+
+import javax.management.InstanceAlreadyExistsException;
+import javax.management.MBeanRegistrationException;
+import javax.management.MBeanServer;
+import javax.management.NotCompliantMBeanException;
+import javax.management.ObjectName;
+
+import org.apache.commons.pool2.ObjectPool;
+
+/**
+ * A delegating connection that, rather than closing the underlying connection, returns itself to an {@link ObjectPool}
+ * when closed.
+ *
+ * @since 2.0
+ */
+public class PoolableConnection extends DelegatingConnection<Connection> implements PoolableConnectionMXBean {
+
+    private static MBeanServer MBEAN_SERVER;
+
+    static {
+        try {
+            MBEAN_SERVER = ManagementFactory.getPlatformMBeanServer();
+        } catch (final NoClassDefFoundError | Exception ignored) {
+            // ignore - JMX not available
+        }
+    }
+
+    /** The pool to which I should return. */
+    private final ObjectPool<PoolableConnection> pool;
+
+    private final ObjectNameWrapper jmxObjectName;
+
+    // Use a prepared statement for validation, retaining the last used SQL to
+    // check if the validation query has changed.
+    private PreparedStatement validationPreparedStatement;
+    private String lastValidationSql;
+
+    /**
+     * Indicate that unrecoverable SQLException was thrown when using this connection. Such a connection should be
+     * considered broken and not pass validation in the future.
+     */
+    private boolean fatalSqlExceptionThrown;
+
+    /**
+     * SQL_STATE codes considered to signal fatal conditions. Overrides the defaults in
+     * {@link Utils#DISCONNECTION_SQL_CODES} (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
+     */
+    private final Collection<String> disconnectionSqlCodes;
+
+    /** Whether or not to fast fail validation after fatal connection errors */
+    private final boolean fastFailValidation;
+
+    /**
+     *
+     * @param conn
+     *            my underlying connection
+     * @param pool
+     *            the pool to which I should return when closed
+     * @param jmxName
+     *            JMX name
+     */
+    public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
+            final ObjectName jmxName) {
+        this(conn, pool, jmxName, null, true);
+    }
+
+    /**
+     *
+     * @param conn
+     *            my underlying connection
+     * @param pool
+     *            the pool to which I should return when closed
+     * @param jmxObjectName
+     *            JMX name
+     * @param disconnectSqlCodes
+     *            SQL_STATE codes considered fatal disconnection errors
+     * @param fastFailValidation
+     *            true means fatal disconnection errors cause subsequent validations to fail immediately (no attempt to
+     *            run query or isValid)
+     */
+    public PoolableConnection(final Connection conn, final ObjectPool<PoolableConnection> pool,
+            final ObjectName jmxObjectName, final Collection<String> disconnectSqlCodes,
+            final boolean fastFailValidation) {
+        super(conn);
+        this.pool = pool;
+        this.jmxObjectName = ObjectNameWrapper.wrap(jmxObjectName);
+        this.disconnectionSqlCodes = disconnectSqlCodes;
+        this.fastFailValidation = fastFailValidation;
+
+        if (jmxObjectName != null) {
+            try {
+                MBEAN_SERVER.registerMBean(this, jmxObjectName);
+            } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException ignored) {
+                // For now, simply skip registration
+            }
+        }
+    }
+
+    /**
+     * Abort my underlying {@link Connection}.
+     *
+     * @since 2.9.0
+     */
+    @Override
+    public void abort(final Executor executor) throws SQLException {
+        if (jmxObjectName != null) {
+            jmxObjectName.unregisterMBean();
+        }
+        super.abort(executor);
+    }
+
+    /**
+     * Returns me to my pool.
+     */
+    @Override
+    public synchronized void close() throws SQLException {
+        if (isClosedInternal()) {
+            // already closed
+            return;
+        }
+
+        boolean isUnderlyingConnectionClosed;
+        try {
+            isUnderlyingConnectionClosed = getDelegateInternal().isClosed();
+        } catch (final SQLException e) {
+            try {
+                pool.invalidateObject(this);
+            } catch (final IllegalStateException ise) {
+                // pool is closed, so close the connection
+                passivate();
+                getInnermostDelegate().close();
+            } catch (final Exception ignored) {
+                // DO NOTHING the original exception will be rethrown
+            }
+            throw new SQLException("Cannot close connection (isClosed check failed)", e);
+        }
+
+        /*
+         * Can't set close before this code block since the connection needs to be open when validation runs. Can't set
+         * close after this code block since by then the connection will have been returned to the pool and may have
+         * been borrowed by another thread. Therefore, the close flag is set in passivate().
+         */
+        if (isUnderlyingConnectionClosed) {
+            // Abnormal close: underlying connection closed unexpectedly, so we
+            // must destroy this proxy
+            try {
+                pool.invalidateObject(this);
+            } catch (final IllegalStateException e) {
+                // pool is closed, so close the connection
+                passivate();
+                getInnermostDelegate().close();
+            } catch (final Exception e) {
+                throw new SQLException("Cannot close connection (invalidating pooled object failed)", e);
+            }
+        } else {
+            // Normal close: underlying connection is still open, so we
+            // simply need to return this proxy to the pool
+            try {
+                pool.returnObject(this);
+            } catch (final IllegalStateException e) {
+                // pool is closed, so close the connection
+                passivate();
+                getInnermostDelegate().close();
+            } catch (final SQLException | RuntimeException e) {
+                throw e;
+            } catch (final Exception e) {
+                throw new SQLException("Cannot close connection (return to pool failed)", e);
+            }
+        }
+    }
+
+    /**
+     * @return The disconnection SQL codes.
+     * @since 2.6.0
+     */
+    public Collection<String> getDisconnectionSqlCodes() {
+        return disconnectionSqlCodes;
+    }
+
+    /**
+     * Expose the {@link #toString()} method via a bean getter so it can be read as a property via JMX.
+     */
+    @Override
+    public String getToString() {
+        return toString();
+    }
+
+    @Override
+    protected void handleException(final SQLException e) throws SQLException {
+        fatalSqlExceptionThrown |= isFatalException(e);
+        super.handleException(e);
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * This method should not be used by a client to determine whether or not a connection should be return to the
+     * connection pool (by calling {@link #close()}). Clients should always attempt to return a connection to the pool
+     * once it is no longer required.
+     */
+    @Override
+    public boolean isClosed() throws SQLException {
+        if (isClosedInternal()) {
+            return true;
+        }
+
+        if (getDelegateInternal().isClosed()) {
+            // Something has gone wrong. The underlying connection has been
+            // closed without the connection being returned to the pool. Return
+            // it now.
+            close();
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks the SQLState of the input exception.
+     * <p>
+     * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the configured list of fatal
+     * exception codes. If this property is not set, codes are compared against the default codes in
+     * {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
+     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
+     * </p>
+     *
+     * @param e SQLException to be examined
+     * @return true if the exception signals a disconnection
+     */
+    boolean isDisconnectionSqlException(final SQLException e) {
+        boolean fatalException = false;
+        final String sqlState = e.getSQLState();
+        if (sqlState != null) {
+            fatalException = disconnectionSqlCodes == null
+                ? sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX) || Utils.DISCONNECTION_SQL_CODES.contains(sqlState)
+                : disconnectionSqlCodes.contains(sqlState);
+        }
+        return fatalException;
+    }
+
+    /**
+     * Checks the SQLState of the input exception and any nested SQLExceptions it wraps.
+     * <p>
+     * If {@link #disconnectionSqlCodes} has been set, sql states are compared to those in the
+     * configured list of fatal exception codes. If this property is not set, codes are compared against the default
+     * codes in {@link Utils#DISCONNECTION_SQL_CODES} and in this case anything starting with #{link
+     * Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a disconnection.
+     * </p>
+     *
+     * @param e
+     *            SQLException to be examined
+     * @return true if the exception signals a disconnection
+     */
+    boolean isFatalException(final SQLException e) {
+        boolean fatalException = isDisconnectionSqlException(e);
+        if (!fatalException) {
+            SQLException parentException = e;
+            SQLException nextException = e.getNextException();
+            while (nextException != null && nextException != parentException && !fatalException) {
+                fatalException = isDisconnectionSqlException(nextException);
+                parentException = nextException;
+                nextException = parentException.getNextException();
+            }
+        }
+        return fatalException;
+    }
+
+    /**
+     * @return Whether to fail-fast.
+     * @since 2.6.0
+     */
+    public boolean isFastFailValidation() {
+        return fastFailValidation;
+    }
+
+    @Override
+    protected void passivate() throws SQLException {
+        super.passivate();
+        setClosedInternal(true);
+        if (getDelegateInternal() instanceof PoolingConnection) {
+            ((PoolingConnection) getDelegateInternal()).connectionReturnedToPool();
+        }
+    }
+
+    /**
+     * Actually close my underlying {@link Connection}.
+     */
+    @Override
+    public void reallyClose() throws SQLException {
+        if (jmxObjectName != null) {
+            jmxObjectName.unregisterMBean();
+        }
+
+        if (validationPreparedStatement != null) {
+            Utils.closeQuietly((AutoCloseable) validationPreparedStatement);
+        }
+
+        super.closeInternal();
+    }
+
+    /**
+     * Validates the connection, using the following algorithm:
+     * <ol>
+     * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
+     * thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
+     * <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
+     * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
+     * <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
+     * least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
+     * </ol>
+     *
+     * @param sql
+     *            The validation SQL query.
+     * @param timeoutSeconds
+     *            The validation timeout in seconds.
+     * @throws SQLException
+     *             Thrown when validation fails or an SQLException occurs during validation
+     * @deprecated Use {@link #validate(String, Duration)}.
+     */
+    @Deprecated
+    public void validate(final String sql, final int timeoutSeconds) throws SQLException {
+        validate(sql, Duration.ofSeconds(timeoutSeconds));
+    }
+
+    /**
+     * Validates the connection, using the following algorithm:
+     * <ol>
+     * <li>If {@code fastFailValidation} (constructor argument) is {@code true} and this connection has previously
+     * thrown a fatal disconnection exception, a {@code SQLException} is thrown.</li>
+     * <li>If {@code sql} is null, the driver's #{@link Connection#isValid(int) isValid(timeout)} is called. If it
+     * returns {@code false}, {@code SQLException} is thrown; otherwise, this method returns successfully.</li>
+     * <li>If {@code sql} is not null, it is executed as a query and if the resulting {@code ResultSet} contains at
+     * least one row, this method returns successfully. If not, {@code SQLException} is thrown.</li>
+     * </ol>
+     *
+     * @param sql
+     *            The validation SQL query.
+     * @param timeoutDuration
+     *            The validation timeout in seconds.
+     * @throws SQLException
+     *             Thrown when validation fails or an SQLException occurs during validation
+     * @since 2.10.0
+     */
+    public void validate(final String sql, Duration timeoutDuration) throws SQLException {
+        if (fastFailValidation && fatalSqlExceptionThrown) {
+            throw new SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
+        }
+
+        if (sql == null || sql.isEmpty()) {
+            if (timeoutDuration.isNegative()) {
+                timeoutDuration = Duration.ZERO;
+            }
+            if (!isValid(timeoutDuration)) {
+                throw new SQLException("isValid() returned false");
+            }
+            return;
+        }
+
+        if (!sql.equals(lastValidationSql)) {
+            lastValidationSql = sql;
+            // Has to be the innermost delegate else the prepared statement will
+            // be closed when the pooled connection is passivated.
+            validationPreparedStatement = getInnermostDelegateInternal().prepareStatement(sql);
+        }
+
+        if (timeoutDuration.compareTo(Duration.ZERO) > 0) {
+            validationPreparedStatement.setQueryTimeout((int) timeoutDuration.getSeconds());
+        }
+
+        try (ResultSet rs = validationPreparedStatement.executeQuery()) {
+            if (!rs.next()) {
+                throw new SQLException("validationQuery didn't return a row");
+            }
+        } catch (final SQLException sqle) {
+            throw sqle;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java
index 3dd9650d..fb1a7915 100644
--- a/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java
+++ b/src/main/java/org/apache/commons/dbcp2/PoolingConnection.java
@@ -1,623 +1,623 @@
-/*
- * 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;
-
-import java.sql.CallableStatement;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.NoSuchElementException;
-
-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 DelegatingConnection} that pools {@link PreparedStatement}s.
- * <p>
- * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement each
- * time, may actually pull the statement from a pool of unused statements. The {@link PreparedStatement#close} method of
- * the returned statement doesn't actually close the statement, but rather returns it to the pool. (See
- * {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.)
- * </p>
- *
- * @see PoolablePreparedStatement
- * @since 2.0
- */
-public class PoolingConnection extends DelegatingConnection<Connection>
-        implements KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
-
-    /**
-     * Statement types.
-     *
-     * @since 2.0 protected enum.
-     * @since 2.4.0 public enum.
-     */
-    public enum StatementType {
-
-        /**
-         * Callable statement.
-         */
-        CALLABLE_STATEMENT,
-
-        /**
-         * Prepared statement.
-         */
-        PREPARED_STATEMENT
-    }
-
-    /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
-    private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pstmtPool;
-
-    private boolean clearStatementPoolOnReturn;
-
-    /**
-     * Constructor.
-     *
-     * @param connection
-     *            the underlying {@link Connection}.
-     */
-    public PoolingConnection(final Connection connection) {
-        super(connection);
-    }
-
-    /**
-     * {@link KeyedPooledObjectFactory} method for activating pooled statements.
-     *
-     * @param key
-     *            ignored
-     * @param pooledObject
-     *            wrapped pooled statement to be activated
-     */
-    @Override
-    public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
-        pooledObject.getObject().activate();
-    }
-
-    /**
-     * Closes and frees all {@link PreparedStatement}s or {@link CallableStatement}s from the pool, and close the
-     * underlying connection.
-     */
-    @Override
-    public synchronized void close() throws SQLException {
-        try {
-            if (null != pstmtPool) {
-                final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> oldpool = pstmtPool;
-                pstmtPool = null;
-                try {
-                    oldpool.close();
-                } catch (final RuntimeException e) {
-                    throw e;
-                } catch (final Exception e) {
-                    throw new SQLException("Cannot close connection", e);
-                }
-            }
-        } finally {
-            try {
-                getDelegateInternal().close();
-            } finally {
-                setClosedInternal(true);
-            }
-        }
-    }
-
-    /**
-     * Notification from {@link PoolableConnection} that we returned to the pool.
-     *
-     * @throws SQLException when {@code clearStatementPoolOnReturn} is true and the statement pool could not be
-     *                      cleared
-     * @since 2.8.0
-     */
-    public void connectionReturnedToPool() throws SQLException {
-        if (pstmtPool != null && clearStatementPoolOnReturn) {
-            try {
-                pstmtPool.clear();
-            } catch (final Exception e) {
-                throw new SQLException("Error clearing statement pool", e);
-            }
-        }
-    }
-
-    /**
-     * Creates a PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull());
-    }
-
-    /**
-     * Creates a PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the 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 the PStmtKey created for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
-    }
-
-    /**
-     * Creates a PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency);
-    }
-
-    /**
-     * Creates a PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     * @param resultSetHoldability
-     *            result set holdability
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    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 PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     * @param resultSetHoldability
-     *            result set holdability
-     * @param statementType
-     *            statement type
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    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 PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     * @param statementType
-     *            statement type
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    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 PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
-    }
-
-    /**
-     * Creates a PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     * @param statementType
-     *            statement type
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql, final StatementType statementType) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType, null);
-    }
-
-    /**
-     * Creates a PStmtKey for the given arguments.
-     *
-     * @param sql
-     *            the SQL string used to define the statement
-     * @param columnNames
-     *            column names
-     *
-     * @return the PStmtKey created for the given arguments.
-     */
-    protected PStmtKey createKey(final String sql, final String[] columnNames) {
-        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames);
-    }
-
-    /**
-     * {@link KeyedPooledObjectFactory} method for destroying PoolablePreparedStatements and PoolableCallableStatements.
-     * Closes the underlying statement.
-     *
-     * @param key
-     *            ignored
-     * @param pooledObject
-     *            the wrapped pooled statement to be destroyed.
-     */
-    @Override
-    public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
-            throws Exception {
-        pooledObject.getObject().getInnermostDelegate().close();
-    }
-
-    private String getCatalogOrNull() {
-        String catalog = null;
-        try {
-            catalog = getCatalog();
-        } catch (final SQLException e) {
-            // Ignored
-        }
-        return catalog;
-    }
-
-    private String getSchemaOrNull() {
-        String schema = null;
-        try {
-            schema = getSchema();
-        } catch (final SQLException e) {
-            // Ignored
-        }
-        return schema;
-    }
-
-    /**
-     * Returns the prepared statement pool we're using.
-     *
-     * @return statement pool
-     * @since 2.8.0
-     */
-    public KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> getStatementPool() {
-        return pstmtPool;
-    }
-
-    /**
-     * {@link KeyedPooledObjectFactory} method for creating {@link PoolablePreparedStatement}s or
-     * {@link PoolableCallableStatement}s. The {@code stmtType} field in the key determines whether a
-     * PoolablePreparedStatement or PoolableCallableStatement is created.
-     *
-     * @param key
-     *            the key for the {@link PreparedStatement} to be created
-     * @see #createKey(String, int, int, StatementType)
-     */
-    @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(getDelegate());
-            @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
-            final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pstmtPool, this);
-            return new DefaultPooledObject<>(pps);
-        }
-        final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate());
-        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pstmtPool, this);
-        return new DefaultPooledObject<>(pcs);
-    }
-
-    /**
-     * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original.
-     *
-     * @param sql The statement to be normalized.
-     *
-     * @return The canonical form of the supplied SQL statement.
-     */
-    protected String normalizeSQL(final String sql) {
-        return sql.trim();
-    }
-
-    /**
-     * {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s or {@link CallableStatement}s.
-     * 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 the pool.
-     *
-     * @param key
-     *            a {@link PStmtKey} for the given arguments
-     * @return a {@link PoolableCallableStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    private CallableStatement prepareCall(final PStmtKey key) throws SQLException {
-        return (CallableStatement) prepareStatement(key);
-    }
-
-    /**
-     * Creates or obtains a {@link CallableStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the CallableStatement
-     * @return a {@link PoolableCallableStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public CallableStatement prepareCall(final String sql) throws SQLException {
-        return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT));
-    }
-
-    /**
-     * Creates or obtains a {@link CallableStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the CallableStatement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     * @return a {@link PoolableCallableStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
-            throws SQLException {
-        return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
-    }
-
-    /**
-     * Creates or obtains a {@link CallableStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the CallableStatement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     * @param resultSetHoldability
-     *            result set holdability
-     * @return a {@link PoolableCallableStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) throws SQLException {
-        return prepareCall(createKey(sql, resultSetType, resultSetConcurrency,
-                resultSetHoldability, StatementType.CALLABLE_STATEMENT));
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from the pool.
-     *
-     * @param key
-     *            a {@link PStmtKey} for the given arguments
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException {
-        if (null == pstmtPool) {
-            throw new SQLException("Statement pool is null - closed or invalid PoolingConnection.");
-        }
-        try {
-            return pstmtPool.borrowObject(key);
-        } catch (final NoSuchElementException e) {
-            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
-        } 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 the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the PreparedStatement
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public PreparedStatement prepareStatement(final String sql) throws SQLException {
-        return prepareStatement(createKey(sql));
-    }
-
-    /*
-     * Creates or obtains a {@link PreparedStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the PreparedStatement
-     * @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
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
-        return prepareStatement(createKey(sql, autoGeneratedKeys));
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the PreparedStatement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
-            throws SQLException {
-        return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency));
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the PreparedStatement
-     * @param resultSetType
-     *            result set type
-     * @param resultSetConcurrency
-     *            result set concurrency
-     * @param resultSetHoldability
-     *            result set holdability
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
-            final int resultSetHoldability) throws SQLException {
-        return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the PreparedStatement
-     * @param columnIndexes
-     *            An array of column indexes indicating the columns that should be returned from the inserted row or
-     *            rows.
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     *
-     */
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
-        return prepareStatement(createKey(sql, columnIndexes));
-    }
-
-    /**
-     * Creates or obtains a {@link PreparedStatement} from the pool.
-     *
-     * @param sql
-     *            the SQL string used to define the PreparedStatement
-     * @param columnNames
-     *            column names
-     * @return a {@link PoolablePreparedStatement}
-     * @throws SQLException
-     *             Wraps an underlying exception.
-     */
-    @Override
-    public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
-        return prepareStatement(createKey(sql, columnNames));
-    }
-
-    /**
-     * Sets whether the pool of statements should be cleared when the connection is returned to its pool.
-     * Default is false.
-     *
-     * @param clearStatementPoolOnReturn clear or not
-     * @since 2.8.0
-     */
-    public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) {
-        this.clearStatementPoolOnReturn = clearStatementPoolOnReturn;
-    }
-
-    /**
-     * Sets the prepared statement pool.
-     *
-     * @param pool
-     *            the prepared statement pool.
-     */
-    public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pool) {
-        pstmtPool = pool;
-    }
-
-    @Override
-    public synchronized String toString() {
-        if (pstmtPool != null) {
-            return "PoolingConnection: " + pstmtPool.toString();
-        }
-        return "PoolingConnection: null";
-    }
-
-    /**
-     * {@link KeyedPooledObjectFactory} method for validating pooled statements. Currently always returns true.
-     *
-     * @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;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.NoSuchElementException;
+
+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 DelegatingConnection} that pools {@link PreparedStatement}s.
+ * <p>
+ * The {@link #prepareStatement} and {@link #prepareCall} methods, rather than creating a new PreparedStatement each
+ * time, may actually pull the statement from a pool of unused statements. The {@link PreparedStatement#close} method of
+ * the returned statement doesn't actually close the statement, but rather returns it to the pool. (See
+ * {@link PoolablePreparedStatement}, {@link PoolableCallableStatement}.)
+ * </p>
+ *
+ * @see PoolablePreparedStatement
+ * @since 2.0
+ */
+public class PoolingConnection extends DelegatingConnection<Connection>
+        implements KeyedPooledObjectFactory<PStmtKey, DelegatingPreparedStatement> {
+
+    /**
+     * Statement types.
+     *
+     * @since 2.0 protected enum.
+     * @since 2.4.0 public enum.
+     */
+    public enum StatementType {
+
+        /**
+         * Callable statement.
+         */
+        CALLABLE_STATEMENT,
+
+        /**
+         * Prepared statement.
+         */
+        PREPARED_STATEMENT
+    }
+
+    /** Pool of {@link PreparedStatement}s. and {@link CallableStatement}s */
+    private KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pstmtPool;
+
+    private boolean clearStatementPoolOnReturn;
+
+    /**
+     * Constructor.
+     *
+     * @param connection
+     *            the underlying {@link Connection}.
+     */
+    public PoolingConnection(final Connection connection) {
+        super(connection);
+    }
+
+    /**
+     * {@link KeyedPooledObjectFactory} method for activating pooled statements.
+     *
+     * @param key
+     *            ignored
+     * @param pooledObject
+     *            wrapped pooled statement to be activated
+     */
+    @Override
+    public void activateObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
+            throws Exception {
+        pooledObject.getObject().activate();
+    }
+
+    /**
+     * Closes and frees all {@link PreparedStatement}s or {@link CallableStatement}s from the pool, and close the
+     * underlying connection.
+     */
+    @Override
+    public synchronized void close() throws SQLException {
+        try {
+            if (null != pstmtPool) {
+                final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> oldpool = pstmtPool;
+                pstmtPool = null;
+                try {
+                    oldpool.close();
+                } catch (final RuntimeException e) {
+                    throw e;
+                } catch (final Exception e) {
+                    throw new SQLException("Cannot close connection", e);
+                }
+            }
+        } finally {
+            try {
+                getDelegateInternal().close();
+            } finally {
+                setClosedInternal(true);
+            }
+        }
+    }
+
+    /**
+     * Notification from {@link PoolableConnection} that we returned to the pool.
+     *
+     * @throws SQLException when {@code clearStatementPoolOnReturn} is true and the statement pool could not be
+     *                      cleared
+     * @since 2.8.0
+     */
+    public void connectionReturnedToPool() throws SQLException {
+        if (pstmtPool != null && clearStatementPoolOnReturn) {
+            try {
+                pstmtPool.clear();
+            } catch (final Exception e) {
+                throw new SQLException("Error clearing statement pool", e);
+            }
+        }
+    }
+
+    /**
+     * Creates a PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull());
+    }
+
+    /**
+     * Creates a PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the 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 the PStmtKey created for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql, final int autoGeneratedKeys) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), autoGeneratedKeys);
+    }
+
+    /**
+     * Creates a PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql, final int resultSetType, final int resultSetConcurrency) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), resultSetType, resultSetConcurrency);
+    }
+
+    /**
+     * Creates a PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     * @param resultSetHoldability
+     *            result set holdability
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    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 PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     * @param resultSetHoldability
+     *            result set holdability
+     * @param statementType
+     *            statement type
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    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 PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     * @param statementType
+     *            statement type
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    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 PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     * @param columnIndexes
+     *            An array of column indexes indicating the columns that should be returned from the inserted row or
+     *            rows.
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql, final int[] columnIndexes) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnIndexes);
+    }
+
+    /**
+     * Creates a PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     * @param statementType
+     *            statement type
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql, final StatementType statementType) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), statementType, null);
+    }
+
+    /**
+     * Creates a PStmtKey for the given arguments.
+     *
+     * @param sql
+     *            the SQL string used to define the statement
+     * @param columnNames
+     *            column names
+     *
+     * @return the PStmtKey created for the given arguments.
+     */
+    protected PStmtKey createKey(final String sql, final String[] columnNames) {
+        return new PStmtKey(normalizeSQL(sql), getCatalogOrNull(), getSchemaOrNull(), columnNames);
+    }
+
+    /**
+     * {@link KeyedPooledObjectFactory} method for destroying PoolablePreparedStatements and PoolableCallableStatements.
+     * Closes the underlying statement.
+     *
+     * @param key
+     *            ignored
+     * @param pooledObject
+     *            the wrapped pooled statement to be destroyed.
+     */
+    @Override
+    public void destroyObject(final PStmtKey key, final PooledObject<DelegatingPreparedStatement> pooledObject)
+            throws Exception {
+        pooledObject.getObject().getInnermostDelegate().close();
+    }
+
+    private String getCatalogOrNull() {
+        String catalog = null;
+        try {
+            catalog = getCatalog();
+        } catch (final SQLException ignored) {
+            // Ignored
+        }
+        return catalog;
+    }
+
+    private String getSchemaOrNull() {
+        String schema = null;
+        try {
+            schema = getSchema();
+        } catch (final SQLException ignored) {
+            // Ignored
+        }
+        return schema;
+    }
+
+    /**
+     * Returns the prepared statement pool we're using.
+     *
+     * @return statement pool
+     * @since 2.8.0
+     */
+    public KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> getStatementPool() {
+        return pstmtPool;
+    }
+
+    /**
+     * {@link KeyedPooledObjectFactory} method for creating {@link PoolablePreparedStatement}s or
+     * {@link PoolableCallableStatement}s. The {@code stmtType} field in the key determines whether a
+     * PoolablePreparedStatement or PoolableCallableStatement is created.
+     *
+     * @param key
+     *            the key for the {@link PreparedStatement} to be created
+     * @see #createKey(String, int, int, StatementType)
+     */
+    @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(getDelegate());
+            @SuppressWarnings({"rawtypes", "unchecked" }) // Unable to find way to avoid this
+            final PoolablePreparedStatement pps = new PoolablePreparedStatement(statement, key, pstmtPool, this);
+            return new DefaultPooledObject<>(pps);
+        }
+        final CallableStatement statement = (CallableStatement) key.createStatement(getDelegate());
+        final PoolableCallableStatement pcs = new PoolableCallableStatement(statement, key, pstmtPool, this);
+        return new DefaultPooledObject<>(pcs);
+    }
+
+    /**
+     * Normalizes the given SQL statement, producing a canonical form that is semantically equivalent to the original.
+     *
+     * @param sql The statement to be normalized.
+     *
+     * @return The canonical form of the supplied SQL statement.
+     */
+    protected String normalizeSQL(final String sql) {
+        return sql.trim();
+    }
+
+    /**
+     * {@link KeyedPooledObjectFactory} method for passivating {@link PreparedStatement}s or {@link CallableStatement}s.
+     * 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 the pool.
+     *
+     * @param key
+     *            a {@link PStmtKey} for the given arguments
+     * @return a {@link PoolableCallableStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    private CallableStatement prepareCall(final PStmtKey key) throws SQLException {
+        return (CallableStatement) prepareStatement(key);
+    }
+
+    /**
+     * Creates or obtains a {@link CallableStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the CallableStatement
+     * @return a {@link PoolableCallableStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public CallableStatement prepareCall(final String sql) throws SQLException {
+        return prepareCall(createKey(sql, StatementType.CALLABLE_STATEMENT));
+    }
+
+    /**
+     * Creates or obtains a {@link CallableStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the CallableStatement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     * @return a {@link PoolableCallableStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency)
+            throws SQLException {
+        return prepareCall(createKey(sql, resultSetType, resultSetConcurrency, StatementType.CALLABLE_STATEMENT));
+    }
+
+    /**
+     * Creates or obtains a {@link CallableStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the CallableStatement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     * @param resultSetHoldability
+     *            result set holdability
+     * @return a {@link PoolableCallableStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public CallableStatement prepareCall(final String sql, final int resultSetType, final int resultSetConcurrency,
+            final int resultSetHoldability) throws SQLException {
+        return prepareCall(createKey(sql, resultSetType, resultSetConcurrency,
+                resultSetHoldability, StatementType.CALLABLE_STATEMENT));
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from the pool.
+     *
+     * @param key
+     *            a {@link PStmtKey} for the given arguments
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    private PreparedStatement prepareStatement(final PStmtKey key) throws SQLException {
+        if (null == pstmtPool) {
+            throw new SQLException("Statement pool is null - closed or invalid PoolingConnection.");
+        }
+        try {
+            return pstmtPool.borrowObject(key);
+        } catch (final NoSuchElementException e) {
+            throw new SQLException("MaxOpenPreparedStatements limit reached", e);
+        } 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 the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the PreparedStatement
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public PreparedStatement prepareStatement(final String sql) throws SQLException {
+        return prepareStatement(createKey(sql));
+    }
+
+    /*
+     * Creates or obtains a {@link PreparedStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the PreparedStatement
+     * @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
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
+        return prepareStatement(createKey(sql, autoGeneratedKeys));
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the PreparedStatement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency)
+            throws SQLException {
+        return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency));
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the PreparedStatement
+     * @param resultSetType
+     *            result set type
+     * @param resultSetConcurrency
+     *            result set concurrency
+     * @param resultSetHoldability
+     *            result set holdability
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency,
+            final int resultSetHoldability) throws SQLException {
+        return prepareStatement(createKey(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the PreparedStatement
+     * @param columnIndexes
+     *            An array of column indexes indicating the columns that should be returned from the inserted row or
+     *            rows.
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     *
+     */
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
+        return prepareStatement(createKey(sql, columnIndexes));
+    }
+
+    /**
+     * Creates or obtains a {@link PreparedStatement} from the pool.
+     *
+     * @param sql
+     *            the SQL string used to define the PreparedStatement
+     * @param columnNames
+     *            column names
+     * @return a {@link PoolablePreparedStatement}
+     * @throws SQLException
+     *             Wraps an underlying exception.
+     */
+    @Override
+    public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
+        return prepareStatement(createKey(sql, columnNames));
+    }
+
+    /**
+     * Sets whether the pool of statements should be cleared when the connection is returned to its pool.
+     * Default is false.
+     *
+     * @param clearStatementPoolOnReturn clear or not
+     * @since 2.8.0
+     */
+    public void setClearStatementPoolOnReturn(final boolean clearStatementPoolOnReturn) {
+        this.clearStatementPoolOnReturn = clearStatementPoolOnReturn;
+    }
+
+    /**
+     * Sets the prepared statement pool.
+     *
+     * @param pool
+     *            the prepared statement pool.
+     */
+    public void setStatementPool(final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> pool) {
+        pstmtPool = pool;
+    }
+
+    @Override
+    public synchronized String toString() {
+        if (pstmtPool != null) {
+            return "PoolingConnection: " + pstmtPool.toString();
+        }
+        return "PoolingConnection: null";
+    }
+
+    /**
+     * {@link KeyedPooledObjectFactory} method for validating pooled statements. Currently always returns true.
+     *
+     * @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/PoolingDriver.java b/src/main/java/org/apache/commons/dbcp2/PoolingDriver.java
index 234490c4..7a683553 100644
--- a/src/main/java/org/apache/commons/dbcp2/PoolingDriver.java
+++ b/src/main/java/org/apache/commons/dbcp2/PoolingDriver.java
@@ -1,260 +1,260 @@
-/*
- * 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;
-
-import java.sql.Connection;
-import java.sql.Driver;
-import java.sql.DriverManager;
-import java.sql.DriverPropertyInfo;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.util.HashMap;
-import java.util.NoSuchElementException;
-import java.util.Properties;
-import java.util.logging.Logger;
-
-import org.apache.commons.pool2.ObjectPool;
-
-/**
- * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
- *
- * @since 2.0
- */
-public class PoolingDriver implements Driver {
-
-    /**
-     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
-     *
-     * @since 2.0
-     */
-    private class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
-
-        private final ObjectPool<? extends Connection> pool;
-
-        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
-            super(delegate);
-            this.pool = pool;
-        }
-
-        /**
-         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
-         */
-        @Override
-        public Connection getDelegate() {
-            if (isAccessToUnderlyingConnectionAllowed()) {
-                return super.getDelegate();
-            }
-            return null;
-        }
-
-        /**
-         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
-         */
-        @Override
-        public Connection getInnermostDelegate() {
-            if (isAccessToUnderlyingConnectionAllowed()) {
-                return super.getInnermostDelegate();
-            }
-            return null;
-        }
-    }
-
-    private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {};
-
-    /* Register myself with the {@link DriverManager}. */
-    static {
-        try {
-            DriverManager.registerDriver(new PoolingDriver());
-        } catch (final Exception e) {
-            // ignore
-        }
-    }
-
-    /** The map of registered pools. */
-    protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
-
-    /** My URL prefix */
-    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
-
-    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
-
-    // version numbers
-    protected static final int MAJOR_VERSION = 1;
-
-    protected static final int MINOR_VERSION = 0;
-
-    /** Controls access to the underlying connection */
-    private final boolean accessToUnderlyingConnectionAllowed;
-
-    /**
-     * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
-     */
-    public PoolingDriver() {
-        this(true);
-    }
-
-    /**
-     * For unit testing purposes.
-     *
-     * @param accessToUnderlyingConnectionAllowed
-     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
-     */
-    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
-        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
-    }
-
-    @Override
-    public boolean acceptsURL(final String url) throws SQLException {
-        return url != null && url.startsWith(URL_PREFIX);
-    }
-
-    /**
-     * Closes a named pool.
-     *
-     * @param name
-     *            The pool name.
-     * @throws SQLException
-     *             Thrown when a problem is caught closing the pool.
-     */
-    public synchronized void closePool(final String name) throws SQLException {
-        @SuppressWarnings("resource")
-        final ObjectPool<? extends Connection> pool = pools.get(name);
-        if (pool != null) {
-            pools.remove(name);
-            try {
-                pool.close();
-            } catch (final Exception e) {
-                throw new SQLException("Error closing pool " + name, e);
-            }
-        }
-    }
-
-    @Override
-    public Connection connect(final String url, final Properties info) throws SQLException {
-        if (acceptsURL(url)) {
-            final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
-
-            try {
-                final Connection conn = pool.borrowObject();
-                if (conn == null) {
-                    return null;
-                }
-                return new PoolGuardConnectionWrapper(pool, conn);
-            } catch (final NoSuchElementException e) {
-                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
-            } catch (final SQLException | RuntimeException e) {
-                throw e;
-            } catch (final Exception e) {
-                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Gets the connection pool for the given name.
-     *
-     * @param name
-     *            The pool name
-     * @return The pool
-     * @throws SQLException
-     *             Thrown when the named pool is not registered.
-     */
-    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
-        final ObjectPool<? extends Connection> pool = pools.get(name);
-        if (null == pool) {
-            throw new SQLException("Pool not registered: " + name);
-        }
-        return pool;
-    }
-
-    @Override
-    public int getMajorVersion() {
-        return MAJOR_VERSION;
-    }
-
-    @Override
-    public int getMinorVersion() {
-        return MINOR_VERSION;
-    }
-
-    @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    /**
-     * Gets the pool names.
-     *
-     * @return the pool names.
-     */
-    public synchronized String[] getPoolNames() {
-        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
-    }
-
-    @Override
-    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
-        return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
-    }
-    /**
-     * Invalidates the given connection.
-     *
-     * @param conn
-     *            connection to invalidate
-     * @throws SQLException
-     *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
-     *             the connection
-     */
-    public void invalidateConnection(final Connection conn) throws SQLException {
-        if (!(conn instanceof PoolGuardConnectionWrapper)) {
-            throw new SQLException("Invalid connection class");
-        }
-        final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
-        @SuppressWarnings("unchecked")
-        final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
-        try {
-            pool.invalidateObject(pgconn.getDelegateInternal());
-        } catch (final Exception e) {
-            // Ignore.
-        }
-    }
-
-    /**
-     * Returns the value of the accessToUnderlyingConnectionAllowed property.
-     *
-     * @return true if access to the underlying is allowed, false otherwise.
-     */
-    protected boolean isAccessToUnderlyingConnectionAllowed() {
-        return accessToUnderlyingConnectionAllowed;
-    }
-    @Override
-    public boolean jdbcCompliant() {
-        return true;
-    }
-
-    /**
-     * Registers a named pool.
-     *
-     * @param name
-     *            The pool name.
-     * @param pool
-     *            The pool.
-     */
-    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
-        pools.put(name, pool);
-    }
-}
+/*
+ * 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;
+
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.DriverPropertyInfo;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.HashMap;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import org.apache.commons.pool2.ObjectPool;
+
+/**
+ * A {@link Driver} implementation that obtains {@link Connection}s from a registered {@link ObjectPool}.
+ *
+ * @since 2.0
+ */
+public class PoolingDriver implements Driver {
+
+    /**
+     * PoolGuardConnectionWrapper is a Connection wrapper that makes sure a closed connection cannot be used anymore.
+     *
+     * @since 2.0
+     */
+    private class PoolGuardConnectionWrapper extends DelegatingConnection<Connection> {
+
+        private final ObjectPool<? extends Connection> pool;
+
+        PoolGuardConnectionWrapper(final ObjectPool<? extends Connection> pool, final Connection delegate) {
+            super(delegate);
+            this.pool = pool;
+        }
+
+        /**
+         * @see org.apache.commons.dbcp2.DelegatingConnection#getDelegate()
+         */
+        @Override
+        public Connection getDelegate() {
+            if (isAccessToUnderlyingConnectionAllowed()) {
+                return super.getDelegate();
+            }
+            return null;
+        }
+
+        /**
+         * @see org.apache.commons.dbcp2.DelegatingConnection#getInnermostDelegate()
+         */
+        @Override
+        public Connection getInnermostDelegate() {
+            if (isAccessToUnderlyingConnectionAllowed()) {
+                return super.getInnermostDelegate();
+            }
+            return null;
+        }
+    }
+
+    private static final DriverPropertyInfo[] EMPTY_DRIVER_PROPERTY_INFO_ARRAY = {};
+
+    /* Register myself with the {@link DriverManager}. */
+    static {
+        try {
+            DriverManager.registerDriver(new PoolingDriver());
+        } catch (final Exception ignored) {
+            // Ignored
+        }
+    }
+
+    /** The map of registered pools. */
+    protected static final HashMap<String, ObjectPool<? extends Connection>> pools = new HashMap<>();
+
+    /** My URL prefix */
+    public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
+
+    protected static final int URL_PREFIX_LEN = URL_PREFIX.length();
+
+    // version numbers
+    protected static final int MAJOR_VERSION = 1;
+
+    protected static final int MINOR_VERSION = 0;
+
+    /** Controls access to the underlying connection */
+    private final boolean accessToUnderlyingConnectionAllowed;
+
+    /**
+     * Constructs a new driver with {@code accessToUnderlyingConnectionAllowed} enabled.
+     */
+    public PoolingDriver() {
+        this(true);
+    }
+
+    /**
+     * For unit testing purposes.
+     *
+     * @param accessToUnderlyingConnectionAllowed
+     *            Do {@link DelegatingConnection}s created by this driver permit access to the delegate?
+     */
+    protected PoolingDriver(final boolean accessToUnderlyingConnectionAllowed) {
+        this.accessToUnderlyingConnectionAllowed = accessToUnderlyingConnectionAllowed;
+    }
+
+    @Override
+    public boolean acceptsURL(final String url) throws SQLException {
+        return url != null && url.startsWith(URL_PREFIX);
+    }
+
+    /**
+     * Closes a named pool.
+     *
+     * @param name
+     *            The pool name.
+     * @throws SQLException
+     *             Thrown when a problem is caught closing the pool.
+     */
+    public synchronized void closePool(final String name) throws SQLException {
+        @SuppressWarnings("resource")
+        final ObjectPool<? extends Connection> pool = pools.get(name);
+        if (pool != null) {
+            pools.remove(name);
+            try {
+                pool.close();
+            } catch (final Exception e) {
+                throw new SQLException("Error closing pool " + name, e);
+            }
+        }
+    }
+
+    @Override
+    public Connection connect(final String url, final Properties info) throws SQLException {
+        if (acceptsURL(url)) {
+            final ObjectPool<? extends Connection> pool = getConnectionPool(url.substring(URL_PREFIX_LEN));
+
+            try {
+                final Connection conn = pool.borrowObject();
+                if (conn == null) {
+                    return null;
+                }
+                return new PoolGuardConnectionWrapper(pool, conn);
+            } catch (final NoSuchElementException e) {
+                throw new SQLException("Cannot get a connection, pool error: " + e.getMessage(), e);
+            } catch (final SQLException | RuntimeException e) {
+                throw e;
+            } catch (final Exception e) {
+                throw new SQLException("Cannot get a connection, general error: " + e.getMessage(), e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the connection pool for the given name.
+     *
+     * @param name
+     *            The pool name
+     * @return The pool
+     * @throws SQLException
+     *             Thrown when the named pool is not registered.
+     */
+    public synchronized ObjectPool<? extends Connection> getConnectionPool(final String name) throws SQLException {
+        final ObjectPool<? extends Connection> pool = pools.get(name);
+        if (null == pool) {
+            throw new SQLException("Pool not registered: " + name);
+        }
+        return pool;
+    }
+
+    @Override
+    public int getMajorVersion() {
+        return MAJOR_VERSION;
+    }
+
+    @Override
+    public int getMinorVersion() {
+        return MINOR_VERSION;
+    }
+
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    /**
+     * Gets the pool names.
+     *
+     * @return the pool names.
+     */
+    public synchronized String[] getPoolNames() {
+        return pools.keySet().toArray(Utils.EMPTY_STRING_ARRAY);
+    }
+
+    @Override
+    public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) {
+        return EMPTY_DRIVER_PROPERTY_INFO_ARRAY;
+    }
+    /**
+     * Invalidates the given connection.
+     *
+     * @param conn
+     *            connection to invalidate
+     * @throws SQLException
+     *             if the connection is not a {@code PoolGuardConnectionWrapper} or an error occurs invalidating
+     *             the connection
+     */
+    public void invalidateConnection(final Connection conn) throws SQLException {
+        if (!(conn instanceof PoolGuardConnectionWrapper)) {
+            throw new SQLException("Invalid connection class");
+        }
+        final PoolGuardConnectionWrapper pgconn = (PoolGuardConnectionWrapper) conn;
+        @SuppressWarnings("unchecked")
+        final ObjectPool<Connection> pool = (ObjectPool<Connection>) pgconn.pool;
+        try {
+            pool.invalidateObject(pgconn.getDelegateInternal());
+        } catch (final Exception ignored) {
+            // Ignored.
+        }
+    }
+
+    /**
+     * Returns the value of the accessToUnderlyingConnectionAllowed property.
+     *
+     * @return true if access to the underlying is allowed, false otherwise.
+     */
+    protected boolean isAccessToUnderlyingConnectionAllowed() {
+        return accessToUnderlyingConnectionAllowed;
+    }
+    @Override
+    public boolean jdbcCompliant() {
+        return true;
+    }
+
+    /**
+     * Registers a named pool.
+     *
+     * @param name
+     *            The pool name.
+     * @param pool
+     *            The pool.
+     */
+    public synchronized void registerPool(final String name, final ObjectPool<? extends Connection> pool) {
+        pools.put(name, pool);
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/Utils.java b/src/main/java/org/apache/commons/dbcp2/Utils.java
index 34f17151..85ce3b90 100644
--- a/src/main/java/org/apache/commons/dbcp2/Utils.java
+++ b/src/main/java/org/apache/commons/dbcp2/Utils.java
@@ -1,206 +1,206 @@
-/*
- * 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;
-
-import java.sql.Connection;
-import java.sql.ResultSet;
-import java.sql.Statement;
-import java.text.MessageFormat;
-import java.util.HashSet;
-import java.util.Properties;
-import java.util.ResourceBundle;
-import java.util.Set;
-
-/**
- * Utility methods.
- *
- * @since 2.0
- */
-public final class Utils {
-
-    private static final ResourceBundle messages = ResourceBundle
-        .getBundle(Utils.class.getPackage().getName() + ".LocalStrings");
-
-    /**
-     * Whether the security manager is enabled.
-     *
-     * @deprecated No replacement.
-     */
-    @Deprecated
-    public static final boolean IS_SECURITY_ENABLED = isSecurityEnabled();
-
-    /** Any SQL_STATE starting with this value is considered a fatal disconnect */
-    public static final String DISCONNECTION_SQL_CODE_PREFIX = "08";
-
-    /**
-     * SQL codes of fatal connection errors.
-     * <ul>
-     * <li>57P01 (Admin shutdown)</li>
-     * <li>57P02 (Crash shutdown)</li>
-     * <li>57P03 (Cannot connect now)</li>
-     * <li>01002 (SQL92 disconnect error)</li>
-     * <li>JZ0C0 (Sybase disconnect error)</li>
-     * <li>JZ0C1 (Sybase disconnect error)</li>
-     * </ul>
-     */
-    public static final Set<String> DISCONNECTION_SQL_CODES;
-
-    static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {};
-
-    static final String[] EMPTY_STRING_ARRAY = {};
-    static {
-        DISCONNECTION_SQL_CODES = new HashSet<>();
-        DISCONNECTION_SQL_CODES.add("57P01"); // Admin shutdown
-        DISCONNECTION_SQL_CODES.add("57P02"); // Crash shutdown
-        DISCONNECTION_SQL_CODES.add("57P03"); // Cannot connect now
-        DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error
-        DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error
-        DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error
-    }
-
-    /**
-     * Clones the given char[] if not null.
-     *
-     * @param value may be null.
-     * @return a cloned char[] or null.
-     */
-    public static char[] clone(final char[] value) {
-        return value == null ? null : value.clone();
-    }
-
-    /**
-     * Clones the given {@link Properties} without the standard "user" or "password" entries.
-     *
-     * @param properties may be null
-     * @return a clone of the input without the standard "user" or "password" entries.
-     * @since 2.8.0
-     */
-    public static Properties cloneWithoutCredentials(final Properties properties) {
-        if (properties != null) {
-            final Properties temp = (Properties) properties.clone();
-            temp.remove(Constants.KEY_USER);
-            temp.remove(Constants.KEY_PASSWORD);
-            return temp;
-        }
-        return properties;
-    }
-
-    /**
-     * Closes the AutoCloseable (which may be null).
-     *
-     * @param autoCloseable an AutoCloseable, may be {@code null}
-     * @since 2.6.0
-     */
-    public static void closeQuietly(final AutoCloseable autoCloseable) {
-        if (autoCloseable != null) {
-            try {
-                autoCloseable.close();
-            } catch (final Exception e) {
-                // ignored
-            }
-        }
-    }
-
-    /**
-     * Closes the Connection (which may be null).
-     *
-     * @param connection a Connection, may be {@code null}
-     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
-     */
-    @Deprecated
-    public static void closeQuietly(final Connection connection) {
-        closeQuietly((AutoCloseable) connection);
-    }
-
-    /**
-     * Closes the ResultSet (which may be null).
-     *
-     * @param resultSet a ResultSet, may be {@code null}
-     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
-     */
-    @Deprecated
-    public static void closeQuietly(final ResultSet resultSet) {
-        closeQuietly((AutoCloseable) resultSet);
-    }
-
-    /**
-     * Closes the Statement (which may be null).
-     *
-     * @param statement a Statement, may be {@code null}.
-     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
-     */
-    @Deprecated
-    public static void closeQuietly(final Statement statement) {
-        closeQuietly((AutoCloseable) statement);
-    }
-
-    /**
-     * Gets the correct i18n message for the given key.
-     *
-     * @param key The key to look up an i18n message.
-     * @return The i18n message.
-     */
-    public static String getMessage(final String key) {
-        return getMessage(key, (Object[]) null);
-    }
-
-    /**
-     * Gets the correct i18n message for the given key with placeholders replaced by the supplied arguments.
-     *
-     * @param key A message key.
-     * @param args The message arguments.
-     * @return An i18n message.
-     */
-    public static String getMessage(final String key, final Object... args) {
-        final String msg = messages.getString(key);
-        if (args == null || args.length == 0) {
-            return msg;
-        }
-        final MessageFormat mf = new MessageFormat(msg);
-        return mf.format(args, new StringBuffer(), null).toString();
-    }
-
-    static boolean isSecurityEnabled() {
-        return System.getSecurityManager() != null;
-    }
-
-    /**
-     * Converts the given String to a char[].
-     *
-     * @param value may be null.
-     * @return a char[] or null.
-     */
-    public static char[] toCharArray(final String value) {
-        return value != null ? value.toCharArray() : null;
-    }
-
-    /**
-     * Converts the given char[] to a String.
-     *
-     * @param value may be null.
-     * @return a String or null.
-     */
-    public static String toString(final char[] value) {
-        return value == null ? null : String.valueOf(value);
-    }
-
-    private Utils() {
-        // not instantiable
-    }
-
-}
+/*
+ * 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;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+/**
+ * Utility methods.
+ *
+ * @since 2.0
+ */
+public final class Utils {
+
+    private static final ResourceBundle messages = ResourceBundle
+        .getBundle(Utils.class.getPackage().getName() + ".LocalStrings");
+
+    /**
+     * Whether the security manager is enabled.
+     *
+     * @deprecated No replacement.
+     */
+    @Deprecated
+    public static final boolean IS_SECURITY_ENABLED = isSecurityEnabled();
+
+    /** Any SQL_STATE starting with this value is considered a fatal disconnect */
+    public static final String DISCONNECTION_SQL_CODE_PREFIX = "08";
+
+    /**
+     * SQL codes of fatal connection errors.
+     * <ul>
+     * <li>57P01 (Admin shutdown)</li>
+     * <li>57P02 (Crash shutdown)</li>
+     * <li>57P03 (Cannot connect now)</li>
+     * <li>01002 (SQL92 disconnect error)</li>
+     * <li>JZ0C0 (Sybase disconnect error)</li>
+     * <li>JZ0C1 (Sybase disconnect error)</li>
+     * </ul>
+     */
+    public static final Set<String> DISCONNECTION_SQL_CODES;
+
+    static final ResultSet[] EMPTY_RESULT_SET_ARRAY = {};
+
+    static final String[] EMPTY_STRING_ARRAY = {};
+    static {
+        DISCONNECTION_SQL_CODES = new HashSet<>();
+        DISCONNECTION_SQL_CODES.add("57P01"); // Admin shutdown
+        DISCONNECTION_SQL_CODES.add("57P02"); // Crash shutdown
+        DISCONNECTION_SQL_CODES.add("57P03"); // Cannot connect now
+        DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error
+        DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error
+        DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error
+    }
+
+    /**
+     * Clones the given char[] if not null.
+     *
+     * @param value may be null.
+     * @return a cloned char[] or null.
+     */
+    public static char[] clone(final char[] value) {
+        return value == null ? null : value.clone();
+    }
+
+    /**
+     * Clones the given {@link Properties} without the standard "user" or "password" entries.
+     *
+     * @param properties may be null
+     * @return a clone of the input without the standard "user" or "password" entries.
+     * @since 2.8.0
+     */
+    public static Properties cloneWithoutCredentials(final Properties properties) {
+        if (properties != null) {
+            final Properties temp = (Properties) properties.clone();
+            temp.remove(Constants.KEY_USER);
+            temp.remove(Constants.KEY_PASSWORD);
+            return temp;
+        }
+        return properties;
+    }
+
+    /**
+     * Closes the AutoCloseable (which may be null).
+     *
+     * @param autoCloseable an AutoCloseable, may be {@code null}
+     * @since 2.6.0
+     */
+    public static void closeQuietly(final AutoCloseable autoCloseable) {
+        if (autoCloseable != null) {
+            try {
+                autoCloseable.close();
+            } catch (final Exception ignored) {
+                // ignored
+            }
+        }
+    }
+
+    /**
+     * Closes the Connection (which may be null).
+     *
+     * @param connection a Connection, may be {@code null}
+     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
+     */
+    @Deprecated
+    public static void closeQuietly(final Connection connection) {
+        closeQuietly((AutoCloseable) connection);
+    }
+
+    /**
+     * Closes the ResultSet (which may be null).
+     *
+     * @param resultSet a ResultSet, may be {@code null}
+     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
+     */
+    @Deprecated
+    public static void closeQuietly(final ResultSet resultSet) {
+        closeQuietly((AutoCloseable) resultSet);
+    }
+
+    /**
+     * Closes the Statement (which may be null).
+     *
+     * @param statement a Statement, may be {@code null}.
+     * @deprecated Use {@link #closeQuietly(AutoCloseable)}.
+     */
+    @Deprecated
+    public static void closeQuietly(final Statement statement) {
+        closeQuietly((AutoCloseable) statement);
+    }
+
+    /**
+     * Gets the correct i18n message for the given key.
+     *
+     * @param key The key to look up an i18n message.
+     * @return The i18n message.
+     */
+    public static String getMessage(final String key) {
+        return getMessage(key, (Object[]) null);
+    }
+
+    /**
+     * Gets the correct i18n message for the given key with placeholders replaced by the supplied arguments.
+     *
+     * @param key A message key.
+     * @param args The message arguments.
+     * @return An i18n message.
+     */
+    public static String getMessage(final String key, final Object... args) {
+        final String msg = messages.getString(key);
+        if (args == null || args.length == 0) {
+            return msg;
+        }
+        final MessageFormat mf = new MessageFormat(msg);
+        return mf.format(args, new StringBuffer(), null).toString();
+    }
+
+    static boolean isSecurityEnabled() {
+        return System.getSecurityManager() != null;
+    }
+
+    /**
+     * Converts the given String to a char[].
+     *
+     * @param value may be null.
+     * @return a char[] or null.
+     */
+    public static char[] toCharArray(final String value) {
+        return value != null ? value.toCharArray() : null;
+    }
+
+    /**
+     * Converts the given char[] to a String.
+     *
+     * @param value may be null.
+     * @return a String or null.
+     */
+    public static String toString(final char[] value) {
+        return value == null ? null : String.valueOf(value);
+    }
+
+    private Utils() {
+        // not instantiable
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java
index 64ad0a0e..6b8fb227 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java
@@ -1,1331 +1,1331 @@
-/*
- * 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.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Serializable;
-import java.nio.charset.StandardCharsets;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
-import java.time.Duration;
-import java.util.Properties;
-import java.util.logging.Logger;
-
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.Referenceable;
-import javax.sql.ConnectionPoolDataSource;
-import javax.sql.DataSource;
-import javax.sql.PooledConnection;
-
-import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
-import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
-import org.apache.commons.pool2.impl.GenericObjectPool;
-
-/**
- * <p>
- * The base class for {@code SharedPoolDataSource} and {@code PerUserPoolDataSource}. Many of the
- * configuration properties are shared and defined here. This class is declared public in order to allow particular
- * usage with commons-beanutils; do not make direct use of it outside of <em>commons-dbcp2</em>.
- * </p>
- *
- * <p>
- * A J2EE container will normally provide some method of initializing the {@code DataSource} whose attributes are
- * presented as bean getters/setters and then deploying it via JNDI. It is then available to an application as a source
- * of pooled logical connections to the database. The pool needs a source of physical connections. This source is in the
- * form of a {@code ConnectionPoolDataSource} that can be specified via the {@link #setDataSourceName(String)} used
- * to lookup the source via JNDI.
- * </p>
- *
- * <p>
- * Although normally used within a JNDI environment, A DataSource can be instantiated and initialized as any bean. In
- * this case the {@code ConnectionPoolDataSource} will likely be instantiated in a similar manner. This class
- * allows the physical source of connections to be attached directly to this pool using the
- * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method.
- * </p>
- *
- * <p>
- * The dbcp package contains an adapter, {@link org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS}, that can be
- * used to allow the use of {@code DataSource}'s based on this class with JDBC driver implementations that do not
- * supply a {@code ConnectionPoolDataSource}, but still provide a {@link java.sql.Driver} implementation.
- * </p>
- *
- * <p>
- * The <a href="package-summary.html">package documentation</a> contains an example using Apache Tomcat and JNDI and it
- * also contains a non-JNDI example.
- * </p>
- *
- * @since 2.0
- */
-public abstract class InstanceKeyDataSource implements DataSource, Referenceable, Serializable, AutoCloseable {
-
-    private static final long serialVersionUID = -6819270431752240878L;
-
-    private static final String GET_CONNECTION_CALLED = "A Connection was already requested from this source, "
-            + "further initialization is not allowed.";
-    private static final String BAD_TRANSACTION_ISOLATION = "The requested TransactionIsolation level is invalid.";
-
-    /**
-     * Internal constant to indicate the level is not set.
-     */
-    protected static final int UNKNOWN_TRANSACTIONISOLATION = -1;
-
-    /** Guards property setters - once true, setters throw IllegalStateException */
-    private volatile boolean getConnectionCalled;
-
-    /** Underlying source of PooledConnections */
-    private ConnectionPoolDataSource dataSource;
-
-    /** DataSource Name used to find the ConnectionPoolDataSource */
-    private String dataSourceName;
-
-    /** Description */
-    private String description;
-
-    /** Environment that may be used to set up a JNDI initial context. */
-    private Properties jndiEnvironment;
-
-    /** Login Timeout */
-    private Duration loginTimeoutDuration = Duration.ZERO;
-
-    /** Log stream */
-    private PrintWriter logWriter;
-
-    /** Instance key */
-    private String instanceKey;
-
-    // Pool properties
-    private boolean defaultBlockWhenExhausted = BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED;
-    private String defaultEvictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME;
-    private boolean defaultLifo = BaseObjectPoolConfig.DEFAULT_LIFO;
-    private int defaultMaxIdle = GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY;
-    private int defaultMaxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
-    private Duration defaultMaxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT;
-    private Duration defaultMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
-    private int defaultMinIdle = GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY;
-    private int defaultNumTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
-    private Duration defaultSoftMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION;
-    private boolean defaultTestOnCreate = BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
-    private boolean defaultTestOnBorrow = BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW;
-    private boolean defaultTestOnReturn = BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN;
-    private boolean defaultTestWhileIdle = BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE;
-    private Duration defaultDurationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
-
-    // Connection factory properties
-    private String validationQuery;
-    private Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1);
-    private boolean rollbackAfterValidation;
-    private Duration maxConnDuration = Duration.ofMillis(-1);
-
-    // Connection properties
-    private Boolean defaultAutoCommit;
-    private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION;
-    private Boolean defaultReadOnly;
-
-    /**
-     * Default no-arg constructor for Serialization.
-     */
-    public InstanceKeyDataSource() {
-    }
-
-    /**
-     * Throws an IllegalStateException, if a PooledConnection has already been requested.
-     *
-     * @throws IllegalStateException Thrown if a PooledConnection has already been requested.
-     */
-    protected void assertInitializationAllowed() throws IllegalStateException {
-        if (getConnectionCalled) {
-            throw new IllegalStateException(GET_CONNECTION_CALLED);
-        }
-    }
-
-    /**
-     * Closes the connection pool being maintained by this datasource.
-     */
-    @Override
-    public abstract void close() throws Exception;
-
-    private void closeDueToException(final PooledConnectionAndInfo info) {
-        if (info != null) {
-            try {
-                info.getPooledConnection().getConnection().close();
-            } catch (final Exception e) {
-                // do not throw this exception because we are in the middle
-                // of handling another exception. But record it because
-                // it potentially leaks connections from the pool.
-                getLogWriter().println("[ERROR] Could not return connection to pool during exception handling. " + e.getMessage());
-            }
-        }
-    }
-
-    /**
-     * Attempts to establish a database connection.
-     */
-    @Override
-    public Connection getConnection() throws SQLException {
-        return getConnection(null, null);
-    }
-
-    /**
-     * Attempts to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)} with the
-     * provided user name and password. The password on the {@code PooledConnectionAndInfo} instance returned by
-     * {@code getPooledConnectionAndInfo} is compared to the {@code password} parameter. If the comparison
-     * fails, a database connection using the supplied user name and password is attempted. If the connection attempt
-     * fails, an SQLException is thrown, indicating that the given password did not match the password used to create
-     * the pooled connection. If the connection attempt succeeds, this means that the database password has been
-     * changed. In this case, the {@code PooledConnectionAndInfo} instance retrieved with the old password is
-     * destroyed and the {@code getPooledConnectionAndInfo} is repeatedly invoked until a
-     * {@code PooledConnectionAndInfo} instance with the new password is returned.
-     */
-    @Override
-    public Connection getConnection(final String userName, final String userPassword) throws SQLException {
-        if (instanceKey == null) {
-            throw new SQLException("Must set the ConnectionPoolDataSource "
-                    + "through setDataSourceName or setConnectionPoolDataSource" + " before calling getConnection.");
-        }
-        getConnectionCalled = true;
-        PooledConnectionAndInfo info = null;
-        try {
-            info = getPooledConnectionAndInfo(userName, userPassword);
-        } catch (final RuntimeException | SQLException e) {
-            closeDueToException(info);
-            throw e;
-        } catch (final Exception e) {
-            closeDueToException(info);
-            throw new SQLException("Cannot borrow connection from pool", e);
-        }
-
-        // Password on PooledConnectionAndInfo does not match
-        if (!(null == userPassword ? null == info.getPassword() : userPassword.equals(info.getPassword()))) {
-            try { // See if password has changed by attempting connection
-                testCPDS(userName, userPassword);
-            } catch (final SQLException ex) {
-                // Password has not changed, so refuse client, but return connection to the pool
-                closeDueToException(info);
-                throw new SQLException(
-                        "Given password did not match password used" + " to create the PooledConnection.", ex);
-            } catch (final javax.naming.NamingException ne) {
-                throw new SQLException("NamingException encountered connecting to database", ne);
-            }
-            /*
-             * Password must have changed -> destroy connection and keep retrying until we get a new, good one,
-             * destroying any idle connections with the old password as we pull them from the pool.
-             */
-            final UserPassKey upkey = info.getUserPassKey();
-            final PooledConnectionManager manager = getConnectionManager(upkey);
-            // Destroy and remove from pool
-            manager.invalidate(info.getPooledConnection());
-            // Reset the password on the factory if using CPDSConnectionFactory
-            manager.setPassword(upkey.getPassword());
-            info = null;
-            for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return
-                try {
-                    info = getPooledConnectionAndInfo(userName, userPassword);
-                } catch (final RuntimeException | SQLException e) {
-                    closeDueToException(info);
-                    throw e;
-                } catch (final Exception e) {
-                    closeDueToException(info);
-                    throw new SQLException("Cannot borrow connection from pool", e);
-                }
-                if (info != null && userPassword != null && userPassword.equals(info.getPassword())) {
-                    break;
-                }
-                if (info != null) {
-                    manager.invalidate(info.getPooledConnection());
-                }
-                info = null;
-            }
-            if (info == null) {
-                throw new SQLException("Cannot borrow connection from pool - password change failure.");
-            }
-        }
-
-        final Connection connection = info.getPooledConnection().getConnection();
-        try {
-            setupDefaults(connection, userName);
-            connection.clearWarnings();
-            return connection;
-        } catch (final SQLException ex) {
-            try {
-                connection.close();
-            } catch (final Exception exc) {
-                getLogWriter().println("ignoring exception during close: " + exc);
-            }
-            throw ex;
-        }
-    }
-
-    protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey);
-
-    /**
-     * Gets the value of connectionPoolDataSource. This method will return null, if the backing data source is being
-     * accessed via JNDI.
-     *
-     * @return value of connectionPoolDataSource.
-     */
-    public ConnectionPoolDataSource getConnectionPoolDataSource() {
-        return dataSource;
-    }
-
-    /**
-     * Gets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source
-     * from a JNDI service provider.
-     *
-     * @return value of dataSourceName.
-     */
-    public String getDataSourceName() {
-        return dataSourceName;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user
-     *         pool.
-     */
-    public boolean getDefaultBlockWhenExhausted() {
-        return this.defaultBlockWhenExhausted;
-    }
-
-    /**
-     * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
-     *
-     * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
-     * @since 2.10.0
-     */
-    public Duration getDefaultDurationBetweenEvictionRuns() {
-        return this.defaultDurationBetweenEvictionRuns;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
-     * pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
-     *         pool.
-     */
-    public String getDefaultEvictionPolicyClassName() {
-        return this.defaultEvictionPolicyClassName;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
-     */
-    public boolean getDefaultLifo() {
-        return this.defaultLifo;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
-     */
-    public int getDefaultMaxIdle() {
-        return this.defaultMaxIdle;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
-     */
-    public int getDefaultMaxTotal() {
-        return this.defaultMaxTotal;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
-     * @since 2.9.0
-     */
-    public Duration getDefaultMaxWait() {
-        return this.defaultMaxWaitDuration;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
-     * @deprecated Use {@link #getDefaultMaxWait()}.
-     */
-    @Deprecated
-    public long getDefaultMaxWaitMillis() {
-        return getDefaultMaxWait().toMillis();
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
-     * pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per
-     *         user pool.
-     * @since 2.10.0
-     */
-    public Duration getDefaultMinEvictableIdleDuration() {
-        return this.defaultMinEvictableIdleDuration;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
-     * pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per
-     *         user pool.
-     * @deprecated Use {@link #getDefaultMinEvictableIdleDuration()}.
-     */
-    @Deprecated
-    public long getDefaultMinEvictableIdleTimeMillis() {
-        return this.defaultMinEvictableIdleDuration.toMillis();
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
-     */
-    public int getDefaultMinIdle() {
-        return this.defaultMinIdle;
-    }
-
-    /**
-     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
-     * pool.
-     *
-     * @return The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
-     *         pool.
-     */
-    public int getDefaultNumTestsPerEvictionRun() {
-        return this.defaultNumTestsPerEvictionRun;
-    }
-
-    /**
-     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     *
-     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     * @since 2.10.0
-     */
-    public Duration getDefaultSoftMinEvictableIdleDuration() {
-        return this.defaultSoftMinEvictableIdleDuration;
-    }
-
-    /**
-     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     *
-     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     * @deprecated Use {@link #getDefaultSoftMinEvictableIdleDuration()}.
-     */
-    @Deprecated
-    public long getDefaultSoftMinEvictableIdleTimeMillis() {
-        return this.defaultSoftMinEvictableIdleDuration.toMillis();
-    }
-
-    /**
-     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
-     *
-     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTestOnBorrow()} for each per user pool.
-     */
-    public boolean getDefaultTestOnBorrow() {
-        return this.defaultTestOnBorrow;
-    }
-
-    /**
-     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnCreate()} for each per user pool.
-     *
-     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTestOnCreate()} for each per user pool.
-     */
-    public boolean getDefaultTestOnCreate() {
-        return this.defaultTestOnCreate;
-    }
-
-    /**
-     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnReturn()} for each per user pool.
-     *
-     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTestOnReturn()} for each per user pool.
-     */
-    public boolean getDefaultTestOnReturn() {
-        return this.defaultTestOnReturn;
-    }
-
-    /**
-     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
-     *
-     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *         GenericObjectPool#getTestWhileIdle()} for each per user pool.
-     */
-    public boolean getDefaultTestWhileIdle() {
-        return this.defaultTestWhileIdle;
-    }
-
-    /**
-     * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
-     *
-     * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
-     * @deprecated Use {@link #getDefaultDurationBetweenEvictionRuns()}.
-     */
-    @Deprecated
-    public long getDefaultTimeBetweenEvictionRunsMillis() {
-        return this.defaultDurationBetweenEvictionRuns.toMillis();
-    }
-
-    /**
-     * Gets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
-     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). If this method returns
-     * -1, the default is JDBC driver dependent.
-     *
-     * @return value of defaultTransactionIsolation.
-     */
-    public int getDefaultTransactionIsolation() {
-        return defaultTransactionIsolation;
-    }
-
-    /**
-     * Gets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
-     * datasource. It serves no internal purpose.
-     *
-     * @return value of description.
-     */
-    public String getDescription() {
-        return description;
-    }
-
-    /**
-     * Gets the instance key.
-     *
-     * @return the instance key.
-     */
-    protected String getInstanceKey() {
-        return instanceKey;
-    }
-
-    /**
-     * Gets the value of jndiEnvironment which is used when instantiating a JNDI InitialContext. This InitialContext is
-     * used to locate the back end ConnectionPoolDataSource.
-     *
-     * @param key
-     *            JNDI environment key.
-     * @return value of jndiEnvironment.
-     */
-    public String getJndiEnvironment(final String key) {
-        String value = null;
-        if (jndiEnvironment != null) {
-            value = jndiEnvironment.getProperty(key);
-        }
-        return value;
-    }
-
-    /**
-     * Gets the value of loginTimeout.
-     *
-     * @return value of loginTimeout.
-     * @deprecated Use {@link #getLoginTimeoutDuration()}.
-     */
-    @Deprecated
-    @Override
-    public int getLoginTimeout() {
-        return (int) loginTimeoutDuration.getSeconds();
-    }
-
-    /**
-     * Gets the value of loginTimeout.
-     *
-     * @return value of loginTimeout.
-     * @since 2.10.0
-     */
-    public Duration getLoginTimeoutDuration() {
-        return loginTimeoutDuration;
-    }
-
-    /**
-     * Gets the value of logWriter.
-     *
-     * @return value of logWriter.
-     */
-    @Override
-    public PrintWriter getLogWriter() {
-        if (logWriter == null) {
-            logWriter = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
-        }
-        return logWriter;
-    }
-
-    /**
-     * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an
-     * infinite lifetime.
-     *
-     * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an
-     *         infinite lifetime.
-     * @since 2.10.0
-     */
-    public Duration getMaxConnDuration() {
-        return maxConnDuration;
-    }
-
-    /**
-     * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an
-     * infinite lifetime.
-     *
-     * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an
-     *         infinite lifetime.
-     * @deprecated Use {@link #getMaxConnDuration()}.
-     */
-    @Deprecated
-    public Duration getMaxConnLifetime() {
-        return maxConnDuration;
-    }
-
-    /**
-     * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     * infinite lifetime.
-     *
-     * @return The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     *         infinite lifetime.
-     * @deprecated Use {@link #getMaxConnLifetime()}.
-     */
-    @Deprecated
-    public long getMaxConnLifetimeMillis() {
-        return maxConnDuration.toMillis();
-    }
-
-    @Override
-    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
-        throw new SQLFeatureNotSupportedException();
-    }
-
-    /**
-     * This method is protected but can only be implemented in this package because PooledConnectionAndInfo is a package
-     * private type.
-     *
-     * @param userName The user name.
-     * @param userPassword The user password.
-     * @return Matching PooledConnectionAndInfo.
-     * @throws SQLException Connection or registration failure.
-     */
-    protected abstract PooledConnectionAndInfo getPooledConnectionAndInfo(String userName, String userPassword)
-            throws SQLException;
-
-    /**
-     * Gets the SQL query that will be used to validate connections from this pool before returning them to the caller.
-     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
-     * specified, {@link Connection#isValid(int)} will be used to validate connections.
-     *
-     * @return The SQL query that will be used to validate connections from this pool before returning them to the
-     *         caller.
-     */
-    public String getValidationQuery() {
-        return this.validationQuery;
-    }
-
-    /**
-     * Returns the timeout in seconds before the validation query fails.
-     *
-     * @return The timeout in seconds before the validation query fails.
-     * @deprecated Use {@link #getValidationQueryTimeoutDuration()}.
-     */
-    @Deprecated
-    public int getValidationQueryTimeout() {
-        return (int) validationQueryTimeoutDuration.getSeconds();
-    }
-
-    /**
-     * Returns the timeout Duration before the validation query fails.
-     *
-     * @return The timeout Duration before the validation query fails.
-     */
-    public Duration getValidationQueryTimeoutDuration() {
-        return validationQueryTimeoutDuration;
-    }
-
-    /**
-     * Gets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
-     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which
-     * will use the default value for the drive.
-     *
-     * @return value of defaultAutoCommit.
-     */
-    public Boolean isDefaultAutoCommit() {
-        return defaultAutoCommit;
-    }
-
-    /**
-     * Gets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value
-     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which
-     * will use the default value for the drive.
-     *
-     * @return value of defaultReadOnly.
-     */
-    public Boolean isDefaultReadOnly() {
-        return defaultReadOnly;
-    }
-
-    /**
-     * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from
-     * this pool before returning them to the caller.
-     *
-     * @return true if a rollback will be issued after executing the validation query
-     */
-    public boolean isRollbackAfterValidation() {
-        return this.rollbackAfterValidation;
-    }
-
-    @Override
-    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
-        return iface.isInstance(this);
-    }
-
-    /**
-     * Sets the back end ConnectionPoolDataSource. This property should not be set if using JNDI to access the
-     * data source.
-     *
-     * @param dataSource
-     *            Value to assign to connectionPoolDataSource.
-     */
-    public void setConnectionPoolDataSource(final ConnectionPoolDataSource dataSource) {
-        assertInitializationAllowed();
-        if (dataSourceName != null) {
-            throw new IllegalStateException("Cannot set the DataSource, if JNDI is used.");
-        }
-        if (this.dataSource != null) {
-            throw new IllegalStateException("The CPDS has already been set. It cannot be altered.");
-        }
-        this.dataSource = dataSource;
-        instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
-    }
-
-    /**
-     * Sets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source
-     * from a JNDI service provider.
-     *
-     * @param dataSourceName
-     *            Value to assign to dataSourceName.
-     */
-    public void setDataSourceName(final String dataSourceName) {
-        assertInitializationAllowed();
-        if (dataSource != null) {
-            throw new IllegalStateException("Cannot set the JNDI name for the DataSource, if already "
-                    + "set using setConnectionPoolDataSource.");
-        }
-        if (this.dataSourceName != null) {
-            throw new IllegalStateException("The DataSourceName has already been set. " + "It cannot be altered.");
-        }
-        this.dataSourceName = dataSourceName;
-        instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
-    }
-
-    /**
-     * Sets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
-     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which
-     * will use the default value for the drive.
-     *
-     * @param defaultAutoCommit
-     *            Value to assign to defaultAutoCommit.
-     */
-    public void setDefaultAutoCommit(final Boolean defaultAutoCommit) {
-        assertInitializationAllowed();
-        this.defaultAutoCommit = defaultAutoCommit;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool.
-     *
-     * @param blockWhenExhausted
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user
-     *            pool.
-     */
-    public void setDefaultBlockWhenExhausted(final boolean blockWhenExhausted) {
-        assertInitializationAllowed();
-        this.defaultBlockWhenExhausted = blockWhenExhausted;
-    }
-
-    /**
-     * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
-     *
-     * @param defaultDurationBetweenEvictionRuns The default value for
-     *        {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
-     * @since 2.10.0
-     */
-    public void setDefaultDurationBetweenEvictionRuns(final Duration defaultDurationBetweenEvictionRuns) {
-        assertInitializationAllowed();
-        this.defaultDurationBetweenEvictionRuns = defaultDurationBetweenEvictionRuns;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
-     * pool.
-     *
-     * @param evictionPolicyClassName
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per
-     *            user pool.
-     */
-    public void setDefaultEvictionPolicyClassName(final String evictionPolicyClassName) {
-        assertInitializationAllowed();
-        this.defaultEvictionPolicyClassName = evictionPolicyClassName;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
-     *
-     * @param lifo
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
-     */
-    public void setDefaultLifo(final boolean lifo) {
-        assertInitializationAllowed();
-        this.defaultLifo = lifo;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
-     *
-     * @param maxIdle
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
-     */
-    public void setDefaultMaxIdle(final int maxIdle) {
-        assertInitializationAllowed();
-        this.defaultMaxIdle = maxIdle;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
-     *
-     * @param maxTotal
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
-     */
-    public void setDefaultMaxTotal(final int maxTotal) {
-        assertInitializationAllowed();
-        this.defaultMaxTotal = maxTotal;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
-     *
-     * @param maxWaitMillis
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
-     * @since 2.9.0
-     */
-    public void setDefaultMaxWait(final Duration maxWaitMillis) {
-        assertInitializationAllowed();
-        this.defaultMaxWaitDuration = maxWaitMillis;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
-     *
-     * @param maxWaitMillis
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
-     * @deprecated Use {@link #setDefaultMaxWait(Duration)}.
-     */
-    @Deprecated
-    public void setDefaultMaxWaitMillis(final long maxWaitMillis) {
-        setDefaultMaxWait(Duration.ofMillis(maxWaitMillis));
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
-     * pool.
-     *
-     * @param defaultMinEvictableIdleDuration
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each
-     *            per user pool.
-     * @since 2.10.0
-     */
-    public void setDefaultMinEvictableIdle(final Duration defaultMinEvictableIdleDuration) {
-        assertInitializationAllowed();
-        this.defaultMinEvictableIdleDuration = defaultMinEvictableIdleDuration;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
-     * pool.
-     *
-     * @param minEvictableIdleTimeMillis
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each
-     *            per user pool.
-     * @deprecated Use {@link #setDefaultMinEvictableIdle(Duration)}.
-     */
-    @Deprecated
-    public void setDefaultMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
-        assertInitializationAllowed();
-        this.defaultMinEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
-     *
-     * @param minIdle
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
-     */
-    public void setDefaultMinIdle(final int minIdle) {
-        assertInitializationAllowed();
-        this.defaultMinIdle = minIdle;
-    }
-
-    /**
-     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
-     * pool.
-     *
-     * @param numTestsPerEvictionRun
-     *            The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per
-     *            user pool.
-     */
-    public void setDefaultNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
-        assertInitializationAllowed();
-        this.defaultNumTestsPerEvictionRun = numTestsPerEvictionRun;
-    }
-
-    /**
-     * Sets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value
-     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which
-     * will use the default value for the drive.
-     *
-     * @param defaultReadOnly
-     *            Value to assign to defaultReadOnly.
-     */
-    public void setDefaultReadOnly(final Boolean defaultReadOnly) {
-        assertInitializationAllowed();
-        this.defaultReadOnly = defaultReadOnly;
-    }
-
-    /**
-     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     *
-     * @param defaultSoftMinEvictableIdleDuration
-     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     * @since 2.10.0
-     */
-    public void setDefaultSoftMinEvictableIdle(final Duration defaultSoftMinEvictableIdleDuration) {
-        assertInitializationAllowed();
-        this.defaultSoftMinEvictableIdleDuration = defaultSoftMinEvictableIdleDuration;
-    }
-
-    /**
-     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     *
-     * @param softMinEvictableIdleTimeMillis
-     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
-     * @deprecated Use {@link #setDefaultSoftMinEvictableIdle(Duration)}.
-     */
-    @Deprecated
-    public void setDefaultSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) {
-        assertInitializationAllowed();
-        this.defaultSoftMinEvictableIdleDuration = Duration.ofMillis(softMinEvictableIdleTimeMillis);
-    }
-
-    /**
-     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
-     *
-     * @param testOnBorrow
-     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestOnBorrow()} for each per user pool.
-     */
-    public void setDefaultTestOnBorrow(final boolean testOnBorrow) {
-        assertInitializationAllowed();
-        this.defaultTestOnBorrow = testOnBorrow;
-    }
-
-    /**
-     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnCreate()} for each per user pool.
-     *
-     * @param testOnCreate
-     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestOnCreate()} for each per user pool.
-     */
-    public void setDefaultTestOnCreate(final boolean testOnCreate) {
-        assertInitializationAllowed();
-        this.defaultTestOnCreate = testOnCreate;
-    }
-
-    /**
-     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestOnReturn()} for each per user pool.
-     *
-     * @param testOnReturn
-     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestOnReturn()} for each per user pool.
-     */
-    public void setDefaultTestOnReturn(final boolean testOnReturn) {
-        assertInitializationAllowed();
-        this.defaultTestOnReturn = testOnReturn;
-    }
-
-    /**
-     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
-     *
-     * @param testWhileIdle
-     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
-     *            GenericObjectPool#getTestWhileIdle()} for each per user pool.
-     */
-    public void setDefaultTestWhileIdle(final boolean testWhileIdle) {
-        assertInitializationAllowed();
-        this.defaultTestWhileIdle = testWhileIdle;
-    }
-
-    /**
-     * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
-     *
-     * @param timeBetweenEvictionRunsMillis The default value for
-     *        {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
-     * @deprecated Use {@link #setDefaultDurationBetweenEvictionRuns(Duration)}.
-     */
-    @Deprecated
-    public void setDefaultTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
-        assertInitializationAllowed();
-        this.defaultDurationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis);
-    }
-
-    /**
-     * Sets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
-     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). The default is JDBC
-     * driver dependent.
-     *
-     * @param defaultTransactionIsolation
-     *            Value to assign to defaultTransactionIsolation
-     */
-    public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) {
-        assertInitializationAllowed();
-        switch (defaultTransactionIsolation) {
-        case Connection.TRANSACTION_NONE:
-        case Connection.TRANSACTION_READ_COMMITTED:
-        case Connection.TRANSACTION_READ_UNCOMMITTED:
-        case Connection.TRANSACTION_REPEATABLE_READ:
-        case Connection.TRANSACTION_SERIALIZABLE:
-            break;
-        default:
-            throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION);
-        }
-        this.defaultTransactionIsolation = defaultTransactionIsolation;
-    }
-
-    /**
-     * Sets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
-     * datasource. It serves no internal purpose.
-     *
-     * @param description
-     *            Value to assign to description.
-     */
-    public void setDescription(final String description) {
-        this.description = description;
-    }
-
-    /**
-     * Sets the JNDI environment to be used when instantiating a JNDI InitialContext. This InitialContext is used to
-     * locate the back end ConnectionPoolDataSource.
-     *
-     * @param properties
-     *            the JNDI environment property to set which will overwrite any current settings
-     */
-    void setJndiEnvironment(final Properties properties) {
-        if (jndiEnvironment == null) {
-            jndiEnvironment = new Properties();
-        } else {
-            jndiEnvironment.clear();
-        }
-        jndiEnvironment.putAll(properties);
-    }
-
-    /**
-     * Sets the value of the given JNDI environment property to be used when instantiating a JNDI InitialContext. This
-     * InitialContext is used to locate the back end ConnectionPoolDataSource.
-     *
-     * @param key
-     *            the JNDI environment property to set.
-     * @param value
-     *            the value assigned to specified JNDI environment property.
-     */
-    public void setJndiEnvironment(final String key, final String value) {
-        if (jndiEnvironment == null) {
-            jndiEnvironment = new Properties();
-        }
-        jndiEnvironment.setProperty(key, value);
-    }
-
-    /**
-     * Sets the value of loginTimeout.
-     *
-     * @param loginTimeout
-     *            Value to assign to loginTimeout.
-     * @since 2.10.0
-     */
-    public void setLoginTimeout(final Duration loginTimeout) {
-        this.loginTimeoutDuration = loginTimeout;
-    }
-
-    /**
-     * Sets the value of loginTimeout.
-     *
-     * @param loginTimeout
-     *            Value to assign to loginTimeout.
-     * @deprecated Use {@link #setLoginTimeout(Duration)}.
-     */
-    @Deprecated
-    @Override
-    public void setLoginTimeout(final int loginTimeout) {
-        this.loginTimeoutDuration = Duration.ofSeconds(loginTimeout);
-    }
-
-    /**
-     * Sets the value of logWriter.
-     *
-     * @param logWriter
-     *            Value to assign to logWriter.
-     */
-    @Override
-    public void setLogWriter(final PrintWriter logWriter) {
-        this.logWriter = logWriter;
-    }
-
-    /**
-     * <p>
-     * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an
-     * infinite lifetime.
-     * </p>
-     * <p>
-     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
-     * </p>
-     *
-     * @param maxConnLifetimeMillis
-     *            The maximum permitted lifetime of a connection. A value of zero or less indicates an
-     *            infinite lifetime.
-     * @since 2.9.0
-     */
-    public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) {
-        this.maxConnDuration = maxConnLifetimeMillis;
-    }
-
-    /**
-     * <p>
-     * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     * infinite lifetime.
-     * </p>
-     * <p>
-     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
-     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
-     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
-     * </p>
-     *
-     * @param maxConnLifetimeMillis
-     *            The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
-     *            infinite lifetime.
-     * @deprecated Use {@link #setMaxConnLifetime(Duration)}.
-     */
-    @Deprecated
-    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
-        setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis));
-    }
-
-    /**
-     * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from
-     * this pool before returning them to the caller. Default behavior is NOT to issue a rollback. The setting will only
-     * have an effect if a validation query is set
-     *
-     * @param rollbackAfterValidation
-     *            new property value
-     */
-    public void setRollbackAfterValidation(final boolean rollbackAfterValidation) {
-        assertInitializationAllowed();
-        this.rollbackAfterValidation = rollbackAfterValidation;
-    }
-
-    protected abstract void setupDefaults(Connection connection, String userName) throws SQLException;
-
-    /**
-     * Sets the SQL query that will be used to validate connections from this pool before returning them to the caller.
-     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
-     * specified, connections will be validated using {@link Connection#isValid(int)}.
-     *
-     * @param validationQuery
-     *            The SQL query that will be used to validate connections from this pool before returning them to the
-     *            caller.
-     */
-    public void setValidationQuery(final String validationQuery) {
-        assertInitializationAllowed();
-        this.validationQuery = validationQuery;
-    }
-
-    /**
-     * Sets the timeout duration before the validation query fails.
-     *
-     * @param validationQueryTimeoutDuration
-     *            The new timeout duration.
-     */
-    public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) {
-        this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
-    }
-
-    /**
-     * Sets the timeout in seconds before the validation query fails.
-     *
-     * @param validationQueryTimeoutSeconds
-     *            The new timeout in seconds
-     * @deprecated Use {@link #setValidationQueryTimeout(Duration)}.
-     */
-    @Deprecated
-    public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
-        this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
-    }
-
-    protected ConnectionPoolDataSource testCPDS(final String userName, final String userPassword)
-            throws javax.naming.NamingException, SQLException {
-        // The source of physical db connections
-        ConnectionPoolDataSource cpds = this.dataSource;
-        if (cpds == null) {
-            Context ctx = null;
-            if (jndiEnvironment == null) {
-                ctx = new InitialContext();
-            } else {
-                ctx = new InitialContext(jndiEnvironment);
-            }
-            final Object ds = ctx.lookup(dataSourceName);
-            if (!(ds instanceof ConnectionPoolDataSource)) {
-                throw new SQLException("Illegal configuration: " + "DataSource " + dataSourceName + " ("
-                        + ds.getClass().getName() + ")" + " doesn't implement javax.sql.ConnectionPoolDataSource");
-            }
-            cpds = (ConnectionPoolDataSource) ds;
-        }
-
-        // try to get a connection with the supplied userName/password
-        PooledConnection conn = null;
-        try {
-            if (userName != null) {
-                conn = cpds.getPooledConnection(userName, userPassword);
-            } else {
-                conn = cpds.getPooledConnection();
-            }
-            if (conn == null) {
-                throw new SQLException("Cannot connect using the supplied userName/password");
-            }
-        } finally {
-            if (conn != null) {
-                try {
-                    conn.close();
-                } catch (final SQLException e) {
-                    // at least we could connect
-                }
-            }
-        }
-        return cpds;
-    }
-
-    /**
-     * @since 2.6.0
-     */
-    @Override
-    public synchronized String toString() {
-        final StringBuilder builder = new StringBuilder(super.toString());
-        builder.append('[');
-        toStringFields(builder);
-        builder.append(']');
-        return builder.toString();
-    }
-
-    protected void toStringFields(final StringBuilder builder) {
-        builder.append("getConnectionCalled=");
-        builder.append(getConnectionCalled);
-        builder.append(", dataSource=");
-        builder.append(dataSource);
-        builder.append(", dataSourceName=");
-        builder.append(dataSourceName);
-        builder.append(", description=");
-        builder.append(description);
-        builder.append(", jndiEnvironment=");
-        builder.append(jndiEnvironment);
-        builder.append(", loginTimeoutDuration=");
-        builder.append(loginTimeoutDuration);
-        builder.append(", logWriter=");
-        builder.append(logWriter);
-        builder.append(", instanceKey=");
-        builder.append(instanceKey);
-        builder.append(", defaultBlockWhenExhausted=");
-        builder.append(defaultBlockWhenExhausted);
-        builder.append(", defaultEvictionPolicyClassName=");
-        builder.append(defaultEvictionPolicyClassName);
-        builder.append(", defaultLifo=");
-        builder.append(defaultLifo);
-        builder.append(", defaultMaxIdle=");
-        builder.append(defaultMaxIdle);
-        builder.append(", defaultMaxTotal=");
-        builder.append(defaultMaxTotal);
-        builder.append(", defaultMaxWaitDuration=");
-        builder.append(defaultMaxWaitDuration);
-        builder.append(", defaultMinEvictableIdleDuration=");
-        builder.append(defaultMinEvictableIdleDuration);
-        builder.append(", defaultMinIdle=");
-        builder.append(defaultMinIdle);
-        builder.append(", defaultNumTestsPerEvictionRun=");
-        builder.append(defaultNumTestsPerEvictionRun);
-        builder.append(", defaultSoftMinEvictableIdleDuration=");
-        builder.append(defaultSoftMinEvictableIdleDuration);
-        builder.append(", defaultTestOnCreate=");
-        builder.append(defaultTestOnCreate);
-        builder.append(", defaultTestOnBorrow=");
-        builder.append(defaultTestOnBorrow);
-        builder.append(", defaultTestOnReturn=");
-        builder.append(defaultTestOnReturn);
-        builder.append(", defaultTestWhileIdle=");
-        builder.append(defaultTestWhileIdle);
-        builder.append(", defaultDurationBetweenEvictionRuns=");
-        builder.append(defaultDurationBetweenEvictionRuns);
-        builder.append(", validationQuery=");
-        builder.append(validationQuery);
-        builder.append(", validationQueryTimeoutDuration=");
-        builder.append(validationQueryTimeoutDuration);
-        builder.append(", rollbackAfterValidation=");
-        builder.append(rollbackAfterValidation);
-        builder.append(", maxConnDuration=");
-        builder.append(maxConnDuration);
-        builder.append(", defaultAutoCommit=");
-        builder.append(defaultAutoCommit);
-        builder.append(", defaultTransactionIsolation=");
-        builder.append(defaultTransactionIsolation);
-        builder.append(", defaultReadOnly=");
-        builder.append(defaultReadOnly);
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public <T> T unwrap(final Class<T> iface) throws SQLException {
-        if (isWrapperFor(iface)) {
-            return (T) this;
-        }
-        throw new SQLException(this + " is not a wrapper for " + iface);
-    }
-    /* JDBC_4_ANT_KEY_END */
-}
+/*
+ * 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.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.time.Duration;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Referenceable;
+import javax.sql.ConnectionPoolDataSource;
+import javax.sql.DataSource;
+import javax.sql.PooledConnection;
+
+import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
+import org.apache.commons.pool2.impl.GenericObjectPool;
+
+/**
+ * <p>
+ * The base class for {@code SharedPoolDataSource} and {@code PerUserPoolDataSource}. Many of the
+ * configuration properties are shared and defined here. This class is declared public in order to allow particular
+ * usage with commons-beanutils; do not make direct use of it outside of <em>commons-dbcp2</em>.
+ * </p>
+ *
+ * <p>
+ * A J2EE container will normally provide some method of initializing the {@code DataSource} whose attributes are
+ * presented as bean getters/setters and then deploying it via JNDI. It is then available to an application as a source
+ * of pooled logical connections to the database. The pool needs a source of physical connections. This source is in the
+ * form of a {@code ConnectionPoolDataSource} that can be specified via the {@link #setDataSourceName(String)} used
+ * to lookup the source via JNDI.
+ * </p>
+ *
+ * <p>
+ * Although normally used within a JNDI environment, A DataSource can be instantiated and initialized as any bean. In
+ * this case the {@code ConnectionPoolDataSource} will likely be instantiated in a similar manner. This class
+ * allows the physical source of connections to be attached directly to this pool using the
+ * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method.
+ * </p>
+ *
+ * <p>
+ * The dbcp package contains an adapter, {@link org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS}, that can be
+ * used to allow the use of {@code DataSource}'s based on this class with JDBC driver implementations that do not
+ * supply a {@code ConnectionPoolDataSource}, but still provide a {@link java.sql.Driver} implementation.
+ * </p>
+ *
+ * <p>
+ * The <a href="package-summary.html">package documentation</a> contains an example using Apache Tomcat and JNDI and it
+ * also contains a non-JNDI example.
+ * </p>
+ *
+ * @since 2.0
+ */
+public abstract class InstanceKeyDataSource implements DataSource, Referenceable, Serializable, AutoCloseable {
+
+    private static final long serialVersionUID = -6819270431752240878L;
+
+    private static final String GET_CONNECTION_CALLED = "A Connection was already requested from this source, "
+            + "further initialization is not allowed.";
+    private static final String BAD_TRANSACTION_ISOLATION = "The requested TransactionIsolation level is invalid.";
+
+    /**
+     * Internal constant to indicate the level is not set.
+     */
+    protected static final int UNKNOWN_TRANSACTIONISOLATION = -1;
+
+    /** Guards property setters - once true, setters throw IllegalStateException */
+    private volatile boolean getConnectionCalled;
+
+    /** Underlying source of PooledConnections */
+    private ConnectionPoolDataSource dataSource;
+
+    /** DataSource Name used to find the ConnectionPoolDataSource */
+    private String dataSourceName;
+
+    /** Description */
+    private String description;
+
+    /** Environment that may be used to set up a JNDI initial context. */
+    private Properties jndiEnvironment;
+
+    /** Login Timeout */
+    private Duration loginTimeoutDuration = Duration.ZERO;
+
+    /** Log stream */
+    private PrintWriter logWriter;
+
+    /** Instance key */
+    private String instanceKey;
+
+    // Pool properties
+    private boolean defaultBlockWhenExhausted = BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED;
+    private String defaultEvictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME;
+    private boolean defaultLifo = BaseObjectPoolConfig.DEFAULT_LIFO;
+    private int defaultMaxIdle = GenericKeyedObjectPoolConfig.DEFAULT_MAX_IDLE_PER_KEY;
+    private int defaultMaxTotal = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL;
+    private Duration defaultMaxWaitDuration = BaseObjectPoolConfig.DEFAULT_MAX_WAIT;
+    private Duration defaultMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;
+    private int defaultMinIdle = GenericKeyedObjectPoolConfig.DEFAULT_MIN_IDLE_PER_KEY;
+    private int defaultNumTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
+    private Duration defaultSoftMinEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_DURATION;
+    private boolean defaultTestOnCreate = BaseObjectPoolConfig.DEFAULT_TEST_ON_CREATE;
+    private boolean defaultTestOnBorrow = BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW;
+    private boolean defaultTestOnReturn = BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN;
+    private boolean defaultTestWhileIdle = BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE;
+    private Duration defaultDurationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS;
+
+    // Connection factory properties
+    private String validationQuery;
+    private Duration validationQueryTimeoutDuration = Duration.ofSeconds(-1);
+    private boolean rollbackAfterValidation;
+    private Duration maxConnDuration = Duration.ofMillis(-1);
+
+    // Connection properties
+    private Boolean defaultAutoCommit;
+    private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION;
+    private Boolean defaultReadOnly;
+
+    /**
+     * Default no-arg constructor for Serialization.
+     */
+    public InstanceKeyDataSource() {
+    }
+
+    /**
+     * Throws an IllegalStateException, if a PooledConnection has already been requested.
+     *
+     * @throws IllegalStateException Thrown if a PooledConnection has already been requested.
+     */
+    protected void assertInitializationAllowed() throws IllegalStateException {
+        if (getConnectionCalled) {
+            throw new IllegalStateException(GET_CONNECTION_CALLED);
+        }
+    }
+
+    /**
+     * Closes the connection pool being maintained by this datasource.
+     */
+    @Override
+    public abstract void close() throws Exception;
+
+    private void closeDueToException(final PooledConnectionAndInfo info) {
+        if (info != null) {
+            try {
+                info.getPooledConnection().getConnection().close();
+            } catch (final Exception e) {
+                // do not throw this exception because we are in the middle
+                // of handling another exception. But record it because
+                // it potentially leaks connections from the pool.
+                getLogWriter().println("[ERROR] Could not return connection to pool during exception handling. " + e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * Attempts to establish a database connection.
+     */
+    @Override
+    public Connection getConnection() throws SQLException {
+        return getConnection(null, null);
+    }
+
+    /**
+     * Attempts to retrieve a database connection using {@link #getPooledConnectionAndInfo(String, String)} with the
+     * provided user name and password. The password on the {@code PooledConnectionAndInfo} instance returned by
+     * {@code getPooledConnectionAndInfo} is compared to the {@code password} parameter. If the comparison
+     * fails, a database connection using the supplied user name and password is attempted. If the connection attempt
+     * fails, an SQLException is thrown, indicating that the given password did not match the password used to create
+     * the pooled connection. If the connection attempt succeeds, this means that the database password has been
+     * changed. In this case, the {@code PooledConnectionAndInfo} instance retrieved with the old password is
+     * destroyed and the {@code getPooledConnectionAndInfo} is repeatedly invoked until a
+     * {@code PooledConnectionAndInfo} instance with the new password is returned.
+     */
+    @Override
+    public Connection getConnection(final String userName, final String userPassword) throws SQLException {
+        if (instanceKey == null) {
+            throw new SQLException("Must set the ConnectionPoolDataSource "
+                    + "through setDataSourceName or setConnectionPoolDataSource" + " before calling getConnection.");
+        }
+        getConnectionCalled = true;
+        PooledConnectionAndInfo info = null;
+        try {
+            info = getPooledConnectionAndInfo(userName, userPassword);
+        } catch (final RuntimeException | SQLException e) {
+            closeDueToException(info);
+            throw e;
+        } catch (final Exception e) {
+            closeDueToException(info);
+            throw new SQLException("Cannot borrow connection from pool", e);
+        }
+
+        // Password on PooledConnectionAndInfo does not match
+        if (!(null == userPassword ? null == info.getPassword() : userPassword.equals(info.getPassword()))) {
+            try { // See if password has changed by attempting connection
+                testCPDS(userName, userPassword);
+            } catch (final SQLException ex) {
+                // Password has not changed, so refuse client, but return connection to the pool
+                closeDueToException(info);
+                throw new SQLException(
+                        "Given password did not match password used" + " to create the PooledConnection.", ex);
+            } catch (final javax.naming.NamingException ne) {
+                throw new SQLException("NamingException encountered connecting to database", ne);
+            }
+            /*
+             * Password must have changed -> destroy connection and keep retrying until we get a new, good one,
+             * destroying any idle connections with the old password as we pull them from the pool.
+             */
+            final UserPassKey upkey = info.getUserPassKey();
+            final PooledConnectionManager manager = getConnectionManager(upkey);
+            // Destroy and remove from pool
+            manager.invalidate(info.getPooledConnection());
+            // Reset the password on the factory if using CPDSConnectionFactory
+            manager.setPassword(upkey.getPassword());
+            info = null;
+            for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return
+                try {
+                    info = getPooledConnectionAndInfo(userName, userPassword);
+                } catch (final RuntimeException | SQLException e) {
+                    closeDueToException(info);
+                    throw e;
+                } catch (final Exception e) {
+                    closeDueToException(info);
+                    throw new SQLException("Cannot borrow connection from pool", e);
+                }
+                if (info != null && userPassword != null && userPassword.equals(info.getPassword())) {
+                    break;
+                }
+                if (info != null) {
+                    manager.invalidate(info.getPooledConnection());
+                }
+                info = null;
+            }
+            if (info == null) {
+                throw new SQLException("Cannot borrow connection from pool - password change failure.");
+            }
+        }
+
+        final Connection connection = info.getPooledConnection().getConnection();
+        try {
+            setupDefaults(connection, userName);
+            connection.clearWarnings();
+            return connection;
+        } catch (final SQLException ex) {
+            try {
+                connection.close();
+            } catch (final Exception exc) {
+                getLogWriter().println("ignoring exception during close: " + exc);
+            }
+            throw ex;
+        }
+    }
+
+    protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey);
+
+    /**
+     * Gets the value of connectionPoolDataSource. This method will return null, if the backing data source is being
+     * accessed via JNDI.
+     *
+     * @return value of connectionPoolDataSource.
+     */
+    public ConnectionPoolDataSource getConnectionPoolDataSource() {
+        return dataSource;
+    }
+
+    /**
+     * Gets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source
+     * from a JNDI service provider.
+     *
+     * @return value of dataSourceName.
+     */
+    public String getDataSourceName() {
+        return dataSourceName;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user
+     *         pool.
+     */
+    public boolean getDefaultBlockWhenExhausted() {
+        return this.defaultBlockWhenExhausted;
+    }
+
+    /**
+     * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
+     *
+     * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
+     * @since 2.10.0
+     */
+    public Duration getDefaultDurationBetweenEvictionRuns() {
+        return this.defaultDurationBetweenEvictionRuns;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
+     * pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
+     *         pool.
+     */
+    public String getDefaultEvictionPolicyClassName() {
+        return this.defaultEvictionPolicyClassName;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
+     */
+    public boolean getDefaultLifo() {
+        return this.defaultLifo;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
+     */
+    public int getDefaultMaxIdle() {
+        return this.defaultMaxIdle;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     */
+    public int getDefaultMaxTotal() {
+        return this.defaultMaxTotal;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     * @since 2.9.0
+     */
+    public Duration getDefaultMaxWait() {
+        return this.defaultMaxWaitDuration;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     * @deprecated Use {@link #getDefaultMaxWait()}.
+     */
+    @Deprecated
+    public long getDefaultMaxWaitMillis() {
+        return getDefaultMaxWait().toMillis();
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
+     * pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per
+     *         user pool.
+     * @since 2.10.0
+     */
+    public Duration getDefaultMinEvictableIdleDuration() {
+        return this.defaultMinEvictableIdleDuration;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
+     * pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per
+     *         user pool.
+     * @deprecated Use {@link #getDefaultMinEvictableIdleDuration()}.
+     */
+    @Deprecated
+    public long getDefaultMinEvictableIdleTimeMillis() {
+        return this.defaultMinEvictableIdleDuration.toMillis();
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
+     */
+    public int getDefaultMinIdle() {
+        return this.defaultMinIdle;
+    }
+
+    /**
+     * Gets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
+     * pool.
+     *
+     * @return The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
+     *         pool.
+     */
+    public int getDefaultNumTestsPerEvictionRun() {
+        return this.defaultNumTestsPerEvictionRun;
+    }
+
+    /**
+     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     *
+     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @since 2.10.0
+     */
+    public Duration getDefaultSoftMinEvictableIdleDuration() {
+        return this.defaultSoftMinEvictableIdleDuration;
+    }
+
+    /**
+     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     *
+     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @deprecated Use {@link #getDefaultSoftMinEvictableIdleDuration()}.
+     */
+    @Deprecated
+    public long getDefaultSoftMinEvictableIdleTimeMillis() {
+        return this.defaultSoftMinEvictableIdleDuration.toMillis();
+    }
+
+    /**
+     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     *
+     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     */
+    public boolean getDefaultTestOnBorrow() {
+        return this.defaultTestOnBorrow;
+    }
+
+    /**
+     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnCreate()} for each per user pool.
+     *
+     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getTestOnCreate()} for each per user pool.
+     */
+    public boolean getDefaultTestOnCreate() {
+        return this.defaultTestOnCreate;
+    }
+
+    /**
+     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnReturn()} for each per user pool.
+     *
+     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getTestOnReturn()} for each per user pool.
+     */
+    public boolean getDefaultTestOnReturn() {
+        return this.defaultTestOnReturn;
+    }
+
+    /**
+     * Gets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     *
+     * @return The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *         GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     */
+    public boolean getDefaultTestWhileIdle() {
+        return this.defaultTestWhileIdle;
+    }
+
+    /**
+     * Gets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
+     *
+     * @return The default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
+     * @deprecated Use {@link #getDefaultDurationBetweenEvictionRuns()}.
+     */
+    @Deprecated
+    public long getDefaultTimeBetweenEvictionRunsMillis() {
+        return this.defaultDurationBetweenEvictionRuns.toMillis();
+    }
+
+    /**
+     * Gets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
+     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). If this method returns
+     * -1, the default is JDBC driver dependent.
+     *
+     * @return value of defaultTransactionIsolation.
+     */
+    public int getDefaultTransactionIsolation() {
+        return defaultTransactionIsolation;
+    }
+
+    /**
+     * Gets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
+     * datasource. It serves no internal purpose.
+     *
+     * @return value of description.
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Gets the instance key.
+     *
+     * @return the instance key.
+     */
+    protected String getInstanceKey() {
+        return instanceKey;
+    }
+
+    /**
+     * Gets the value of jndiEnvironment which is used when instantiating a JNDI InitialContext. This InitialContext is
+     * used to locate the back end ConnectionPoolDataSource.
+     *
+     * @param key
+     *            JNDI environment key.
+     * @return value of jndiEnvironment.
+     */
+    public String getJndiEnvironment(final String key) {
+        String value = null;
+        if (jndiEnvironment != null) {
+            value = jndiEnvironment.getProperty(key);
+        }
+        return value;
+    }
+
+    /**
+     * Gets the value of loginTimeout.
+     *
+     * @return value of loginTimeout.
+     * @deprecated Use {@link #getLoginTimeoutDuration()}.
+     */
+    @Deprecated
+    @Override
+    public int getLoginTimeout() {
+        return (int) loginTimeoutDuration.getSeconds();
+    }
+
+    /**
+     * Gets the value of loginTimeout.
+     *
+     * @return value of loginTimeout.
+     * @since 2.10.0
+     */
+    public Duration getLoginTimeoutDuration() {
+        return loginTimeoutDuration;
+    }
+
+    /**
+     * Gets the value of logWriter.
+     *
+     * @return value of logWriter.
+     */
+    @Override
+    public PrintWriter getLogWriter() {
+        if (logWriter == null) {
+            logWriter = new PrintWriter(new OutputStreamWriter(System.out, StandardCharsets.UTF_8));
+        }
+        return logWriter;
+    }
+
+    /**
+     * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an
+     * infinite lifetime.
+     *
+     * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an
+     *         infinite lifetime.
+     * @since 2.10.0
+     */
+    public Duration getMaxConnDuration() {
+        return maxConnDuration;
+    }
+
+    /**
+     * Gets the maximum permitted lifetime of a connection. A value of zero or less indicates an
+     * infinite lifetime.
+     *
+     * @return The maximum permitted lifetime of a connection. A value of zero or less indicates an
+     *         infinite lifetime.
+     * @deprecated Use {@link #getMaxConnDuration()}.
+     */
+    @Deprecated
+    public Duration getMaxConnLifetime() {
+        return maxConnDuration;
+    }
+
+    /**
+     * Gets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     * infinite lifetime.
+     *
+     * @return The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     *         infinite lifetime.
+     * @deprecated Use {@link #getMaxConnLifetime()}.
+     */
+    @Deprecated
+    public long getMaxConnLifetimeMillis() {
+        return maxConnDuration.toMillis();
+    }
+
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        throw new SQLFeatureNotSupportedException();
+    }
+
+    /**
+     * This method is protected but can only be implemented in this package because PooledConnectionAndInfo is a package
+     * private type.
+     *
+     * @param userName The user name.
+     * @param userPassword The user password.
+     * @return Matching PooledConnectionAndInfo.
+     * @throws SQLException Connection or registration failure.
+     */
+    protected abstract PooledConnectionAndInfo getPooledConnectionAndInfo(String userName, String userPassword)
+            throws SQLException;
+
+    /**
+     * Gets the SQL query that will be used to validate connections from this pool before returning them to the caller.
+     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
+     * specified, {@link Connection#isValid(int)} will be used to validate connections.
+     *
+     * @return The SQL query that will be used to validate connections from this pool before returning them to the
+     *         caller.
+     */
+    public String getValidationQuery() {
+        return this.validationQuery;
+    }
+
+    /**
+     * Returns the timeout in seconds before the validation query fails.
+     *
+     * @return The timeout in seconds before the validation query fails.
+     * @deprecated Use {@link #getValidationQueryTimeoutDuration()}.
+     */
+    @Deprecated
+    public int getValidationQueryTimeout() {
+        return (int) validationQueryTimeoutDuration.getSeconds();
+    }
+
+    /**
+     * Returns the timeout Duration before the validation query fails.
+     *
+     * @return The timeout Duration before the validation query fails.
+     */
+    public Duration getValidationQueryTimeoutDuration() {
+        return validationQueryTimeoutDuration;
+    }
+
+    /**
+     * Gets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
+     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which
+     * will use the default value for the drive.
+     *
+     * @return value of defaultAutoCommit.
+     */
+    public Boolean isDefaultAutoCommit() {
+        return defaultAutoCommit;
+    }
+
+    /**
+     * Gets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value
+     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which
+     * will use the default value for the drive.
+     *
+     * @return value of defaultReadOnly.
+     */
+    public Boolean isDefaultReadOnly() {
+        return defaultReadOnly;
+    }
+
+    /**
+     * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from
+     * this pool before returning them to the caller.
+     *
+     * @return true if a rollback will be issued after executing the validation query
+     */
+    public boolean isRollbackAfterValidation() {
+        return this.rollbackAfterValidation;
+    }
+
+    @Override
+    public boolean isWrapperFor(final Class<?> iface) throws SQLException {
+        return iface.isInstance(this);
+    }
+
+    /**
+     * Sets the back end ConnectionPoolDataSource. This property should not be set if using JNDI to access the
+     * data source.
+     *
+     * @param dataSource
+     *            Value to assign to connectionPoolDataSource.
+     */
+    public void setConnectionPoolDataSource(final ConnectionPoolDataSource dataSource) {
+        assertInitializationAllowed();
+        if (dataSourceName != null) {
+            throw new IllegalStateException("Cannot set the DataSource, if JNDI is used.");
+        }
+        if (this.dataSource != null) {
+            throw new IllegalStateException("The CPDS has already been set. It cannot be altered.");
+        }
+        this.dataSource = dataSource;
+        instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
+    }
+
+    /**
+     * Sets the name of the ConnectionPoolDataSource which backs this pool. This name is used to look up the data source
+     * from a JNDI service provider.
+     *
+     * @param dataSourceName
+     *            Value to assign to dataSourceName.
+     */
+    public void setDataSourceName(final String dataSourceName) {
+        assertInitializationAllowed();
+        if (dataSource != null) {
+            throw new IllegalStateException("Cannot set the JNDI name for the DataSource, if already "
+                    + "set using setConnectionPoolDataSource.");
+        }
+        if (this.dataSourceName != null) {
+            throw new IllegalStateException("The DataSourceName has already been set. " + "It cannot be altered.");
+        }
+        this.dataSourceName = dataSourceName;
+        instanceKey = InstanceKeyDataSourceFactory.registerNewInstance(this);
+    }
+
+    /**
+     * Sets the value of defaultAutoCommit, which defines the state of connections handed out from this pool. The value
+     * can be changed on the Connection using Connection.setAutoCommit(boolean). The default is {@code null} which
+     * will use the default value for the drive.
+     *
+     * @param defaultAutoCommit
+     *            Value to assign to defaultAutoCommit.
+     */
+    public void setDefaultAutoCommit(final Boolean defaultAutoCommit) {
+        assertInitializationAllowed();
+        this.defaultAutoCommit = defaultAutoCommit;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user pool.
+     *
+     * @param blockWhenExhausted
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getBlockWhenExhausted()} for each per user
+     *            pool.
+     */
+    public void setDefaultBlockWhenExhausted(final boolean blockWhenExhausted) {
+        assertInitializationAllowed();
+        this.defaultBlockWhenExhausted = blockWhenExhausted;
+    }
+
+    /**
+     * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
+     *
+     * @param defaultDurationBetweenEvictionRuns The default value for
+     *        {@link GenericObjectPool#getDurationBetweenEvictionRuns ()} for each per user pool.
+     * @since 2.10.0
+     */
+    public void setDefaultDurationBetweenEvictionRuns(final Duration defaultDurationBetweenEvictionRuns) {
+        assertInitializationAllowed();
+        this.defaultDurationBetweenEvictionRuns = defaultDurationBetweenEvictionRuns;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per user
+     * pool.
+     *
+     * @param evictionPolicyClassName
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getEvictionPolicyClassName()} for each per
+     *            user pool.
+     */
+    public void setDefaultEvictionPolicyClassName(final String evictionPolicyClassName) {
+        assertInitializationAllowed();
+        this.defaultEvictionPolicyClassName = evictionPolicyClassName;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
+     *
+     * @param lifo
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getLifo()} for each per user pool.
+     */
+    public void setDefaultLifo(final boolean lifo) {
+        assertInitializationAllowed();
+        this.defaultLifo = lifo;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
+     *
+     * @param maxIdle
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxIdlePerKey()} for each per user pool.
+     */
+    public void setDefaultMaxIdle(final int maxIdle) {
+        assertInitializationAllowed();
+        this.defaultMaxIdle = maxIdle;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     *
+     * @param maxTotal
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxTotalPerKey()} for each per user pool.
+     */
+    public void setDefaultMaxTotal(final int maxTotal) {
+        assertInitializationAllowed();
+        this.defaultMaxTotal = maxTotal;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     *
+     * @param maxWaitMillis
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitDuration()} for each per user pool.
+     * @since 2.9.0
+     */
+    public void setDefaultMaxWait(final Duration maxWaitMillis) {
+        assertInitializationAllowed();
+        this.defaultMaxWaitDuration = maxWaitMillis;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
+     *
+     * @param maxWaitMillis
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMaxWaitMillis()} for each per user pool.
+     * @deprecated Use {@link #setDefaultMaxWait(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultMaxWaitMillis(final long maxWaitMillis) {
+        setDefaultMaxWait(Duration.ofMillis(maxWaitMillis));
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
+     * pool.
+     *
+     * @param defaultMinEvictableIdleDuration
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each
+     *            per user pool.
+     * @since 2.10.0
+     */
+    public void setDefaultMinEvictableIdle(final Duration defaultMinEvictableIdleDuration) {
+        assertInitializationAllowed();
+        this.defaultMinEvictableIdleDuration = defaultMinEvictableIdleDuration;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each per user
+     * pool.
+     *
+     * @param minEvictableIdleTimeMillis
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinEvictableIdleDuration()} for each
+     *            per user pool.
+     * @deprecated Use {@link #setDefaultMinEvictableIdle(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) {
+        assertInitializationAllowed();
+        this.defaultMinEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
+     *
+     * @param minIdle
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} for each per user pool.
+     */
+    public void setDefaultMinIdle(final int minIdle) {
+        assertInitializationAllowed();
+        this.defaultMinIdle = minIdle;
+    }
+
+    /**
+     * Sets the default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per user
+     * pool.
+     *
+     * @param numTestsPerEvictionRun
+     *            The default value for {@link GenericKeyedObjectPoolConfig#getNumTestsPerEvictionRun()} for each per
+     *            user pool.
+     */
+    public void setDefaultNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
+        assertInitializationAllowed();
+        this.defaultNumTestsPerEvictionRun = numTestsPerEvictionRun;
+    }
+
+    /**
+     * Sets the value of defaultReadOnly, which defines the state of connections handed out from this pool. The value
+     * can be changed on the Connection using Connection.setReadOnly(boolean). The default is {@code null} which
+     * will use the default value for the drive.
+     *
+     * @param defaultReadOnly
+     *            Value to assign to defaultReadOnly.
+     */
+    public void setDefaultReadOnly(final Boolean defaultReadOnly) {
+        assertInitializationAllowed();
+        this.defaultReadOnly = defaultReadOnly;
+    }
+
+    /**
+     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     *
+     * @param defaultSoftMinEvictableIdleDuration
+     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @since 2.10.0
+     */
+    public void setDefaultSoftMinEvictableIdle(final Duration defaultSoftMinEvictableIdleDuration) {
+        assertInitializationAllowed();
+        this.defaultSoftMinEvictableIdleDuration = defaultSoftMinEvictableIdleDuration;
+    }
+
+    /**
+     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     *
+     * @param softMinEvictableIdleTimeMillis
+     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} for each per user pool.
+     * @deprecated Use {@link #setDefaultSoftMinEvictableIdle(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) {
+        assertInitializationAllowed();
+        this.defaultSoftMinEvictableIdleDuration = Duration.ofMillis(softMinEvictableIdleTimeMillis);
+    }
+
+    /**
+     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     *
+     * @param testOnBorrow
+     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestOnBorrow()} for each per user pool.
+     */
+    public void setDefaultTestOnBorrow(final boolean testOnBorrow) {
+        assertInitializationAllowed();
+        this.defaultTestOnBorrow = testOnBorrow;
+    }
+
+    /**
+     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnCreate()} for each per user pool.
+     *
+     * @param testOnCreate
+     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestOnCreate()} for each per user pool.
+     */
+    public void setDefaultTestOnCreate(final boolean testOnCreate) {
+        assertInitializationAllowed();
+        this.defaultTestOnCreate = testOnCreate;
+    }
+
+    /**
+     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestOnReturn()} for each per user pool.
+     *
+     * @param testOnReturn
+     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestOnReturn()} for each per user pool.
+     */
+    public void setDefaultTestOnReturn(final boolean testOnReturn) {
+        assertInitializationAllowed();
+        this.defaultTestOnReturn = testOnReturn;
+    }
+
+    /**
+     * Sets the default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     * GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     *
+     * @param testWhileIdle
+     *            The default value for {@link org.apache.commons.pool2.impl.GenericObjectPool
+     *            GenericObjectPool#getTestWhileIdle()} for each per user pool.
+     */
+    public void setDefaultTestWhileIdle(final boolean testWhileIdle) {
+        assertInitializationAllowed();
+        this.defaultTestWhileIdle = testWhileIdle;
+    }
+
+    /**
+     * Sets the default value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
+     *
+     * @param timeBetweenEvictionRunsMillis The default value for
+     *        {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for each per user pool.
+     * @deprecated Use {@link #setDefaultDurationBetweenEvictionRuns(Duration)}.
+     */
+    @Deprecated
+    public void setDefaultTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
+        assertInitializationAllowed();
+        this.defaultDurationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis);
+    }
+
+    /**
+     * Sets the value of defaultTransactionIsolation, which defines the state of connections handed out from this pool.
+     * The value can be changed on the Connection using Connection.setTransactionIsolation(int). The default is JDBC
+     * driver dependent.
+     *
+     * @param defaultTransactionIsolation
+     *            Value to assign to defaultTransactionIsolation
+     */
+    public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) {
+        assertInitializationAllowed();
+        switch (defaultTransactionIsolation) {
+        case Connection.TRANSACTION_NONE:
+        case Connection.TRANSACTION_READ_COMMITTED:
+        case Connection.TRANSACTION_READ_UNCOMMITTED:
+        case Connection.TRANSACTION_REPEATABLE_READ:
+        case Connection.TRANSACTION_SERIALIZABLE:
+            break;
+        default:
+            throw new IllegalArgumentException(BAD_TRANSACTION_ISOLATION);
+        }
+        this.defaultTransactionIsolation = defaultTransactionIsolation;
+    }
+
+    /**
+     * Sets the description. This property is defined by JDBC as for use with GUI (or other) tools that might deploy the
+     * datasource. It serves no internal purpose.
+     *
+     * @param description
+     *            Value to assign to description.
+     */
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    /**
+     * Sets the JNDI environment to be used when instantiating a JNDI InitialContext. This InitialContext is used to
+     * locate the back end ConnectionPoolDataSource.
+     *
+     * @param properties
+     *            the JNDI environment property to set which will overwrite any current settings
+     */
+    void setJndiEnvironment(final Properties properties) {
+        if (jndiEnvironment == null) {
+            jndiEnvironment = new Properties();
+        } else {
+            jndiEnvironment.clear();
+        }
+        jndiEnvironment.putAll(properties);
+    }
+
+    /**
+     * Sets the value of the given JNDI environment property to be used when instantiating a JNDI InitialContext. This
+     * InitialContext is used to locate the back end ConnectionPoolDataSource.
+     *
+     * @param key
+     *            the JNDI environment property to set.
+     * @param value
+     *            the value assigned to specified JNDI environment property.
+     */
+    public void setJndiEnvironment(final String key, final String value) {
+        if (jndiEnvironment == null) {
+            jndiEnvironment = new Properties();
+        }
+        jndiEnvironment.setProperty(key, value);
+    }
+
+    /**
+     * Sets the value of loginTimeout.
+     *
+     * @param loginTimeout
+     *            Value to assign to loginTimeout.
+     * @since 2.10.0
+     */
+    public void setLoginTimeout(final Duration loginTimeout) {
+        this.loginTimeoutDuration = loginTimeout;
+    }
+
+    /**
+     * Sets the value of loginTimeout.
+     *
+     * @param loginTimeout
+     *            Value to assign to loginTimeout.
+     * @deprecated Use {@link #setLoginTimeout(Duration)}.
+     */
+    @Deprecated
+    @Override
+    public void setLoginTimeout(final int loginTimeout) {
+        this.loginTimeoutDuration = Duration.ofSeconds(loginTimeout);
+    }
+
+    /**
+     * Sets the value of logWriter.
+     *
+     * @param logWriter
+     *            Value to assign to logWriter.
+     */
+    @Override
+    public void setLogWriter(final PrintWriter logWriter) {
+        this.logWriter = logWriter;
+    }
+
+    /**
+     * <p>
+     * Sets the maximum permitted lifetime of a connection. A value of zero or less indicates an
+     * infinite lifetime.
+     * </p>
+     * <p>
+     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
+     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
+     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * </p>
+     *
+     * @param maxConnLifetimeMillis
+     *            The maximum permitted lifetime of a connection. A value of zero or less indicates an
+     *            infinite lifetime.
+     * @since 2.9.0
+     */
+    public void setMaxConnLifetime(final Duration maxConnLifetimeMillis) {
+        this.maxConnDuration = maxConnLifetimeMillis;
+    }
+
+    /**
+     * <p>
+     * Sets the maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     * infinite lifetime.
+     * </p>
+     * <p>
+     * Note: this method currently has no effect once the pool has been initialized. The pool is initialized the first
+     * time one of the following methods is invoked: <code>getConnection, setLogwriter,
+     * setLoginTimeout, getLoginTimeout, getLogWriter.</code>
+     * </p>
+     *
+     * @param maxConnLifetimeMillis
+     *            The maximum permitted lifetime of a connection in milliseconds. A value of zero or less indicates an
+     *            infinite lifetime.
+     * @deprecated Use {@link #setMaxConnLifetime(Duration)}.
+     */
+    @Deprecated
+    public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
+        setMaxConnLifetime(Duration.ofMillis(maxConnLifetimeMillis));
+    }
+
+    /**
+     * Whether a rollback will be issued after executing the SQL query that will be used to validate connections from
+     * this pool before returning them to the caller. Default behavior is NOT to issue a rollback. The setting will only
+     * have an effect if a validation query is set
+     *
+     * @param rollbackAfterValidation
+     *            new property value
+     */
+    public void setRollbackAfterValidation(final boolean rollbackAfterValidation) {
+        assertInitializationAllowed();
+        this.rollbackAfterValidation = rollbackAfterValidation;
+    }
+
+    protected abstract void setupDefaults(Connection connection, String userName) throws SQLException;
+
+    /**
+     * Sets the SQL query that will be used to validate connections from this pool before returning them to the caller.
+     * If specified, this query <strong>MUST</strong> be an SQL SELECT statement that returns at least one row. If not
+     * specified, connections will be validated using {@link Connection#isValid(int)}.
+     *
+     * @param validationQuery
+     *            The SQL query that will be used to validate connections from this pool before returning them to the
+     *            caller.
+     */
+    public void setValidationQuery(final String validationQuery) {
+        assertInitializationAllowed();
+        this.validationQuery = validationQuery;
+    }
+
+    /**
+     * Sets the timeout duration before the validation query fails.
+     *
+     * @param validationQueryTimeoutDuration
+     *            The new timeout duration.
+     */
+    public void setValidationQueryTimeout(final Duration validationQueryTimeoutDuration) {
+        this.validationQueryTimeoutDuration = validationQueryTimeoutDuration;
+    }
+
+    /**
+     * Sets the timeout in seconds before the validation query fails.
+     *
+     * @param validationQueryTimeoutSeconds
+     *            The new timeout in seconds
+     * @deprecated Use {@link #setValidationQueryTimeout(Duration)}.
+     */
+    @Deprecated
+    public void setValidationQueryTimeout(final int validationQueryTimeoutSeconds) {
+        this.validationQueryTimeoutDuration = Duration.ofSeconds(validationQueryTimeoutSeconds);
+    }
+
+    protected ConnectionPoolDataSource testCPDS(final String userName, final String userPassword)
+            throws javax.naming.NamingException, SQLException {
+        // The source of physical db connections
+        ConnectionPoolDataSource cpds = this.dataSource;
+        if (cpds == null) {
+            Context ctx = null;
+            if (jndiEnvironment == null) {
+                ctx = new InitialContext();
+            } else {
+                ctx = new InitialContext(jndiEnvironment);
+            }
+            final Object ds = ctx.lookup(dataSourceName);
+            if (!(ds instanceof ConnectionPoolDataSource)) {
+                throw new SQLException("Illegal configuration: " + "DataSource " + dataSourceName + " ("
+                        + ds.getClass().getName() + ")" + " doesn't implement javax.sql.ConnectionPoolDataSource");
+            }
+            cpds = (ConnectionPoolDataSource) ds;
+        }
+
+        // try to get a connection with the supplied userName/password
+        PooledConnection conn = null;
+        try {
+            if (userName != null) {
+                conn = cpds.getPooledConnection(userName, userPassword);
+            } else {
+                conn = cpds.getPooledConnection();
+            }
+            if (conn == null) {
+                throw new SQLException("Cannot connect using the supplied userName/password");
+            }
+        } finally {
+            if (conn != null) {
+                try {
+                    conn.close();
+                } catch (final SQLException ignored) {
+                    // at least we could connect
+                }
+            }
+        }
+        return cpds;
+    }
+
+    /**
+     * @since 2.6.0
+     */
+    @Override
+    public synchronized String toString() {
+        final StringBuilder builder = new StringBuilder(super.toString());
+        builder.append('[');
+        toStringFields(builder);
+        builder.append(']');
+        return builder.toString();
+    }
+
+    protected void toStringFields(final StringBuilder builder) {
+        builder.append("getConnectionCalled=");
+        builder.append(getConnectionCalled);
+        builder.append(", dataSource=");
+        builder.append(dataSource);
+        builder.append(", dataSourceName=");
+        builder.append(dataSourceName);
+        builder.append(", description=");
+        builder.append(description);
+        builder.append(", jndiEnvironment=");
+        builder.append(jndiEnvironment);
+        builder.append(", loginTimeoutDuration=");
+        builder.append(loginTimeoutDuration);
+        builder.append(", logWriter=");
+        builder.append(logWriter);
+        builder.append(", instanceKey=");
+        builder.append(instanceKey);
+        builder.append(", defaultBlockWhenExhausted=");
+        builder.append(defaultBlockWhenExhausted);
+        builder.append(", defaultEvictionPolicyClassName=");
+        builder.append(defaultEvictionPolicyClassName);
+        builder.append(", defaultLifo=");
+        builder.append(defaultLifo);
+        builder.append(", defaultMaxIdle=");
+        builder.append(defaultMaxIdle);
+        builder.append(", defaultMaxTotal=");
+        builder.append(defaultMaxTotal);
+        builder.append(", defaultMaxWaitDuration=");
+        builder.append(defaultMaxWaitDuration);
+        builder.append(", defaultMinEvictableIdleDuration=");
+        builder.append(defaultMinEvictableIdleDuration);
+        builder.append(", defaultMinIdle=");
+        builder.append(defaultMinIdle);
+        builder.append(", defaultNumTestsPerEvictionRun=");
+        builder.append(defaultNumTestsPerEvictionRun);
+        builder.append(", defaultSoftMinEvictableIdleDuration=");
+        builder.append(defaultSoftMinEvictableIdleDuration);
+        builder.append(", defaultTestOnCreate=");
+        builder.append(defaultTestOnCreate);
+        builder.append(", defaultTestOnBorrow=");
+        builder.append(defaultTestOnBorrow);
+        builder.append(", defaultTestOnReturn=");
+        builder.append(defaultTestOnReturn);
+        builder.append(", defaultTestWhileIdle=");
+        builder.append(defaultTestWhileIdle);
+        builder.append(", defaultDurationBetweenEvictionRuns=");
+        builder.append(defaultDurationBetweenEvictionRuns);
+        builder.append(", validationQuery=");
+        builder.append(validationQuery);
+        builder.append(", validationQueryTimeoutDuration=");
+        builder.append(validationQueryTimeoutDuration);
+        builder.append(", rollbackAfterValidation=");
+        builder.append(rollbackAfterValidation);
+        builder.append(", maxConnDuration=");
+        builder.append(maxConnDuration);
+        builder.append(", defaultAutoCommit=");
+        builder.append(defaultAutoCommit);
+        builder.append(", defaultTransactionIsolation=");
+        builder.append(defaultTransactionIsolation);
+        builder.append(", defaultReadOnly=");
+        builder.append(defaultReadOnly);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T> T unwrap(final Class<T> iface) throws SQLException {
+        if (isWrapperFor(iface)) {
+            return (T) this;
+        }
+        throw new SQLException(this + " is not a wrapper for " + iface);
+    }
+    /* JDBC_4_ANT_KEY_END */
+}
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 5f2cbbcf..23832cb4 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSourceFactory.java
@@ -1,348 +1,348 @@
-/*
- * 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.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.concurrent.ConcurrentHashMap;
-
-import javax.naming.Context;
-import javax.naming.Name;
-import javax.naming.RefAddr;
-import javax.naming.Reference;
-import javax.naming.spi.ObjectFactory;
-
-import org.apache.commons.dbcp2.ListException;
-import org.apache.commons.dbcp2.Utils;
-
-/**
- * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s or {@code PerUserPoolDataSource}s
- *
- * @since 2.0
- */
-abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
-
-    private static final Map<String, InstanceKeyDataSource> INSTANCE_MAP = new ConcurrentHashMap<>();
-
-    /**
-     * Closes all pools associated with this class.
-     *
-     * @throws Exception
-     *             a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()}
-     * @see InstanceKeyDataSource#close()
-     * @see ListException
-     * @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by
-     *        {@link InstanceKeyDataSource#close()}.
-     */
-    public static void closeAll() throws Exception {
-        // Get iterator to loop over all instances of this data source.
-        final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
-        for (final Entry<String, InstanceKeyDataSource> next : INSTANCE_MAP.entrySet()) {
-            // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
-            if (next != null) {
-                @SuppressWarnings("resource") final InstanceKeyDataSource value = next.getValue();
-                if (value != null) {
-                    try {
-                        value.close();
-                    } catch (final Exception e) {
-                        exceptionList.add(e);
-                    }
-                }
-            }
-        }
-        INSTANCE_MAP.clear();
-        if (!exceptionList.isEmpty()) {
-            throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
-        }
-    }
-
-    /**
-     * Deserializes the provided byte array to create an object.
-     *
-     * @param data
-     *            Data to deserialize to create the configuration parameter.
-     *
-     * @return The Object created by deserializing the data.
-     *
-     * @throws ClassNotFoundException
-     *            If a class cannot be found during the deserialization of a configuration parameter.
-     * @throws IOException
-     *            If an I/O error occurs during the deserialization of a configuration parameter.
-     */
-    protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
-        ObjectInputStream in = null;
-        try {
-            in = new ObjectInputStream(new ByteArrayInputStream(data));
-            return in.readObject();
-        } finally {
-            Utils.closeQuietly(in);
-        }
-    }
-
-    static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
-        int max = 0;
-        for (final String s : INSTANCE_MAP.keySet()) {
-            if (s != null) {
-                try {
-                    max = Math.max(max, Integer.parseInt(s));
-                } catch (final NumberFormatException e) {
-                    // no sweat, ignore those keys
-                }
-            }
-        }
-        final String instanceKey = String.valueOf(max + 1);
-        // Put a placeholder here for now, so other instances will not
-        // take our key. We will replace with a pool when ready.
-        INSTANCE_MAP.put(instanceKey, ds);
-        return instanceKey;
-    }
-
-    static void removeInstance(final String key) {
-        if (key != null) {
-            INSTANCE_MAP.remove(key);
-        }
-    }
-
-    /**
-     * Creates an instance of the subclass and sets any properties contained in the Reference.
-     *
-     * @param ref
-     *            The properties to be set on the created DataSource
-     *
-     * @return A configured DataSource of the appropriate type.
-     *
-     * @throws ClassNotFoundException
-     *            If a class cannot be found during the deserialization of a configuration parameter.
-     * @throws IOException
-     *            If an I/O error occurs during the deserialization of a configuration parameter.
-     */
-    protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;
-
-    /**
-     * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource
-     */
-    @Override
-    public Object getObjectInstance(final Object refObj, final Name name, final Context context,
-            final Hashtable<?, ?> env) throws IOException, ClassNotFoundException {
-        // The spec says to return null if we can't create an instance
-        // of the reference
-        Object obj = null;
-        if (refObj instanceof Reference) {
-            final Reference ref = (Reference) refObj;
-            if (isCorrectClass(ref.getClassName())) {
-                final RefAddr refAddr = ref.get("instanceKey");
-                if (refAddr != null && refAddr.getContent() != null) {
-                    // object was bound to JNDI via Referenceable API.
-                    obj = INSTANCE_MAP.get(refAddr.getContent());
-                } else {
-                    // Tomcat JNDI creates a Reference out of server.xml
-                    // <ResourceParam> configuration and passes it to an
-                    // instance of the factory given in server.xml.
-                    String key = null;
-                    if (name != null) {
-                        key = name.toString();
-                        obj = INSTANCE_MAP.get(key);
-                    }
-                    if (obj == null) {
-                        final InstanceKeyDataSource ds = getNewInstance(ref);
-                        setCommonProperties(ref, ds);
-                        obj = ds;
-                        if (key != null) {
-                            INSTANCE_MAP.put(key, ds);
-                        }
-                    }
-                }
-            }
-        }
-        return obj;
-    }
-
-    /**
-     * Tests if className is the value returned from getClass().getName().toString().
-     *
-     * @param className
-     *            The class name to test.
-     *
-     * @return true if and only if className is the value returned from getClass().getName().toString()
-     */
-    protected abstract boolean isCorrectClass(String className);
-
-    boolean parseBoolean(final RefAddr refAddr) {
-        return Boolean.parseBoolean(toString(refAddr));
-    }
-
-    int parseInt(final RefAddr refAddr) {
-        return Integer.parseInt(toString(refAddr));
-    }
-
-    long parseLong(final RefAddr refAddr) {
-        return Long.parseLong(toString(refAddr));
-    }
-
-    private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds)
-            throws IOException, ClassNotFoundException {
-
-        RefAddr refAddr = ref.get("dataSourceName");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDataSourceName(toString(refAddr));
-        }
-
-        refAddr = ref.get("description");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDescription(toString(refAddr));
-        }
-
-        refAddr = ref.get("jndiEnvironment");
-        if (refAddr != null && refAddr.getContent() != null) {
-            final byte[] serialized = (byte[]) refAddr.getContent();
-            ikds.setJndiEnvironment((Properties) deserialize(serialized));
-        }
-
-        refAddr = ref.get("loginTimeout");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setLoginTimeout(Duration.ofSeconds(parseInt(refAddr)));
-        }
-
-        // Pool properties
-        refAddr = ref.get("blockWhenExhausted");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr));
-        }
-
-        refAddr = ref.get("evictionPolicyClassName");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultEvictionPolicyClassName(toString(refAddr));
-        }
-
-        // Pool properties
-        refAddr = ref.get("lifo");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultLifo(parseBoolean(refAddr));
-        }
-
-        refAddr = ref.get("maxIdlePerKey");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMaxIdle(parseInt(refAddr));
-        }
-
-        refAddr = ref.get("maxTotalPerKey");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMaxTotal(parseInt(refAddr));
-        }
-
-        refAddr = ref.get("maxWaitMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMaxWait(Duration.ofMillis(parseLong(refAddr)));
-        }
-
-        refAddr = ref.get("minEvictableIdleTimeMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
-        }
-
-        refAddr = ref.get("minIdlePerKey");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultMinIdle(parseInt(refAddr));
-        }
-
-        refAddr = ref.get("numTestsPerEvictionRun");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr));
-        }
-
-        refAddr = ref.get("softMinEvictableIdleTimeMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultSoftMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
-        }
-
-        refAddr = ref.get("testOnCreate");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestOnCreate(parseBoolean(refAddr));
-        }
-
-        refAddr = ref.get("testOnBorrow");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestOnBorrow(parseBoolean(refAddr));
-        }
-
-        refAddr = ref.get("testOnReturn");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestOnReturn(parseBoolean(refAddr));
-        }
-
-        refAddr = ref.get("testWhileIdle");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTestWhileIdle(parseBoolean(refAddr));
-        }
-
-        refAddr = ref.get("timeBetweenEvictionRunsMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultDurationBetweenEvictionRuns(Duration.ofMillis(parseLong(refAddr)));
-        }
-
-        // Connection factory properties
-
-        refAddr = ref.get("validationQuery");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setValidationQuery(toString(refAddr));
-        }
-
-        refAddr = ref.get("validationQueryTimeout");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setValidationQueryTimeout(Duration.ofSeconds(parseInt(refAddr)));
-        }
-
-        refAddr = ref.get("rollbackAfterValidation");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setRollbackAfterValidation(parseBoolean(refAddr));
-        }
-
-        refAddr = ref.get("maxConnLifetimeMillis");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setMaxConnLifetime(Duration.ofMillis(parseLong(refAddr)));
-        }
-
-        // Connection properties
-
-        refAddr = ref.get("defaultAutoCommit");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultAutoCommit(Boolean.valueOf(toString(refAddr)));
-        }
-
-        refAddr = ref.get("defaultTransactionIsolation");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultTransactionIsolation(parseInt(refAddr));
-        }
-
-        refAddr = ref.get("defaultReadOnly");
-        if (refAddr != null && refAddr.getContent() != null) {
-            ikds.setDefaultReadOnly(Boolean.valueOf(toString(refAddr)));
-        }
-    }
-
-    String toString(final RefAddr refAddr) {
-        return refAddr.getContent().toString();
-    }
-}
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.naming.Context;
+import javax.naming.Name;
+import javax.naming.RefAddr;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+
+import org.apache.commons.dbcp2.ListException;
+import org.apache.commons.dbcp2.Utils;
+
+/**
+ * A JNDI ObjectFactory which creates {@code SharedPoolDataSource}s or {@code PerUserPoolDataSource}s
+ *
+ * @since 2.0
+ */
+abstract class InstanceKeyDataSourceFactory implements ObjectFactory {
+
+    private static final Map<String, InstanceKeyDataSource> INSTANCE_MAP = new ConcurrentHashMap<>();
+
+    /**
+     * Closes all pools associated with this class.
+     *
+     * @throws Exception
+     *             a {@link ListException} containing all exceptions thrown by {@link InstanceKeyDataSource#close()}
+     * @see InstanceKeyDataSource#close()
+     * @see ListException
+     * @since 2.4.0 throws a {@link ListException} instead of, in 2.3.0 and before, the first exception thrown by
+     *        {@link InstanceKeyDataSource#close()}.
+     */
+    public static void closeAll() throws Exception {
+        // Get iterator to loop over all instances of this data source.
+        final List<Throwable> exceptionList = new ArrayList<>(INSTANCE_MAP.size());
+        for (final Entry<String, InstanceKeyDataSource> next : INSTANCE_MAP.entrySet()) {
+            // Bullet-proof to avoid anything else but problems from InstanceKeyDataSource#close().
+            if (next != null) {
+                @SuppressWarnings("resource") final InstanceKeyDataSource value = next.getValue();
+                if (value != null) {
+                    try {
+                        value.close();
+                    } catch (final Exception e) {
+                        exceptionList.add(e);
+                    }
+                }
+            }
+        }
+        INSTANCE_MAP.clear();
+        if (!exceptionList.isEmpty()) {
+            throw new ListException("Could not close all InstanceKeyDataSource instances.", exceptionList);
+        }
+    }
+
+    /**
+     * Deserializes the provided byte array to create an object.
+     *
+     * @param data
+     *            Data to deserialize to create the configuration parameter.
+     *
+     * @return The Object created by deserializing the data.
+     *
+     * @throws ClassNotFoundException
+     *            If a class cannot be found during the deserialization of a configuration parameter.
+     * @throws IOException
+     *            If an I/O error occurs during the deserialization of a configuration parameter.
+     */
+    protected static final Object deserialize(final byte[] data) throws IOException, ClassNotFoundException {
+        ObjectInputStream in = null;
+        try {
+            in = new ObjectInputStream(new ByteArrayInputStream(data));
+            return in.readObject();
+        } finally {
+            Utils.closeQuietly(in);
+        }
+    }
+
+    static synchronized String registerNewInstance(final InstanceKeyDataSource ds) {
+        int max = 0;
+        for (final String s : INSTANCE_MAP.keySet()) {
+            if (s != null) {
+                try {
+                    max = Math.max(max, Integer.parseInt(s));
+                } catch (final NumberFormatException ignored) {
+                    // no sweat, ignore those keys
+                }
+            }
+        }
+        final String instanceKey = String.valueOf(max + 1);
+        // Put a placeholder here for now, so other instances will not
+        // take our key. We will replace with a pool when ready.
+        INSTANCE_MAP.put(instanceKey, ds);
+        return instanceKey;
+    }
+
+    static void removeInstance(final String key) {
+        if (key != null) {
+            INSTANCE_MAP.remove(key);
+        }
+    }
+
+    /**
+     * Creates an instance of the subclass and sets any properties contained in the Reference.
+     *
+     * @param ref
+     *            The properties to be set on the created DataSource
+     *
+     * @return A configured DataSource of the appropriate type.
+     *
+     * @throws ClassNotFoundException
+     *            If a class cannot be found during the deserialization of a configuration parameter.
+     * @throws IOException
+     *            If an I/O error occurs during the deserialization of a configuration parameter.
+     */
+    protected abstract InstanceKeyDataSource getNewInstance(Reference ref) throws IOException, ClassNotFoundException;
+
+    /**
+     * Implements ObjectFactory to create an instance of SharedPoolDataSource or PerUserPoolDataSource
+     */
+    @Override
+    public Object getObjectInstance(final Object refObj, final Name name, final Context context,
+            final Hashtable<?, ?> env) throws IOException, ClassNotFoundException {
+        // The spec says to return null if we can't create an instance
+        // of the reference
+        Object obj = null;
+        if (refObj instanceof Reference) {
+            final Reference ref = (Reference) refObj;
+            if (isCorrectClass(ref.getClassName())) {
+                final RefAddr refAddr = ref.get("instanceKey");
+                if (refAddr != null && refAddr.getContent() != null) {
+                    // object was bound to JNDI via Referenceable API.
+                    obj = INSTANCE_MAP.get(refAddr.getContent());
+                } else {
+                    // Tomcat JNDI creates a Reference out of server.xml
+                    // <ResourceParam> configuration and passes it to an
+                    // instance of the factory given in server.xml.
+                    String key = null;
+                    if (name != null) {
+                        key = name.toString();
+                        obj = INSTANCE_MAP.get(key);
+                    }
+                    if (obj == null) {
+                        final InstanceKeyDataSource ds = getNewInstance(ref);
+                        setCommonProperties(ref, ds);
+                        obj = ds;
+                        if (key != null) {
+                            INSTANCE_MAP.put(key, ds);
+                        }
+                    }
+                }
+            }
+        }
+        return obj;
+    }
+
+    /**
+     * Tests if className is the value returned from getClass().getName().toString().
+     *
+     * @param className
+     *            The class name to test.
+     *
+     * @return true if and only if className is the value returned from getClass().getName().toString()
+     */
+    protected abstract boolean isCorrectClass(String className);
+
+    boolean parseBoolean(final RefAddr refAddr) {
+        return Boolean.parseBoolean(toString(refAddr));
+    }
+
+    int parseInt(final RefAddr refAddr) {
+        return Integer.parseInt(toString(refAddr));
+    }
+
+    long parseLong(final RefAddr refAddr) {
+        return Long.parseLong(toString(refAddr));
+    }
+
+    private void setCommonProperties(final Reference ref, final InstanceKeyDataSource ikds)
+            throws IOException, ClassNotFoundException {
+
+        RefAddr refAddr = ref.get("dataSourceName");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDataSourceName(toString(refAddr));
+        }
+
+        refAddr = ref.get("description");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDescription(toString(refAddr));
+        }
+
+        refAddr = ref.get("jndiEnvironment");
+        if (refAddr != null && refAddr.getContent() != null) {
+            final byte[] serialized = (byte[]) refAddr.getContent();
+            ikds.setJndiEnvironment((Properties) deserialize(serialized));
+        }
+
+        refAddr = ref.get("loginTimeout");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setLoginTimeout(Duration.ofSeconds(parseInt(refAddr)));
+        }
+
+        // Pool properties
+        refAddr = ref.get("blockWhenExhausted");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultBlockWhenExhausted(parseBoolean(refAddr));
+        }
+
+        refAddr = ref.get("evictionPolicyClassName");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultEvictionPolicyClassName(toString(refAddr));
+        }
+
+        // Pool properties
+        refAddr = ref.get("lifo");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultLifo(parseBoolean(refAddr));
+        }
+
+        refAddr = ref.get("maxIdlePerKey");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultMaxIdle(parseInt(refAddr));
+        }
+
+        refAddr = ref.get("maxTotalPerKey");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultMaxTotal(parseInt(refAddr));
+        }
+
+        refAddr = ref.get("maxWaitMillis");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultMaxWait(Duration.ofMillis(parseLong(refAddr)));
+        }
+
+        refAddr = ref.get("minEvictableIdleTimeMillis");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
+        }
+
+        refAddr = ref.get("minIdlePerKey");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultMinIdle(parseInt(refAddr));
+        }
+
+        refAddr = ref.get("numTestsPerEvictionRun");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultNumTestsPerEvictionRun(parseInt(refAddr));
+        }
+
+        refAddr = ref.get("softMinEvictableIdleTimeMillis");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultSoftMinEvictableIdle(Duration.ofMillis(parseLong(refAddr)));
+        }
+
+        refAddr = ref.get("testOnCreate");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultTestOnCreate(parseBoolean(refAddr));
+        }
+
+        refAddr = ref.get("testOnBorrow");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultTestOnBorrow(parseBoolean(refAddr));
+        }
+
+        refAddr = ref.get("testOnReturn");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultTestOnReturn(parseBoolean(refAddr));
+        }
+
+        refAddr = ref.get("testWhileIdle");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultTestWhileIdle(parseBoolean(refAddr));
+        }
+
+        refAddr = ref.get("timeBetweenEvictionRunsMillis");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultDurationBetweenEvictionRuns(Duration.ofMillis(parseLong(refAddr)));
+        }
+
+        // Connection factory properties
+
+        refAddr = ref.get("validationQuery");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setValidationQuery(toString(refAddr));
+        }
+
+        refAddr = ref.get("validationQueryTimeout");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setValidationQueryTimeout(Duration.ofSeconds(parseInt(refAddr)));
+        }
+
+        refAddr = ref.get("rollbackAfterValidation");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setRollbackAfterValidation(parseBoolean(refAddr));
+        }
+
+        refAddr = ref.get("maxConnLifetimeMillis");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setMaxConnLifetime(Duration.ofMillis(parseLong(refAddr)));
+        }
+
+        // Connection properties
+
+        refAddr = ref.get("defaultAutoCommit");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultAutoCommit(Boolean.valueOf(toString(refAddr)));
+        }
+
+        refAddr = ref.get("defaultTransactionIsolation");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultTransactionIsolation(parseInt(refAddr));
+        }
+
+        refAddr = ref.get("defaultReadOnly");
+        if (refAddr != null && refAddr.getContent() != null) {
+            ikds.setDefaultReadOnly(Boolean.valueOf(toString(refAddr)));
+        }
+    }
+
+    String toString(final RefAddr refAddr) {
+        return refAddr.getContent().toString();
+    }
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java b/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java
index fe5891f2..7056a46f 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java
@@ -1,1318 +1,1318 @@
-/*
- * 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 java.time.Duration;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.NoSuchElementException;
-
-import javax.naming.NamingException;
-import javax.naming.Reference;
-import javax.naming.StringRefAddr;
-import javax.sql.ConnectionPoolDataSource;
-
-import org.apache.commons.dbcp2.SwallowedExceptionLogger;
-import org.apache.commons.dbcp2.Utils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.commons.pool2.ObjectPool;
-import org.apache.commons.pool2.impl.GenericObjectPool;
-
-/**
- * <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. This datasource uses individual pools per user, and some
- * properties can be set specifically for a given user, if the deployment environment can support initialization of
- * mapped properties. So for example, a pool of admin or write-access Connections can be guaranteed a certain number of
- * connections, separate from a maximum set for users with read-only connections.
- * </p>
- *
- * <p>
- * User passwords can be changed without re-initializing the datasource. When a
- * {@code getConnection(userName, password)} request is processed with a password that is different from those used
- * to create connections in the pool associated with {@code userName}, an attempt is made to create a new
- * connection using the supplied password and if this succeeds, the existing pool is cleared and a new pool is created
- * for connections using the new password.
- * </p>
- *
- * @since 2.0
- */
-public class PerUserPoolDataSource extends InstanceKeyDataSource {
-
-    private static final long serialVersionUID = 7872747993848065028L;
-
-    private static final Log log = LogFactory.getLog(PerUserPoolDataSource.class);
-
-    // Per user pool properties
-    private Map<String, Boolean> perUserBlockWhenExhausted;
-    private Map<String, String> perUserEvictionPolicyClassName;
-    private Map<String, Boolean> perUserLifo;
-    private Map<String, Integer> perUserMaxIdle;
-    private Map<String, Integer> perUserMaxTotal;
-    private Map<String, Duration> perUserMaxWaitDuration;
-    private Map<String, Duration> perUserMinEvictableIdleDuration;
-    private Map<String, Integer> perUserMinIdle;
-    private Map<String, Integer> perUserNumTestsPerEvictionRun;
-    private Map<String, Duration> perUserSoftMinEvictableIdleDuration;
-    private Map<String, Boolean> perUserTestOnCreate;
-    private Map<String, Boolean> perUserTestOnBorrow;
-    private Map<String, Boolean> perUserTestOnReturn;
-    private Map<String, Boolean> perUserTestWhileIdle;
-    private Map<String, Duration> perUserDurationBetweenEvictionRuns;
-
-    // Per user connection properties
-    private Map<String, Boolean> perUserDefaultAutoCommit;
-    private Map<String, Integer> perUserDefaultTransactionIsolation;
-    private Map<String, Boolean> perUserDefaultReadOnly;
-
-    /**
-     * Map to keep track of Pools for a given user.
-     */
-    private transient Map<PoolKey, PooledConnectionManager> managers = new HashMap<>();
-
-    /**
-     * Default no-arg constructor for Serialization.
-     */
-    public PerUserPoolDataSource() {
-    }
-
-    /**
-     * Clears pool(s) maintained by this data source.
-     *
-     * @see org.apache.commons.pool2.ObjectPool#clear()
-     * @since 2.3.0
-     */
-    public void clear() {
-        for (final PooledConnectionManager manager : managers.values()) {
-            try {
-                getCPDSConnectionFactoryPool(manager).clear();
-            } catch (final Exception closePoolException) {
-                // ignore and try to close others.
-            }
-        }
-        InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
-    }
-
-    /**
-     * Closes pool(s) maintained by this data source.
-     *
-     * @see org.apache.commons.pool2.ObjectPool#close()
-     */
-    @SuppressWarnings("resource")
-    @Override
-    public void close() {
-        for (final PooledConnectionManager manager : managers.values()) {
-            Utils.closeQuietly(getCPDSConnectionFactoryPool(manager));
-        }
-        InstanceKeyDataSourceFactory.removeInstance(getInstanceKey());
-    }
-
-    /**
-     * Converts a map with Long milliseconds values to another map with Duration values.
-     */
-    private Map<String, Duration> convertMap(final Map<String, Duration> currentMap, final Map<String, Long> longMap) {
-        final Map<String, Duration> durationMap = new HashMap<>();
-        longMap.forEach((k, v) -> durationMap.put(k, toDurationOrNull(v)));
-        if (currentMap == null) {
-            return durationMap;
-        }
-        currentMap.clear();
-        currentMap.putAll(durationMap);
-        return currentMap;
-
-    }
-
-    private HashMap<String, Boolean> createMap() {
-        // Should there be a default size different than what this ctor provides?
-        return new HashMap<>();
-    }
-
-    @Override
-    protected PooledConnectionManager getConnectionManager(final UserPassKey upKey) {
-        return managers.get(getPoolKey(upKey.getUserName()));
-    }
-
-    /**
-     * Gets the underlying pool but does NOT allocate it.
-     *
-     * @param manager A CPDSConnectionFactory.
-     * @return the underlying pool.
-     */
-    private ObjectPool<PooledConnectionAndInfo> getCPDSConnectionFactoryPool(final PooledConnectionManager manager) {
-        return ((CPDSConnectionFactory) manager).getPool();
-    }
-
-    /**
-     * Gets the number of active connections in the default pool.
-     *
-     * @return The number of active connections in the default pool.
-     */
-    public int getNumActive() {
-        return getNumActive(null);
-    }
-
-    /**
-     * Gets the number of active connections in the pool for a given user.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    @SuppressWarnings("resource")
-    public int getNumActive(final String userName) {
-        final ObjectPool<PooledConnectionAndInfo> pool = getPool(getPoolKey(userName));
-        return pool == null ? 0 : pool.getNumActive();
-    }
-
-    /**
-     * Gets the number of idle connections in the default pool.
-     *
-     * @return The number of idle connections in the default pool.
-     */
-    public int getNumIdle() {
-        return getNumIdle(null);
-    }
-
-    /**
-     * Gets the number of idle connections in the pool for a given user.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    @SuppressWarnings("resource")
-    public int getNumIdle(final String userName) {
-        final ObjectPool<PooledConnectionAndInfo> pool = getPool(getPoolKey(userName));
-        return pool == null ? 0 : pool.getNumIdle();
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getBlockWhenExhausted()} for the specified user's pool
-     * or the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public boolean getPerUserBlockWhenExhausted(final String userName) {
-        Boolean value = null;
-        if (perUserBlockWhenExhausted != null) {
-            value = perUserBlockWhenExhausted.get(userName);
-        }
-        if (value == null) {
-            return getDefaultBlockWhenExhausted();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific default value for {@link Connection#setAutoCommit(boolean)} for the specified user's pool.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public Boolean getPerUserDefaultAutoCommit(final String userName) {
-        Boolean value = null;
-        if (perUserDefaultAutoCommit != null) {
-            value = perUserDefaultAutoCommit.get(userName);
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific default value for {@link Connection#setReadOnly(boolean)} for the specified user's pool.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public Boolean getPerUserDefaultReadOnly(final String userName) {
-        Boolean value = null;
-        if (perUserDefaultReadOnly != null) {
-            value = perUserDefaultReadOnly.get(userName);
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific default value for {@link Connection#setTransactionIsolation(int)} for the specified user's
-     * pool.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public Integer getPerUserDefaultTransactionIsolation(final String userName) {
-        Integer value = null;
-        if (perUserDefaultTransactionIsolation != null) {
-            value = perUserDefaultTransactionIsolation.get(userName);
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getDurationBetweenEvictionRuns()} for the specified
-     * user's pool or the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     * @since 2.10.0
-     */
-    public Duration getPerUserDurationBetweenEvictionRuns(final String userName) {
-        Duration value = null;
-        if (perUserDurationBetweenEvictionRuns != null) {
-            value = perUserDurationBetweenEvictionRuns.get(userName);
-        }
-        if (value == null) {
-            return getDefaultDurationBetweenEvictionRuns();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getEvictionPolicyClassName()} for the specified user's
-     * pool or the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public String getPerUserEvictionPolicyClassName(final String userName) {
-        String value = null;
-        if (perUserEvictionPolicyClassName != null) {
-            value = perUserEvictionPolicyClassName.get(userName);
-        }
-        if (value == null) {
-            return getDefaultEvictionPolicyClassName();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getLifo()} for the specified user's pool or the default
-     * if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public boolean getPerUserLifo(final String userName) {
-        Boolean value = null;
-        if (perUserLifo != null) {
-            value = perUserLifo.get(userName);
-        }
-        if (value == null) {
-            return getDefaultLifo();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getMaxIdle()} for the specified user's pool or the
-     * default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public int getPerUserMaxIdle(final String userName) {
-        Integer value = null;
-        if (perUserMaxIdle != null) {
-            value = perUserMaxIdle.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMaxIdle();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getMaxTotal()} for the specified user's pool or the
-     * default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public int getPerUserMaxTotal(final String userName) {
-        Integer value = null;
-        if (perUserMaxTotal != null) {
-            value = perUserMaxTotal.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMaxTotal();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool or
-     * the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     * @since 2.10.0
-     */
-    public Duration getPerUserMaxWaitDuration(final String userName) {
-        Duration value = null;
-        if (perUserMaxWaitDuration != null) {
-            value = perUserMaxWaitDuration.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMaxWait();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getMaxWaitDuration()} for the specified user's pool or
-     * the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     * @deprecated Use {@link #getPerUserMaxWaitDuration}.
-     */
-    @Deprecated
-    public long getPerUserMaxWaitMillis(final String userName) {
-        return getPerUserMaxWaitDuration(userName).toMillis();
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified
-     * user's pool or the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value, never null.
-     * @since 2.10.0
-     */
-    public Duration getPerUserMinEvictableIdleDuration(final String userName) {
-        Duration value = null;
-        if (perUserMinEvictableIdleDuration != null) {
-            value = perUserMinEvictableIdleDuration.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMinEvictableIdleDuration();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getMinEvictableIdleDuration()} for the specified
-     * user's pool or the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     * @deprecated Use {@link #getPerUserMinEvictableIdleDuration(String)}.
-     */
-    @Deprecated
-    public long getPerUserMinEvictableIdleTimeMillis(final String userName) {
-        return getPerUserMinEvictableIdleDuration(userName).toMillis();
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getMinIdle()} for the specified user's pool or the
-     * default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public int getPerUserMinIdle(final String userName) {
-        Integer value = null;
-        if (perUserMinIdle != null) {
-            value = perUserMinIdle.get(userName);
-        }
-        if (value == null) {
-            return getDefaultMinIdle();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getNumTestsPerEvictionRun()} for the specified user's
-     * pool or the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     */
-    public int getPerUserNumTestsPerEvictionRun(final String userName) {
-        Integer value = null;
-        if (perUserNumTestsPerEvictionRun != null) {
-            value = perUserNumTestsPerEvictionRun.get(userName);
-        }
-        if (value == null) {
-            return getDefaultNumTestsPerEvictionRun();
-        }
-        return value;
-    }
-
-    /**
-     * Gets the user specific value for {@link GenericObjectPool#getSoftMinEvictableIdleDuration()} for the specified
-     * user's pool or the default if no user specific value is defined.
-     *
-     * @param userName
-     *            The user name key.
-     * @return The user specific value.
-     * @since 2.10.0
-     */
-    public Duration getPerUserSoftMinEvictableIdleDuration(final String userName) {
-        Duration value = null;
-        if (perUserSoftMinEvictableIdleDuration != null) {
-            value = perUserSoftMinEvictableIdleDuration.get(userName);
-        }
-        if (value == null) {
-            return getDefaultSoftMinEvictableIdleDuration();
-        }
-        return value;
-    }
... 3992 lines suppressed ...


[commons-dbcp] 04/07: Fix PMD UselessOverridingMethod (Design)

Posted by gg...@apache.org.
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

commit 1cbcba401ea92e044a0ff1e4674a92d6a1c58555
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 09:00:18 2022 -0400

    Fix PMD UselessOverridingMethod (Design)
---
 .../commons/dbcp2/datasources/CharArray.java       | 169 ++++++++++-----------
 1 file changed, 81 insertions(+), 88 deletions(-)

diff --git a/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java b/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java
index 0a098b9e..f1d9c2aa 100644
--- a/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java
+++ b/src/main/java/org/apache/commons/dbcp2/datasources/CharArray.java
@@ -1,88 +1,81 @@
-/*
- * 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.util.Arrays;
-
-import org.apache.commons.dbcp2.Utils;
-
-/**
- * A {@code char} array wrapper that does not reveal its contents inadvertently through toString(). In contrast to, for
- * example, AtomicReference which toString()'s its contents.
- *
- * May contain null.
- *
- * @since 2.9.0
- */
-final class CharArray {
-
-    static final CharArray NULL = new CharArray((char[]) null);
-
-    private final char[] chars;
-
-    CharArray(final char[] chars) {
-        this.chars = Utils.clone(chars);
-    }
-
-    CharArray(final String string) {
-        this.chars = Utils.toCharArray(string);
-    }
-
-    /**
-     * Converts the value of char array as a String.
-     *
-     * @return value as a string, may be null.
-     */
-    String asString() {
-        return Utils.toString(chars);
-    }
-
-    @Override
-    public boolean equals(final Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof CharArray)) {
-            return false;
-        }
-        final CharArray other = (CharArray) obj;
-        return Arrays.equals(chars, other.chars);
-    }
-
-    /**
-     * Gets the value of char array.
-     *
-     * @return value, may be null.
-     */
-    char[] get() {
-        return chars == null ? null : chars.clone();
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(chars);
-    }
-
-    /**
-     * Calls {@code super.toString()} and does not reveal its contents inadvertently.
-     */
-    @Override
-    public String toString() {
-        return super.toString();
-    }
-}
+/*
+ * 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.util.Arrays;
+
+import org.apache.commons.dbcp2.Utils;
+
+/**
+ * A {@code char} array wrapper that does not reveal its contents inadvertently through toString(). In contrast to, for
+ * example, AtomicReference which toString()'s its contents.
+ *
+ * May contain null.
+ *
+ * @since 2.9.0
+ */
+final class CharArray {
+
+    static final CharArray NULL = new CharArray((char[]) null);
+
+    private final char[] chars;
+
+    CharArray(final char[] chars) {
+        this.chars = Utils.clone(chars);
+    }
+
+    CharArray(final String string) {
+        this.chars = Utils.toCharArray(string);
+    }
+
+    /**
+     * Converts the value of char array as a String.
+     *
+     * @return value as a string, may be null.
+     */
+    String asString() {
+        return Utils.toString(chars);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof CharArray)) {
+            return false;
+        }
+        final CharArray other = (CharArray) obj;
+        return Arrays.equals(chars, other.chars);
+    }
+
+    /**
+     * Gets the value of char array.
+     *
+     * @return value, may be null.
+     */
+    char[] get() {
+        return chars == null ? null : chars.clone();
+    }
+
+    @Override
+    public int hashCode() {
+        return Arrays.hashCode(chars);
+    }
+
+}


[commons-dbcp] 05/07: Add PMD check to default Maven goal.

Posted by gg...@apache.org.
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

commit d59b4548520de3e2f0e355b072db4be5eb460286
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 09:10:17 2022 -0400

    Add PMD check to default Maven goal.
---
 pom.xml                 | 2 +-
 src/changes/changes.xml | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index fd0d11ec..79afaf21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -351,7 +351,7 @@
   </properties>
 
   <build>
-    <defaultGoal>clean verify apache-rat:check checkstyle:check japicmp:cmp javadoc:javadoc</defaultGoal>
+    <defaultGoal>clean verify apache-rat:check japicmp:cmp checkstyle:check pmd:check javadoc:javadoc</defaultGoal>
     <pluginManagement>
       <plugins>
         <plugin>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index fd8fe0d9..37c0c8b1 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -95,6 +95,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="add" due-to="Gary Gregory">
         Add github/codeql-action.
       </action>
+      <action dev="ggregory" type="add" due-to="Gary Gregory">
+        Add PMD check to default Maven goal.
+      </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.


[commons-dbcp] 07/07: Bump JaCoCo from 0.8.7 to 0.8.8

Posted by gg...@apache.org.
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

commit 74ee0145350cea4bd276ad955cc81befeb49a021
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 09:18:05 2022 -0400

    Bump JaCoCo from 0.8.7 to 0.8.8
---
 pom.xml                 | 2 +-
 src/changes/changes.xml | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 79afaf21..65741b82 100644
--- a/pom.xml
+++ b/pom.xml
@@ -329,7 +329,7 @@
     <!-- Constant for Commons Pool version (used in multiple places) -->
     <commons.pool.version>2.11.1</commons.pool.version>
     <commons.japicmp.version>0.15.7</commons.japicmp.version>
-    <commons.jacoco.version>0.8.7</commons.jacoco.version>
+    <commons.jacoco.version>0.8.8</commons.jacoco.version>
     <!-- See DBCP-445 and DBCP-454 -->
     <commons.osgi.import>javax.transaction;version="1.1.0",javax.transaction.xa;version="1.1.0";partial=true;mandatory:=partial,*</commons.osgi.import>
     <commons.japicmp.ignoreMissingClasses>true</commons.japicmp.ignoreMissingClasses>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 37c0c8b1..858bc0d7 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -154,6 +154,9 @@ The <action> type attribute can be add,update,fix,remove.
       <action dev="ggregory" type="update" due-to="Dependabot">
         Bump commons-parent from 52 to 53 #180.
       </action>
+      <action dev="ggregory" type="update" due-to="Gary Gregory">
+        Bump JaCoCo from 0.8.7 to 0.8.8.
+      </action>
     </release>
     <release version="2.9.0" date="2021-07-30" description="This is a minor release, including bug fixes and enhancements.">
       <!-- ADD -->


[commons-dbcp] 06/07: Refactor duplicate code

Posted by gg...@apache.org.
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

commit 316ed1b324c141b82fb1e2d0cb4cbc91e94cb961
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sat Jul 2 09:17:57 2022 -0400

    Refactor duplicate code
---
 .../dbcp2/managed/SynchronizationAdapter.java      |  36 +
 .../commons/dbcp2/managed/TransactionContext.java  |   7 +-
 .../dbcp2/managed/TestBasicManagedDataSource.java  | 489 ++++++------
 .../dbcp2/managed/TestManagedDataSourceInTx.java   | 877 ++++++++++-----------
 4 files changed, 715 insertions(+), 694 deletions(-)

diff --git a/src/main/java/org/apache/commons/dbcp2/managed/SynchronizationAdapter.java b/src/main/java/org/apache/commons/dbcp2/managed/SynchronizationAdapter.java
new file mode 100644
index 00000000..f5a0211d
--- /dev/null
+++ b/src/main/java/org/apache/commons/dbcp2/managed/SynchronizationAdapter.java
@@ -0,0 +1,36 @@
+/*
+ * 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 javax.transaction.Synchronization;
+
+/**
+ * Implements {@link Synchronization} for subclasses.
+ */
+class SynchronizationAdapter implements Synchronization {
+
+    @Override
+    public void afterCompletion(final int status) {
+        // Noop
+    }
+
+    @Override
+    public void beforeCompletion() {
+        // Noop
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java b/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java
index aac64a01..e44a487d 100644
--- a/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java
+++ b/src/main/java/org/apache/commons/dbcp2/managed/TransactionContext.java
@@ -91,16 +91,11 @@ public class TransactionContext {
                 listener.afterCompletion(this, transaction != null && transaction.getStatus() == Status.STATUS_COMMITTED);
                 return;
             }
-            final Synchronization s = new Synchronization() {
+            final Synchronization s = new SynchronizationAdapter() {
                 @Override
                 public void afterCompletion(final int status) {
                     listener.afterCompletion(TransactionContext.this, status == Status.STATUS_COMMITTED);
                 }
-
-                @Override
-                public void beforeCompletion() {
-                    // empty
-                }
             };
             if (transactionSynchronizationRegistry != null) {
                 transactionSynchronizationRegistry.registerInterposedSynchronization(s);
diff --git a/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java b/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java
index 8fa5e7bf..7dbc3cf2 100644
--- a/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java
+++ b/src/test/java/org/apache/commons/dbcp2/managed/TestBasicManagedDataSource.java
@@ -1,247 +1,242 @@
-/*
-
-  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 static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.sql.Connection;
-import java.sql.SQLException;
-
-import javax.sql.XADataSource;
-import javax.transaction.Synchronization;
-import javax.transaction.TransactionManager;
-import javax.transaction.TransactionSynchronizationRegistry;
-import javax.transaction.xa.XAException;
-
-import org.apache.commons.dbcp2.BasicDataSource;
-import org.apache.commons.dbcp2.TestBasicDataSource;
-import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
-import org.h2.Driver;
-import org.h2.jdbcx.JdbcDataSource;
-import org.junit.jupiter.api.Test;
-
-import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
-import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple;
-
-/**
- * TestSuite for BasicManagedDataSource
- */
-public class TestBasicManagedDataSource extends TestBasicDataSource {
-
-    @Override
-    protected BasicDataSource createDataSource() throws Exception {
-        final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource();
-        final TransactionManagerImpl transactionManager = new TransactionManagerImpl();
-        basicManagedDataSource.setTransactionManager(transactionManager);
-        basicManagedDataSource.setTransactionSynchronizationRegistry(transactionManager);
-        return basicManagedDataSource;
-    }
-
-    @Test
-    public void testCreateXaDataSourceNewInstance() throws SQLException, XAException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setXADataSource(JdbcDataSource.class.getCanonicalName());
-            basicManagedDataSource.setDriverClassName(Driver.class.getName());
-            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
-            assertNotNull(basicManagedDataSource.createConnectionFactory());
-        }
-    }
-
-    @Test
-    public void testCreateXaDataSourceNoInstanceSetAndNoDataSource() throws SQLException, XAException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
-            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
-            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
-            assertNotNull(basicManagedDataSource.createConnectionFactory());
-        }
-    }
-
-    /**
-     * JIRA: DBCP-294
-     * Verify that PoolableConnections created by BasicManagedDataSource unregister themselves
-     * when reallyClosed.
-     */
-    @Test
-    public void testReallyClose() throws Exception {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
-            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
-            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
-            basicManagedDataSource.setUsername("userName");
-            basicManagedDataSource.setPassword("password");
-            basicManagedDataSource.setMaxIdle(1);
-            // Create two connections
-            final ManagedConnection<?> conn = (ManagedConnection<?>) basicManagedDataSource.getConnection();
-            assertNotNull(basicManagedDataSource.getTransactionRegistry().getXAResource(conn));
-            final ManagedConnection<?> conn2 = (ManagedConnection<?>) basicManagedDataSource.getConnection();
-            conn2.close(); // Return one connection to the pool
-            conn.close(); // No room at the inn - this will trigger reallyClose(), which should unregister
-            try {
-                basicManagedDataSource.getTransactionRegistry().getXAResource(conn);
-                fail("Expecting SQLException - XAResources orphaned");
-            } catch (final SQLException ex) {
-                // expected
-            }
-            conn2.close();
-        }
-    }
-
-    @Test
-    public void testRuntimeExceptionsAreRethrown() throws SQLException, XAException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
-            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
-            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
-            basicManagedDataSource.setUsername("userName");
-            basicManagedDataSource.setPassword("password");
-            basicManagedDataSource.setMaxIdle(1);
-            // results in a NPE
-            assertThrows(NullPointerException.class, () -> basicManagedDataSource.createPoolableConnectionFactory(null));
-        }
-    }
-
-    @Test
-    public void testSetDriverName() throws SQLException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setDriverClassName("adams");
-            assertEquals("adams", basicManagedDataSource.getDriverClassName());
-            basicManagedDataSource.setDriverClassName(null);
-            assertNull(basicManagedDataSource.getDriverClassName());
-        }
-    }
-
-    @Test
-    public void testSetNullXaDataSourceInstance() throws SQLException, XAException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
-            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
-            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
-            basicManagedDataSource.setUsername("userName");
-            basicManagedDataSource.setPassword("password");
-            basicManagedDataSource.setMaxIdle(1);
-            basicManagedDataSource.setXaDataSourceInstance(null);
-            assertNull(basicManagedDataSource.getXaDataSourceInstance());
-        }
-    }
-
-    /** DBCP-564 */
-    @Test
-    public void testSetRollbackOnlyBeforeGetConnectionDoesNotLeak() throws Exception {
-        final TransactionManager transactionManager = ((BasicManagedDataSource) ds).getTransactionManager();
-        final int n = 3;
-        ds.setMaxIdle(n);
-        ds.setMaxTotal(n);
-
-        for (int i = 0; i <= n; i++) { // loop n+1 times
-            transactionManager.begin();
-            transactionManager.setRollbackOnly();
-            try (final Connection conn = getConnection()) {
-                assertNotNull(conn);
-            }
-            transactionManager.rollback();
-        }
-
-        assertEquals(0, ds.getNumActive());
-        assertEquals(1, ds.getNumIdle());
-    }
-
-    @Test
-    public void testSetXaDataSourceInstance() throws SQLException, XAException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
-            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
-            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
-            basicManagedDataSource.setUsername("userName");
-            basicManagedDataSource.setPassword("password");
-            basicManagedDataSource.setMaxIdle(1);
-            basicManagedDataSource.setXaDataSourceInstance(new JdbcDataSource());
-            assertNotNull(basicManagedDataSource.createConnectionFactory());
-        }
-    }
-
-    @Test
-    public void testTransactionManagerNotSet() throws SQLException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            assertThrows(SQLException.class, basicManagedDataSource::createConnectionFactory);
-        }
-    }
-
-    @Test
-    public void testTransactionSynchronizationRegistry() throws Exception {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setTransactionManager(new TransactionManagerImple());
-            final TransactionSynchronizationRegistry tsr = new TransactionSynchronizationRegistryImple();
-            basicManagedDataSource.setTransactionSynchronizationRegistry(tsr);
-            final JdbcDataSource xaDataSource = new JdbcDataSource();
-            xaDataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
-            basicManagedDataSource.setXaDataSourceInstance(xaDataSource);
-            basicManagedDataSource.setMaxIdle(1);
-
-            final TransactionManager tm = basicManagedDataSource.getTransactionManager();
-            tm.begin();
-            tsr.registerInterposedSynchronization(new Synchronization() {
-                @Override
-                public void afterCompletion(final int i) {
-
-                }
-
-                @Override
-                public void beforeCompletion() {
-                    Connection connection = null;
-                    try {
-                        connection = basicManagedDataSource.getConnection();
-                        assertNotNull(connection);
-                    } catch (final SQLException e) {
-                        fail(e.getMessage());
-                    } finally {
-                        if (connection != null) {
-                            try {
-                                connection.close();
-                            } catch (final SQLException e) {
-                                fail(e.getMessage());
-                            }
-                        }
-                    }
-                }
-            });
-            tm.commit();
-        }
-    }
-
-    @Test
-    public void testXADataSource() throws SQLException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            basicManagedDataSource.setXADataSource("anything");
-            assertEquals("anything", basicManagedDataSource.getXADataSource());
-        }
-    }
-
-    @Test
-    public void testXaDataSourceInstance() throws SQLException {
-        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
-            final XADataSource ds = new JdbcDataSource();
-            basicManagedDataSource.setXaDataSourceInstance(ds);
-            assertEquals(ds, basicManagedDataSource.getXaDataSourceInstance());
-        }
-    }
-}
+/*
+
+  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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+import javax.sql.XADataSource;
+import javax.transaction.Synchronization;
+import javax.transaction.TransactionManager;
+import javax.transaction.TransactionSynchronizationRegistry;
+import javax.transaction.xa.XAException;
+
+import org.apache.commons.dbcp2.BasicDataSource;
+import org.apache.commons.dbcp2.TestBasicDataSource;
+import org.apache.geronimo.transaction.manager.TransactionManagerImpl;
+import org.h2.Driver;
+import org.h2.jdbcx.JdbcDataSource;
+import org.junit.jupiter.api.Test;
+
+import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple;
+import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple;
+
+/**
+ * TestSuite for BasicManagedDataSource
+ */
+public class TestBasicManagedDataSource extends TestBasicDataSource {
+
+    @Override
+    protected BasicDataSource createDataSource() throws Exception {
+        final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource();
+        final TransactionManagerImpl transactionManager = new TransactionManagerImpl();
+        basicManagedDataSource.setTransactionManager(transactionManager);
+        basicManagedDataSource.setTransactionSynchronizationRegistry(transactionManager);
+        return basicManagedDataSource;
+    }
+
+    @Test
+    public void testCreateXaDataSourceNewInstance() throws SQLException, XAException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setXADataSource(JdbcDataSource.class.getCanonicalName());
+            basicManagedDataSource.setDriverClassName(Driver.class.getName());
+            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
+            assertNotNull(basicManagedDataSource.createConnectionFactory());
+        }
+    }
+
+    @Test
+    public void testCreateXaDataSourceNoInstanceSetAndNoDataSource() throws SQLException, XAException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
+            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
+            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
+            assertNotNull(basicManagedDataSource.createConnectionFactory());
+        }
+    }
+
+    /**
+     * JIRA: DBCP-294
+     * Verify that PoolableConnections created by BasicManagedDataSource unregister themselves
+     * when reallyClosed.
+     */
+    @Test
+    public void testReallyClose() throws Exception {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
+            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
+            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
+            basicManagedDataSource.setUsername("userName");
+            basicManagedDataSource.setPassword("password");
+            basicManagedDataSource.setMaxIdle(1);
+            // Create two connections
+            final ManagedConnection<?> conn = (ManagedConnection<?>) basicManagedDataSource.getConnection();
+            assertNotNull(basicManagedDataSource.getTransactionRegistry().getXAResource(conn));
+            final ManagedConnection<?> conn2 = (ManagedConnection<?>) basicManagedDataSource.getConnection();
+            conn2.close(); // Return one connection to the pool
+            conn.close(); // No room at the inn - this will trigger reallyClose(), which should unregister
+            try {
+                basicManagedDataSource.getTransactionRegistry().getXAResource(conn);
+                fail("Expecting SQLException - XAResources orphaned");
+            } catch (final SQLException ex) {
+                // expected
+            }
+            conn2.close();
+        }
+    }
+
+    @Test
+    public void testRuntimeExceptionsAreRethrown() throws SQLException, XAException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
+            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
+            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
+            basicManagedDataSource.setUsername("userName");
+            basicManagedDataSource.setPassword("password");
+            basicManagedDataSource.setMaxIdle(1);
+            // results in a NPE
+            assertThrows(NullPointerException.class, () -> basicManagedDataSource.createPoolableConnectionFactory(null));
+        }
+    }
+
+    @Test
+    public void testSetDriverName() throws SQLException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setDriverClassName("adams");
+            assertEquals("adams", basicManagedDataSource.getDriverClassName());
+            basicManagedDataSource.setDriverClassName(null);
+            assertNull(basicManagedDataSource.getDriverClassName());
+        }
+    }
+
+    @Test
+    public void testSetNullXaDataSourceInstance() throws SQLException, XAException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
+            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
+            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
+            basicManagedDataSource.setUsername("userName");
+            basicManagedDataSource.setPassword("password");
+            basicManagedDataSource.setMaxIdle(1);
+            basicManagedDataSource.setXaDataSourceInstance(null);
+            assertNull(basicManagedDataSource.getXaDataSourceInstance());
+        }
+    }
+
+    /** DBCP-564 */
+    @Test
+    public void testSetRollbackOnlyBeforeGetConnectionDoesNotLeak() throws Exception {
+        final TransactionManager transactionManager = ((BasicManagedDataSource) ds).getTransactionManager();
+        final int n = 3;
+        ds.setMaxIdle(n);
+        ds.setMaxTotal(n);
+
+        for (int i = 0; i <= n; i++) { // loop n+1 times
+            transactionManager.begin();
+            transactionManager.setRollbackOnly();
+            try (final Connection conn = getConnection()) {
+                assertNotNull(conn);
+            }
+            transactionManager.rollback();
+        }
+
+        assertEquals(0, ds.getNumActive());
+        assertEquals(1, ds.getNumIdle());
+    }
+
+    @Test
+    public void testSetXaDataSourceInstance() throws SQLException, XAException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setTransactionManager(new TransactionManagerImpl());
+            basicManagedDataSource.setDriverClassName("org.apache.commons.dbcp2.TesterDriver");
+            basicManagedDataSource.setUrl("jdbc:apache:commons:testdriver");
+            basicManagedDataSource.setUsername("userName");
+            basicManagedDataSource.setPassword("password");
+            basicManagedDataSource.setMaxIdle(1);
+            basicManagedDataSource.setXaDataSourceInstance(new JdbcDataSource());
+            assertNotNull(basicManagedDataSource.createConnectionFactory());
+        }
+    }
+
+    @Test
+    public void testTransactionManagerNotSet() throws SQLException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            assertThrows(SQLException.class, basicManagedDataSource::createConnectionFactory);
+        }
+    }
+
+    @Test
+    public void testTransactionSynchronizationRegistry() throws Exception {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setTransactionManager(new TransactionManagerImple());
+            final TransactionSynchronizationRegistry tsr = new TransactionSynchronizationRegistryImple();
+            basicManagedDataSource.setTransactionSynchronizationRegistry(tsr);
+            final JdbcDataSource xaDataSource = new JdbcDataSource();
+            xaDataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
+            basicManagedDataSource.setXaDataSourceInstance(xaDataSource);
+            basicManagedDataSource.setMaxIdle(1);
+
+            final TransactionManager tm = basicManagedDataSource.getTransactionManager();
+            tm.begin();
+            tsr.registerInterposedSynchronization(new SynchronizationAdapter() {
+                @Override
+                public void beforeCompletion() {
+                    Connection connection = null;
+                    try {
+                        connection = basicManagedDataSource.getConnection();
+                        assertNotNull(connection);
+                    } catch (final SQLException e) {
+                        fail(e.getMessage());
+                    } finally {
+                        if (connection != null) {
+                            try {
+                                connection.close();
+                            } catch (final SQLException e) {
+                                fail(e.getMessage());
+                            }
+                        }
+                    }
+                }
+            });
+            tm.commit();
+        }
+    }
+
+    @Test
+    public void testXADataSource() throws SQLException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            basicManagedDataSource.setXADataSource("anything");
+            assertEquals("anything", basicManagedDataSource.getXADataSource());
+        }
+    }
+
+    @Test
+    public void testXaDataSourceInstance() throws SQLException {
+        try (final BasicManagedDataSource basicManagedDataSource = new BasicManagedDataSource()) {
+            final XADataSource ds = new JdbcDataSource();
+            basicManagedDataSource.setXaDataSourceInstance(ds);
+            assertEquals(ds, basicManagedDataSource.getXaDataSourceInstance());
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java b/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java
index dc6b6f75..d6db5efb 100644
--- a/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java
+++ b/src/test/java/org/apache/commons/dbcp2/managed/TestManagedDataSourceInTx.java
@@ -1,441 +1,436 @@
-/*
-
-  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 static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertSame;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.sql.CallableStatement;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.sql.Statement;
-
-import javax.transaction.Synchronization;
-import javax.transaction.Transaction;
-
-import org.apache.commons.dbcp2.DelegatingConnection;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-/**
- * Tests ManagedDataSource with an active transaction in progress.
- */
-public class TestManagedDataSourceInTx extends TestManagedDataSource {
-
-    // can't actually test close in a transaction
-    @Override
-    protected void assertBackPointers(final Connection conn, final Statement statement) throws SQLException {
-        assertFalse(conn.isClosed());
-        assertFalse(isClosed(statement));
-
-        assertSame(conn, statement.getConnection(),
-                "statement.getConnection() should return the exact same connection instance that was used to create the statement");
-
-        final ResultSet resultSet = statement.getResultSet();
-        assertFalse(isClosed(resultSet));
-        assertSame(statement, resultSet.getStatement(),
-                "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
-
-        final ResultSet executeResultSet = statement.executeQuery("select * from dual");
-        assertFalse(isClosed(executeResultSet));
-        assertSame(statement, executeResultSet.getStatement(),
-                "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
-
-        final ResultSet keysResultSet = statement.getGeneratedKeys();
-        assertFalse(isClosed(keysResultSet));
-        assertSame(statement, keysResultSet.getStatement(),
-                "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
-
-        ResultSet preparedResultSet = null;
-        if (statement instanceof PreparedStatement) {
-            final PreparedStatement preparedStatement = (PreparedStatement) statement;
-            preparedResultSet = preparedStatement.executeQuery();
-            assertFalse(isClosed(preparedResultSet));
-            assertSame(statement, preparedResultSet.getStatement(),
-                    "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
-        }
-
-
-        resultSet.getStatement().getConnection().close();
-    }
-
-    @Override
-    @BeforeEach
-    public void setUp() throws Exception {
-        super.setUp();
-        transactionManager.begin();
-    }
-
-    @Override
-    @AfterEach
-    public void tearDown() throws Exception {
-        if (transactionManager.getTransaction() != null) {
-            transactionManager.commit();
-        }
-        super.tearDown();
-    }
-
-    @Override
-    @Test
-    public void testAutoCommitBehavior() throws Exception {
-        final Connection connection = newConnection();
-
-        // auto commit should be off
-        assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled");
-
-        // attempt to set auto commit
-        try {
-            connection.setAutoCommit(true);
-            fail("setAutoCommit method should be disabled while enlisted in a transaction");
-        } catch (final SQLException e) {
-            // expected
-        }
-
-        // make sure it is still disabled
-        assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled");
-
-        // close connection
-        connection.close();
-    }
-
-    @Override
-    @Test
-    public void testClearWarnings() throws Exception {
-        // open a connection
-        Connection connection = newConnection();
-        assertNotNull(connection);
-
-        // generate SQLWarning on connection
-        final CallableStatement statement = connection.prepareCall("warning");
-        assertNotNull(connection.getWarnings());
-
-        // create a new shared connection
-        final Connection sharedConnection = newConnection();
-
-        // shared connection should see warning
-        assertNotNull(sharedConnection.getWarnings());
-
-        // close and allocate a new (original) connection
-        connection.close();
-        connection = newConnection();
-
-        // warnings should not have been cleared by closing the connection
-        assertNotNull(connection.getWarnings());
-        assertNotNull(sharedConnection.getWarnings());
-
-        statement.close();
-        connection.close();
-        sharedConnection.close();
-    }
-
-    @Test
-    public void testCloseInTransaction() throws Exception {
-        final DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
-        final DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection();
-
-        assertNotEquals(connectionA, connectionB);
-        assertNotEquals(connectionB, connectionA);
-        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
-        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
-
-        connectionA.close();
-        connectionB.close();
-
-        final Connection connection = newConnection();
-
-        assertFalse(connection.isClosed(), "Connection should be open");
-
-        connection.close();
-
-        assertTrue(connection.isClosed(), "Connection should be closed");
-    }
-
-    @Test
-    public void testCommit() throws Exception {
-        final Connection connection = newConnection();
-
-        // connection should be open
-        assertFalse(connection.isClosed(), "Connection should be open");
-
-        // attempt commit directly
-        try {
-            connection.commit();
-            fail("commit method should be disabled while enlisted in a transaction");
-        } catch (final SQLException e) {
-            // expected
-        }
-
-        // make sure it is still open
-        assertFalse(connection.isClosed(), "Connection should be open");
-
-        // close connection
-        connection.close();
-    }
-
-    @Override
-    @Test
-    public void testConnectionReturnOnCommit() throws Exception {
-       // override with no-op test
-    }
-
-    @Override
-    @Test
-    public void testConnectionsAreDistinct() throws Exception {
-        final Connection[] conn = new Connection[getMaxTotal()];
-        for(int i=0;i<conn.length;i++) {
-            conn[i] = newConnection();
-            for(int j=0;j<i;j++) {
-                // two connections should be distinct instances
-                Assertions.assertNotSame(conn[j], conn[i]);
-                // neither should they should be equivalent even though they are
-                // sharing the same underlying connection
-                Assertions.assertNotEquals(conn[j], conn[i]);
-                // Check underlying connection is the same
-                Assertions.assertEquals(((DelegatingConnection<?>) conn[j]).getInnermostDelegateInternal(),
-                        ((DelegatingConnection<?>) conn[i]).getInnermostDelegateInternal());
-            }
-        }
-        for (final Connection element : conn) {
-            element.close();
-        }
-    }
-
-    @Test
-    public void testDoubleReturn() throws Exception {
-        transactionManager.getTransaction().registerSynchronization(new Synchronization() {
-            private ManagedConnection<?> conn;
-
-            @Override
-            public void afterCompletion(final int i) {
-                final int numActive = pool.getNumActive();
-                try {
-                    conn.checkOpen();
-                } catch (final Exception e) {
-                    // Ignore
-                }
-                assertEquals(numActive, pool.getNumActive());
-                try {
-                    conn.close();
-                } catch (final Exception e) {
-                    fail("Should have been able to close the connection");
-                }
-                // TODO Requires DBCP-515 assertTrue(numActive -1 == pool.getNumActive());
-            }
-
-            @Override
-            public void beforeCompletion() {
-                try {
-                    conn = (ManagedConnection<?>) ds.getConnection();
-                    assertNotNull(conn);
-                } catch (final SQLException e) {
-                    fail("Could not get connection");
-                }
-            }
-        });
-        transactionManager.commit();
-    }
-
-    @Test
-    public void testGetConnectionInAfterCompletion() throws Exception {
-
-        final DelegatingConnection<?> connection = (DelegatingConnection<?>) newConnection();
-        // Don't close so we can check it for warnings in afterCompletion
-        transactionManager.getTransaction().registerSynchronization(new Synchronization() {
-            @Override
-            public void afterCompletion(final int i) {
-                try {
-                    final Connection connection1 = ds.getConnection();
-                    try {
-                        connection1.getWarnings();
-                        fail("Could operate on closed connection");
-                    } catch (final SQLException e) {
-                        // This is expected
-                    }
-                } catch (final SQLException e) {
-                    fail("Should have been able to get connection");
-                }
-            }
-
-            @Override
-            public void beforeCompletion() {
-                // empty
-            }
-        });
-        connection.close();
-        transactionManager.commit();
-    }
-
-    @Override
-    @Test
-    public void testHashCode() throws Exception {
-        final Connection conn1 = newConnection();
-        assertNotNull(conn1);
-        final Connection conn2 = newConnection();
-        assertNotNull(conn2);
-
-        // shared connections should not have the same hashcode
-        Assertions.assertNotEquals(conn1.hashCode(), conn2.hashCode());
-    }
-
-    /**
-     * @see #testSharedConnection()
-     */
-    @Override
-    @Test
-    public void testManagedConnectionEqualsFail() throws Exception {
-        // this test is invalid for managed connections since because
-        // two connections to the same datasource are supposed to share
-        // a single connection
-    }
-
-    @Override
-    @Test
-    public void testMaxTotal() throws Exception {
-        final Transaction[] transactions = new Transaction[getMaxTotal()];
-        final Connection[] c = new Connection[getMaxTotal()];
-        for (int i = 0; i < c.length; i++) {
-            // create a new connection in the current transaction
-            c[i] = newConnection();
-            assertNotNull(c[i]);
-
-            // suspend the current transaction and start a new one
-            transactions[i] = transactionManager.suspend();
-            assertNotNull(transactions[i]);
-            transactionManager.begin();
-        }
-
-        try {
-            newConnection();
-            fail("Allowed to open more than DefaultMaxTotal connections.");
-        } catch (final java.sql.SQLException e) {
-            // should only be able to open 10 connections, so this test should
-            // throw an exception
-        } finally {
-            transactionManager.commit();
-            for (int i = 0; i < c.length; i++) {
-                transactionManager.resume(transactions[i]);
-                c[i].close();
-                transactionManager.commit();
-            }
-        }
-    }
-
-    @Override
-    @Test
-    public void testNestedConnections() {
-        // Not supported
-    }
-
-    @Test
-    public void testReadOnly() throws Exception {
-        final Connection connection = newConnection();
-
-        // NOTE: This test class uses connections that are read-only by default
-
-        // connection should be read only
-        assertTrue(connection.isReadOnly(), "Connection be read-only");
-
-        // attempt to setReadOnly
-        try {
-            connection.setReadOnly(true);
-            fail("setReadOnly method should be disabled while enlisted in a transaction");
-        } catch (final SQLException e) {
-            // expected
-        }
-
-        // make sure it is still read-only
-        assertTrue(connection.isReadOnly(), "Connection be read-only");
-
-        // attempt to setReadonly
-        try {
-            connection.setReadOnly(false);
-            fail("setReadOnly method should be disabled while enlisted in a transaction");
-        } catch (final SQLException e) {
-            // expected
-        }
-
-        // make sure it is still read-only
-        assertTrue(connection.isReadOnly(), "Connection be read-only");
-
-        // close connection
-        connection.close();
-    }
-
-    @Override
-    @Test
-    public void testSharedConnection() throws Exception {
-        final DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
-        final DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection();
-
-        assertNotEquals(connectionA, connectionB);
-        assertNotEquals(connectionB, connectionA);
-        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
-        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
-
-        connectionA.close();
-        connectionB.close();
-    }
-
-    @Test
-    public void testSharedTransactionConversion() throws Exception {
-        final DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
-        final DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection();
-
-        // in a transaction the inner connections should be equal
-        assertNotEquals(connectionA, connectionB);
-        assertNotEquals(connectionB, connectionA);
-        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
-        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
-
-        transactionManager.commit();
-
-        // use the connection so it adjusts to the completed transaction
-        connectionA.getAutoCommit();
-        connectionB.getAutoCommit();
-
-        // no there is no transaction so inner connections should not be equal
-        assertNotEquals(connectionA, connectionB);
-        assertNotEquals(connectionB, connectionA);
-        assertFalse(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
-        assertFalse(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
-
-        transactionManager.begin();
-
-        // use the connection so it adjusts to the new transaction
-        connectionA.getAutoCommit();
-        connectionB.getAutoCommit();
-
-        // back in a transaction so inner connections should be equal again
-        assertNotEquals(connectionA, connectionB);
-        assertNotEquals(connectionB, connectionA);
-        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
-        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
-
-        connectionA.close();
-        connectionB.close();
-    }
-}
+/*
+
+  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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.sql.CallableStatement;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import javax.transaction.Synchronization;
+import javax.transaction.Transaction;
+
+import org.apache.commons.dbcp2.DelegatingConnection;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests ManagedDataSource with an active transaction in progress.
+ */
+public class TestManagedDataSourceInTx extends TestManagedDataSource {
+
+    // can't actually test close in a transaction
+    @Override
+    protected void assertBackPointers(final Connection conn, final Statement statement) throws SQLException {
+        assertFalse(conn.isClosed());
+        assertFalse(isClosed(statement));
+
+        assertSame(conn, statement.getConnection(),
+                "statement.getConnection() should return the exact same connection instance that was used to create the statement");
+
+        final ResultSet resultSet = statement.getResultSet();
+        assertFalse(isClosed(resultSet));
+        assertSame(statement, resultSet.getStatement(),
+                "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
+
+        final ResultSet executeResultSet = statement.executeQuery("select * from dual");
+        assertFalse(isClosed(executeResultSet));
+        assertSame(statement, executeResultSet.getStatement(),
+                "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
+
+        final ResultSet keysResultSet = statement.getGeneratedKeys();
+        assertFalse(isClosed(keysResultSet));
+        assertSame(statement, keysResultSet.getStatement(),
+                "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
+
+        ResultSet preparedResultSet = null;
+        if (statement instanceof PreparedStatement) {
+            final PreparedStatement preparedStatement = (PreparedStatement) statement;
+            preparedResultSet = preparedStatement.executeQuery();
+            assertFalse(isClosed(preparedResultSet));
+            assertSame(statement, preparedResultSet.getStatement(),
+                    "resultSet.getStatement() should return the exact same statement instance that was used to create the result set");
+        }
+
+
+        resultSet.getStatement().getConnection().close();
+    }
+
+    @Override
+    @BeforeEach
+    public void setUp() throws Exception {
+        super.setUp();
+        transactionManager.begin();
+    }
+
+    @Override
+    @AfterEach
+    public void tearDown() throws Exception {
+        if (transactionManager.getTransaction() != null) {
+            transactionManager.commit();
+        }
+        super.tearDown();
+    }
+
+    @Override
+    @Test
+    public void testAutoCommitBehavior() throws Exception {
+        final Connection connection = newConnection();
+
+        // auto commit should be off
+        assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled");
+
+        // attempt to set auto commit
+        try {
+            connection.setAutoCommit(true);
+            fail("setAutoCommit method should be disabled while enlisted in a transaction");
+        } catch (final SQLException e) {
+            // expected
+        }
+
+        // make sure it is still disabled
+        assertFalse(connection.getAutoCommit(), "Auto-commit should be disabled");
+
+        // close connection
+        connection.close();
+    }
+
+    @Override
+    @Test
+    public void testClearWarnings() throws Exception {
+        // open a connection
+        Connection connection = newConnection();
+        assertNotNull(connection);
+
+        // generate SQLWarning on connection
+        final CallableStatement statement = connection.prepareCall("warning");
+        assertNotNull(connection.getWarnings());
+
+        // create a new shared connection
+        final Connection sharedConnection = newConnection();
+
+        // shared connection should see warning
+        assertNotNull(sharedConnection.getWarnings());
+
+        // close and allocate a new (original) connection
+        connection.close();
+        connection = newConnection();
+
+        // warnings should not have been cleared by closing the connection
+        assertNotNull(connection.getWarnings());
+        assertNotNull(sharedConnection.getWarnings());
+
+        statement.close();
+        connection.close();
+        sharedConnection.close();
+    }
+
+    @Test
+    public void testCloseInTransaction() throws Exception {
+        final DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
+        final DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection();
+
+        assertNotEquals(connectionA, connectionB);
+        assertNotEquals(connectionB, connectionA);
+        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
+        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
+
+        connectionA.close();
+        connectionB.close();
+
+        final Connection connection = newConnection();
+
+        assertFalse(connection.isClosed(), "Connection should be open");
+
+        connection.close();
+
+        assertTrue(connection.isClosed(), "Connection should be closed");
+    }
+
+    @Test
+    public void testCommit() throws Exception {
+        final Connection connection = newConnection();
+
+        // connection should be open
+        assertFalse(connection.isClosed(), "Connection should be open");
+
+        // attempt commit directly
+        try {
+            connection.commit();
+            fail("commit method should be disabled while enlisted in a transaction");
+        } catch (final SQLException e) {
+            // expected
+        }
+
+        // make sure it is still open
+        assertFalse(connection.isClosed(), "Connection should be open");
+
+        // close connection
+        connection.close();
+    }
+
+    @Override
+    @Test
+    public void testConnectionReturnOnCommit() throws Exception {
+       // override with no-op test
+    }
+
+    @Override
+    @Test
+    public void testConnectionsAreDistinct() throws Exception {
+        final Connection[] conn = new Connection[getMaxTotal()];
+        for(int i=0;i<conn.length;i++) {
+            conn[i] = newConnection();
+            for(int j=0;j<i;j++) {
+                // two connections should be distinct instances
+                Assertions.assertNotSame(conn[j], conn[i]);
+                // neither should they should be equivalent even though they are
+                // sharing the same underlying connection
+                Assertions.assertNotEquals(conn[j], conn[i]);
+                // Check underlying connection is the same
+                Assertions.assertEquals(((DelegatingConnection<?>) conn[j]).getInnermostDelegateInternal(),
+                        ((DelegatingConnection<?>) conn[i]).getInnermostDelegateInternal());
+            }
+        }
+        for (final Connection element : conn) {
+            element.close();
+        }
+    }
+
+    @Test
+    public void testDoubleReturn() throws Exception {
+        transactionManager.getTransaction().registerSynchronization(new Synchronization() {
+            private ManagedConnection<?> conn;
+
+            @Override
+            public void afterCompletion(final int i) {
+                final int numActive = pool.getNumActive();
+                try {
+                    conn.checkOpen();
+                } catch (final Exception e) {
+                    // Ignore
+                }
+                assertEquals(numActive, pool.getNumActive());
+                try {
+                    conn.close();
+                } catch (final Exception e) {
+                    fail("Should have been able to close the connection");
+                }
+                // TODO Requires DBCP-515 assertTrue(numActive -1 == pool.getNumActive());
+            }
+
+            @Override
+            public void beforeCompletion() {
+                try {
+                    conn = (ManagedConnection<?>) ds.getConnection();
+                    assertNotNull(conn);
+                } catch (final SQLException e) {
+                    fail("Could not get connection");
+                }
+            }
+        });
+        transactionManager.commit();
+    }
+
+    @Test
+    public void testGetConnectionInAfterCompletion() throws Exception {
+
+        final DelegatingConnection<?> connection = (DelegatingConnection<?>) newConnection();
+        // Don't close so we can check it for warnings in afterCompletion
+        transactionManager.getTransaction().registerSynchronization(new SynchronizationAdapter() {
+            @Override
+            public void afterCompletion(final int i) {
+                try {
+                    final Connection connection1 = ds.getConnection();
+                    try {
+                        connection1.getWarnings();
+                        fail("Could operate on closed connection");
+                    } catch (final SQLException e) {
+                        // This is expected
+                    }
+                } catch (final SQLException e) {
+                    fail("Should have been able to get connection");
+                }
+            }
+        });
+        connection.close();
+        transactionManager.commit();
+    }
+
+    @Override
+    @Test
+    public void testHashCode() throws Exception {
+        final Connection conn1 = newConnection();
+        assertNotNull(conn1);
+        final Connection conn2 = newConnection();
+        assertNotNull(conn2);
+
+        // shared connections should not have the same hashcode
+        Assertions.assertNotEquals(conn1.hashCode(), conn2.hashCode());
+    }
+
+    /**
+     * @see #testSharedConnection()
+     */
+    @Override
+    @Test
+    public void testManagedConnectionEqualsFail() throws Exception {
+        // this test is invalid for managed connections since because
+        // two connections to the same datasource are supposed to share
+        // a single connection
+    }
+
+    @Override
+    @Test
+    public void testMaxTotal() throws Exception {
+        final Transaction[] transactions = new Transaction[getMaxTotal()];
+        final Connection[] c = new Connection[getMaxTotal()];
+        for (int i = 0; i < c.length; i++) {
+            // create a new connection in the current transaction
+            c[i] = newConnection();
+            assertNotNull(c[i]);
+
+            // suspend the current transaction and start a new one
+            transactions[i] = transactionManager.suspend();
+            assertNotNull(transactions[i]);
+            transactionManager.begin();
+        }
+
+        try {
+            newConnection();
+            fail("Allowed to open more than DefaultMaxTotal connections.");
+        } catch (final java.sql.SQLException e) {
+            // should only be able to open 10 connections, so this test should
+            // throw an exception
+        } finally {
+            transactionManager.commit();
+            for (int i = 0; i < c.length; i++) {
+                transactionManager.resume(transactions[i]);
+                c[i].close();
+                transactionManager.commit();
+            }
+        }
+    }
+
+    @Override
+    @Test
+    public void testNestedConnections() {
+        // Not supported
+    }
+
+    @Test
+    public void testReadOnly() throws Exception {
+        final Connection connection = newConnection();
+
+        // NOTE: This test class uses connections that are read-only by default
+
+        // connection should be read only
+        assertTrue(connection.isReadOnly(), "Connection be read-only");
+
+        // attempt to setReadOnly
+        try {
+            connection.setReadOnly(true);
+            fail("setReadOnly method should be disabled while enlisted in a transaction");
+        } catch (final SQLException e) {
+            // expected
+        }
+
+        // make sure it is still read-only
+        assertTrue(connection.isReadOnly(), "Connection be read-only");
+
+        // attempt to setReadonly
+        try {
+            connection.setReadOnly(false);
+            fail("setReadOnly method should be disabled while enlisted in a transaction");
+        } catch (final SQLException e) {
+            // expected
+        }
+
+        // make sure it is still read-only
+        assertTrue(connection.isReadOnly(), "Connection be read-only");
+
+        // close connection
+        connection.close();
+    }
+
+    @Override
+    @Test
+    public void testSharedConnection() throws Exception {
+        final DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
+        final DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection();
+
+        assertNotEquals(connectionA, connectionB);
+        assertNotEquals(connectionB, connectionA);
+        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
+        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
+
+        connectionA.close();
+        connectionB.close();
+    }
+
+    @Test
+    public void testSharedTransactionConversion() throws Exception {
+        final DelegatingConnection<?> connectionA = (DelegatingConnection<?>) newConnection();
+        final DelegatingConnection<?> connectionB = (DelegatingConnection<?>) newConnection();
+
+        // in a transaction the inner connections should be equal
+        assertNotEquals(connectionA, connectionB);
+        assertNotEquals(connectionB, connectionA);
+        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
+        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
+
+        transactionManager.commit();
+
+        // use the connection so it adjusts to the completed transaction
+        connectionA.getAutoCommit();
+        connectionB.getAutoCommit();
+
+        // no there is no transaction so inner connections should not be equal
+        assertNotEquals(connectionA, connectionB);
+        assertNotEquals(connectionB, connectionA);
+        assertFalse(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
+        assertFalse(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
+
+        transactionManager.begin();
+
+        // use the connection so it adjusts to the new transaction
+        connectionA.getAutoCommit();
+        connectionB.getAutoCommit();
+
+        // back in a transaction so inner connections should be equal again
+        assertNotEquals(connectionA, connectionB);
+        assertNotEquals(connectionB, connectionA);
+        assertTrue(connectionA.innermostDelegateEquals(connectionB.getInnermostDelegate()));
+        assertTrue(connectionB.innermostDelegateEquals(connectionA.getInnermostDelegate()));
+
+        connectionA.close();
+        connectionB.close();
+    }
+}