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:38 UTC

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

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 ...