DataSourceProxy.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tomcat.jdbc.pool;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import javax.sql.XAConnection;

import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.PoolProperties.InterceptorDefinition;
import org.apache.tomcat.jdbc.pool.util.MessageFactory;

/**
 * The DataSource proxy lets us implements methods that don't exist in the current compiler JDK but might be methods
 * that are part of a future JDK DataSource interface. <br>
 * It's a trick to work around compiler issues when implementing interfaces. For example, I could put in Java 6 methods
 * of javax.sql.DataSource here, and compile it with JDK 1.5 and still be able to run under Java 6 without getting
 * NoSuchMethodException.
 */

public class DataSourceProxy implements PoolConfiguration {
    private static final Log log = LogFactory.getLog(DataSourceProxy.class);

    private final AtomicBoolean connectionWithoutCredentialsWarningIssued = new AtomicBoolean(false);

    /**
     * The underlying connection pool.
     */
    protected volatile ConnectionPool pool = null;

    /**
     * The pool configuration properties.
     */
    protected volatile PoolConfiguration poolProperties = null;

    /**
     * Terminal lifecycle flag. Set by {@link #close(boolean)} while holding this proxy's monitor, the same monitor
     * under which {@link #pCreatePool()} checks the flag and publishes its result. Once set, no {@link ConnectionPool}
     * is published any more: {@link #createPool()} fails with a {@link SQLException} instead of resurrecting a pool
     * that its owner has already stopped tracking, and a creation that was already in flight - whose constructor the
     * close did not wait for - closes the pool it built instead of publishing it.
     */
    private volatile boolean closed = false;

    /**
     * Serializes pool construction away from this proxy's monitor. The pool constructor pre-connects
     * {@link PoolConfiguration#getInitialSize() initialSize} connections, so against a hung database it blocks in the
     * driver for as long as the JDBC login timeout; running that under the monitor {@link #close(boolean)} takes made
     * the close wait on the driver for an unbounded, uninterruptible time. The gate keeps the construction serialized
     * anyway - it is the only point at which two pools are ever built from the same {@code poolProperties}, whose
     * contract is that no two pools are ever constructed from one instance at a time - while leaving the close free,
     * as it only has to serialize against the short publication step, not the driver. This lock is taken before the
     * proxy monitor and never while holding it, so it cannot invert against the monitor's other users:
     * {@link #close(boolean)} and, in the {@link DataSource} subclass, the JMX registration callbacks; none of them
     * reaches pool creation.
     */
    private final Object poolCreationLock = new Object();

    /**
     * Construct a DataSourceProxy with default pool properties.
     */
    public DataSourceProxy() {
        this(new PoolProperties());
    }

    /**
     * Construct a DataSourceProxy with the given pool configuration.
     *
     * @param poolProperties The pool configuration properties
     */
    public DataSourceProxy(PoolConfiguration poolProperties) {
        if (poolProperties == null) {
            throw new NullPointerException(MessageFactory.get("dataSourceProxy.poolConfigurationNull"));
        }
        this.poolProperties = poolProperties;
    }


    /**
     * Check if this proxy implements, or wraps an instance of, the given interface. The configured
     * {@link PoolConfiguration#getDataSource() DataSource}, if any, is checked as the wrapped object.
     * <p>
     * Has to match signature in DataSource.
     *
     * @param iface The interface to check
     *
     * @return {@code true} if this proxy implements the interface or wraps an object that does
     *
     * @throws SQLException if the wrapped data source throws one
     */
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return true;
        }
        Object dataSource = poolProperties.getDataSource();
        if (iface.isInstance(dataSource)) {
            return true;
        }
        if (dataSource instanceof java.sql.Wrapper) {
            // Like the pooled connections, the configured data source may
            // itself be a wrapper - of the driver's data source, of a
            // pooling data source the configuration replaced - and it is
            // what answers for its own layers
            return ((java.sql.Wrapper) dataSource).isWrapperFor(iface);
        }
        return false;
    }


    /**
     * Unwrap the proxy to the given interface. The configured {@link PoolConfiguration#getDataSource() DataSource}, if
     * any, is checked as the wrapped object.
     * <p>
     * Has to match signature in DataSource
     *
     * @param <T>   The interface type
     * @param iface The interface to unwrap to
     *
     * @return an object that implements the interface
     *
     * @throws SQLException if no object implementing the interface is found
     */
    public <T> T unwrap(Class<T> iface) throws SQLException {
        if (iface.isInstance(this)) {
            return iface.cast(this);
        }
        Object dataSource = poolProperties.getDataSource();
        if (iface.isInstance(dataSource)) {
            return iface.cast(dataSource);
        }
        if (dataSource instanceof java.sql.Wrapper && ((java.sql.Wrapper) dataSource).isWrapperFor(iface)) {
            return ((java.sql.Wrapper) dataSource).unwrap(iface);
        }
        // The Wrapper contract asks for a failure, not for a silent null
        // that turns into an unrelated NullPointerException at the caller's
        // first use of the "unwrapped" value.
        throw new SQLException(MessageFactory.get("dataSourceProxy.notAWrapper", iface.getName()));
    }

    /**
     * Get a database connection. {@link javax.sql.DataSource#getConnection()}
     *
     * @param username The user name
     * @param password The password
     *
     * @return the connection
     *
     * @throws SQLException Connection error, or this data source has been closed
     */
    public Connection getConnection(String username, String password) throws SQLException {
        if (this.getPoolProperties().isAlternateUsernameAllowed()) {
            // Go through createPool() rather than testing the field and then
            // calling it: that reads the volatile field only once, so a
            // close() running concurrently cannot turn this borrow into a
            // NullPointerException.
            return createPool().getConnection(username, password);
        } else {
            if (connectionWithoutCredentialsWarningIssued.compareAndSet(false, true)) {
                log.warn(MessageFactory.get("dataSourceProxy.alternateUsernameIgnored", username));
            }
            return getConnection();
        }
    }

    /**
     * Return the pool configuration properties.
     *
     * @return the pool configuration
     */
    public PoolConfiguration getPoolProperties() {
        return poolProperties;
    }

    /**
     * Sets up the connection pool, by creating a pooling driver. The reference is taken from the volatile field with a
     * single read, so that the borrow paths which use this method as their single point of access cannot observe a
     * {@code null} that a concurrent {@link #close(boolean)} published after they saw the field set. The result is
     * never {@code null}: it is either the pool that is in use or this method fails with an {@link SQLException}.
     *
     * @return the connection pool
     *
     * @throws SQLException Error creating pool, or if this data source has been closed
     */
    public ConnectionPool createPool() throws SQLException {
        // Read the volatile field once. Guarding on it and then returning it
        // would read it twice, and a close() landing between the reads makes
        // the second one null and hands a null pool out to the caller.
        final ConnectionPool p = pool;
        if (p != null) {
            return p;
        } else {
            return pCreatePool();
        }
    }

    /**
     * Sets up the connection pool, by creating a pooling driver.
     */
    private ConnectionPool pCreatePool() throws SQLException {
        // Runs under the construction gate, not under the monitor that
        // close(boolean) takes: the constructor pre-connects the initial
        // size of the pool, so against a hung database it blocks inside the
        // driver's connect for as long as the JDBC login timeout, and a
        // close parked behind that would be unbounded and uninterruptible.
        // Returning null is not possible here: either the closed flag
        // rejects the caller or a pool is published and returned.
        if (closed) {
            throw new SQLException(MessageFactory.get("dataSourceProxy.dataSourceClosed"));
        }
        synchronized (poolCreationLock) {
            // Re-check under the gate: a close completed while this thread
            // queued must not be resurrected, and a creator that won the
            // gate has already published its pool. The gate also keeps this
            // the only construction the shared poolProperties ever faces.
            if (closed) {
                throw new SQLException(MessageFactory.get("dataSourceProxy.dataSourceClosed"));
            }
            final ConnectionPool existing = pool;
            if (existing != null) {
                return existing;
            }
            // The constructor - and with it the driver connects of the
            // initial pre-load - runs with the close monitor free. A failure
            // here propagates directly: an init that fails after starting
            // anything tears its pool down itself before throwing.
            final ConnectionPool fresh = new ConnectionPool(poolProperties);
            final boolean rejected;
            synchronized (this) {
                // Check-and-publish against the closure, which sets the flag
                // and takes the field in one step under this same monitor:
                // whichever of the two runs first wins, so the close either
                // finds this pool published and closes it, or it found the
                // field empty and this thread takes the pool down below. It
                // is never neither - that was the leak - and never both.
                rejected = closed;
                if (!rejected) {
                    pool = fresh;
                }
            }
            if (rejected) {
                // close(boolean) completed while this pool was being built
                // and had nothing published to close, so the sessions the
                // pre-load established and the cleaner the constructor
                // started are this thread's to release.
                try {
                    fresh.close(true);
                } catch (Exception x) {
                    log.warn(MessageFactory.get("dataSourceProxy.errorClosingPool"), x);
                }
                throw new SQLException(MessageFactory.get("dataSourceProxy.dataSourceClosed"));
            }
            return fresh;
        }
    }

    /**
     * Get a database connection. {@link javax.sql.DataSource#getConnection()}
     *
     * @return the connection
     *
     * @throws SQLException Connection error, or this data source has been closed
     */
    public Connection getConnection() throws SQLException {
        // One read of the volatile field, through createPool(): a guard on
        // the field followed by a call on it reads it twice, and a close()
        // landing between the two reads makes the second one null and a
        // NullPointerException escapes this SQLException contract.
        return createPool().getConnection();
    }

    /**
     * Invokes an sync operation to retrieve the connection.
     *
     * @return a Future containing a reference to the connection when it becomes available
     *
     * @throws SQLException Connection error, or this data source has been closed
     */
    public Future<Connection> getConnectionAsync() throws SQLException {
        // See getConnection() on why the pool is reached through createPool()
        return createPool().getConnectionAsync();
    }

    /**
     * Get a database connection. {@link javax.sql.XADataSource#getXAConnection()}
     *
     * @return the connection
     *
     * @throws SQLException Connection error
     */
    public XAConnection getXAConnection() throws SQLException {
        Connection con = getConnection();
        if (con instanceof XAConnection) {
            return (XAConnection) con;
        } else {
            try {
                con.close();
            } catch (Exception ignore) {
                // Ignore
            }
            throw new SQLException(MessageFactory.get("dataSourceProxy.connectionNotXA"));
        }
    }

    /**
     * Get a database connection. {@link javax.sql.XADataSource#getXAConnection(String, String)}
     *
     * @param username The user name
     * @param password The password
     *
     * @return the connection
     *
     * @throws SQLException Connection error
     */
    public XAConnection getXAConnection(String username, String password) throws SQLException {
        Connection con = getConnection(username, password);
        if (con instanceof XAConnection) {
            return (XAConnection) con;
        } else {
            try {
                con.close();
            } catch (Exception ignore) {
                // Ignore
            }
            throw new SQLException(MessageFactory.get("dataSourceProxy.connectionNotXA"));
        }
    }


    /**
     * Get a database connection. {@link javax.sql.DataSource#getConnection()}
     *
     * @return the connection
     *
     * @throws SQLException Connection error
     */
    public javax.sql.PooledConnection getPooledConnection() throws SQLException {
        return (javax.sql.PooledConnection) getConnection();
    }

    /**
     * Get a database connection. {@link javax.sql.DataSource#getConnection(String,String)}
     *
     * @param username The user name
     * @param password The password
     *
     * @return the connection
     *
     * @throws SQLException Connection error
     */
    public javax.sql.PooledConnection getPooledConnection(String username, String password) throws SQLException {
        return (javax.sql.PooledConnection) getConnection(username, password);
    }

    /**
     * Return the underlying connection pool, creating it if necessary.
     *
     * @return the connection pool, or null if creation failed or this data source has been closed
     */
    public ConnectionPool getPool() {
        try {
            return createPool();
        } catch (SQLException x) {
            if (closed) {
                // A closed data source legitimately has no pool; this is
                // a normal lifecycle state, not a creation failure.
                return null;
            }
            log.error(MessageFactory.get("dataSourceProxy.errorCreatingPool"), x);
            return null;
        }
    }


    /**
     * Close the connection pool, returning connections to the database. The closure is terminal: later borrow or pool
     * creation attempts on this data source fail with an <code>SQLException</code> instead of recreating the pool.
     */
    public void close() {
        close(false);
    }

    /**
     * Close the connection pool. If a pool creation is in flight, this method does not wait for it - the driver
     * connects of the creation's initial pre-load can block for an unbounded, uninterruptible time against a hung
     * database - it marks this data source closed and returns: the creation observes the closure at its publication
     * step and closes the pool it built instead of publishing it.
     *
     * @param all if true close all connections, otherwise only idle ones
     */
    public synchronized void close(boolean all) {
        // Sets the terminal closed flag and takes the published pool in one
        // step under this monitor, which the publication step of
        // pCreatePool() also takes as one step: a creation whose pool is not
        // yet published cannot be missed by this close and then published
        // after it - it either wins the monitor and this close closes its
        // pool, or this close wins and that creation tears its pool down as
        // rejected. The closed flag makes the closure terminal for the pool
        // creation paths as well: a closed data source no longer resurrects
        // its pool on the next borrow or statistics access. The monitor is
        // never held across a pool construction, so this method never waits
        // on a driver connect.
        closed = true;
        final ConnectionPool p = pool;
        pool = null;
        if (p != null) {
            try {
                p.close(all);
            } catch (Exception x) {
                log.warn(MessageFactory.get("dataSourceProxy.errorClosingPool"), x);
            }
        }
    }

    /**
     * Return the current number of connections in the pool.
     *
     * @return the pool size
     */
    public int getPoolSize() {
        final ConnectionPool p = pool;
        if (p == null) {
            return 0;
        } else {
            return p.getSize();
        }
    }


    @Override
    public String toString() {
        return super.toString() + "{" + getPoolProperties() + "}";
    }


    @Override
    public String getPoolName() {
        return getPoolProperties().getPoolName();
    }


    /**
     * Set the pool configuration properties directly.
     *
     * @param poolProperties The new pool configuration
     */
    public void setPoolProperties(PoolConfiguration poolProperties) {
        // Same fail-fast check as the constructor: a null configuration would
        // leave the ~60 delegating accessors and the borrow paths to throw
        // bare NullPointerExceptions at opaque points, while the login timeout
        // accessors would silently degrade. The class invariant is that this
        // field is never null, so reject the misuse at its site.
        if (poolProperties == null) {
            throw new NullPointerException(MessageFactory.get("dataSourceProxy.poolConfigurationNull"));
        }
        this.poolProperties = poolProperties;
    }


    @Override
    public void setDriverClassName(String driverClassName) {
        this.poolProperties.setDriverClassName(driverClassName);
    }


    @Override
    public void setInitialSize(int initialSize) {
        this.poolProperties.setInitialSize(initialSize);
    }


    @Override
    public void setInitSQL(String initSQL) {
        this.poolProperties.setInitSQL(initSQL);
    }


    @Override
    public void setLogAbandoned(boolean logAbandoned) {
        this.poolProperties.setLogAbandoned(logAbandoned);
    }


    @Override
    public void setMaxActive(int maxActive) {
        this.poolProperties.setMaxActive(maxActive);
    }


    @Override
    public void setMaxIdle(int maxIdle) {
        this.poolProperties.setMaxIdle(maxIdle);
    }


    @Override
    public void setMaxWait(int maxWait) {
        this.poolProperties.setMaxWait(maxWait);
    }


    @Override
    public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
        this.poolProperties.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
    }


    @Override
    public void setMinIdle(int minIdle) {
        this.poolProperties.setMinIdle(minIdle);
    }


    @Override
    public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
        this.poolProperties.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
    }


    /**
     * Sets the password used to establish connections with, and mirrors it into the {@code password} database
     * property. A {@code null} password clears the credential: the entry is removed from the database properties
     * instead of the properties being set to null, which {@link java.util.Properties} rejects and which previously
     * threw after the field had already been cleared, leaving the previous password behind for the driver to keep
     * using. A configuration whose database properties are {@code null} only stores the field, as there is no
     * property collection to mirror into. {@inheritDoc}
     */
    @Override
    public void setPassword(String password) {
        this.poolProperties.setPassword(password);
        Properties dbProperties = this.poolProperties.getDbProperties();
        if (dbProperties != null) {
            String storedPassword = this.poolProperties.getPassword();
            if (storedPassword == null) {
                dbProperties.remove(PoolUtilities.PROP_PASSWORD);
            } else {
                dbProperties.setProperty(PoolUtilities.PROP_PASSWORD, storedPassword);
            }
        }
    }


    @Override
    public void setRemoveAbandoned(boolean removeAbandoned) {
        this.poolProperties.setRemoveAbandoned(removeAbandoned);
    }


    @Override
    public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
        this.poolProperties.setRemoveAbandonedTimeout(removeAbandonedTimeout);
    }


    @Override
    public void setTestOnBorrow(boolean testOnBorrow) {
        this.poolProperties.setTestOnBorrow(testOnBorrow);
    }


    @Override
    public void setTestOnConnect(boolean testOnConnect) {
        this.poolProperties.setTestOnConnect(testOnConnect);
    }


    @Override
    public void setTestOnReturn(boolean testOnReturn) {
        this.poolProperties.setTestOnReturn(testOnReturn);
    }


    @Override
    public void setTestWhileIdle(boolean testWhileIdle) {
        this.poolProperties.setTestWhileIdle(testWhileIdle);
    }


    @Override
    public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
        this.poolProperties.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
    }


    @Override
    public void setUrl(String url) {
        this.poolProperties.setUrl(url);
    }


    /**
     * Sets the user name used to establish connections with, and mirrors it into the {@code user} database property.
     * A {@code null} user name clears the credential: the entry is removed from the database properties instead of the
     * properties being set to null, which {@link java.util.Properties} rejects and which previously threw after the
     * field had already been cleared, leaving the previous user name behind for the driver to keep using. A
     * configuration whose database properties are {@code null} only stores the field, as there is no property
     * collection to mirror into. {@inheritDoc}
     */
    @Override
    public void setUsername(String username) {
        this.poolProperties.setUsername(username);
        Properties dbProperties = this.poolProperties.getDbProperties();
        if (dbProperties != null) {
            String storedUsername = this.poolProperties.getUsername();
            if (storedUsername == null) {
                dbProperties.remove(PoolUtilities.PROP_USER);
            } else {
                dbProperties.setProperty(PoolUtilities.PROP_USER, storedUsername);
            }
        }
    }


    @Override
    public void setValidationInterval(long validationInterval) {
        this.poolProperties.setValidationInterval(validationInterval);
    }


    @Override
    public void setValidationQuery(String validationQuery) {
        this.poolProperties.setValidationQuery(validationQuery);
    }


    @Override
    public void setValidatorClassName(String className) {
        this.poolProperties.setValidatorClassName(className);
    }


    @Override
    public void setValidationQueryTimeout(int validationQueryTimeout) {
        this.poolProperties.setValidationQueryTimeout(validationQueryTimeout);
    }


    @Override
    public void setJdbcInterceptors(String interceptors) {
        this.getPoolProperties().setJdbcInterceptors(interceptors);
    }


    @Override
    public void setJmxEnabled(boolean enabled) {
        this.getPoolProperties().setJmxEnabled(enabled);
    }


    @Override
    public void setFairQueue(boolean fairQueue) {
        this.getPoolProperties().setFairQueue(fairQueue);
    }


    @Override
    public void setUseLock(boolean useLock) {
        this.getPoolProperties().setUseLock(useLock);
    }


    @Override
    public void setDefaultCatalog(String catalog) {
        this.getPoolProperties().setDefaultCatalog(catalog);
    }


    @Override
    public void setDefaultAutoCommit(Boolean autocommit) {
        this.getPoolProperties().setDefaultAutoCommit(autocommit);
    }


    @Override
    public void setDefaultTransactionIsolation(int defaultTransactionIsolation) {
        this.getPoolProperties().setDefaultTransactionIsolation(defaultTransactionIsolation);
    }


    @Override
    public void setConnectionProperties(String properties) {
        try {
            // Delegate so the raw string is stored as well as parsed: a
            // hand-rolled merge into the database properties left
            // getConnectionProperties() reporting null for a value that
            // was just set, unlike every other implementation of the
            // interface (PoolProperties itself and the pool's JMX wrapper).
            getPoolProperties().setConnectionProperties(properties);
        } catch (RuntimeException x) {
            log.error(MessageFactory.get("dataSourceProxy.unableToParseConnectionProperties"), x);
            throw x;
        }
    }


    @Override
    public void setUseEquals(boolean useEquals) {
        this.getPoolProperties().setUseEquals(useEquals);
    }

    /**
     * no-op {@link javax.sql.DataSource#getParentLogger}
     *
     * @return no return value
     *
     * @throws SQLFeatureNotSupportedException Unsupported
     */
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        throw new SQLFeatureNotSupportedException();
    }

    /**
     * no-op {@link javax.sql.DataSource#getLogWriter}
     *
     * @return null
     *
     * @throws SQLException No exception
     */
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }


    /**
     * no-op {@link javax.sql.DataSource#setLogWriter(PrintWriter)}
     *
     * @param out Ignored
     *
     * @throws SQLException No exception
     */
    public void setLogWriter(PrintWriter out) throws SQLException {
        // NOOP
    }

    /**
     * no-op {@link javax.sql.DataSource#getLoginTimeout}
     *
     * @return the timeout
     */
    public int getLoginTimeout() {
        if (poolProperties == null) {
            return 0;
        } else {
            return poolProperties.getMaxWait() / 1000;
        }
    }

    /**
     * {@link javax.sql.DataSource#setLoginTimeout(int)}
     *
     * @param i The timeout value
     */
    public void setLoginTimeout(int i) {
        if (poolProperties == null) {
            return;
        } else {
            poolProperties.setMaxWait(1000 * i);
        }

    }


    @Override
    public int getSuspectTimeout() {
        return getPoolProperties().getSuspectTimeout();
    }


    @Override
    public void setSuspectTimeout(int seconds) {
        getPoolProperties().setSuspectTimeout(seconds);
    }

    // These members expose JMX attributes through Tomcat's dynamic
    // reflection.
    /**
     * If the pool has not been created, it will be created during this call.
     *
     * @return the number of established but idle connections
     */
    public int getIdle() {
        try {
            return createPool().getIdle();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * {@link #getIdle()}
     *
     * @return the number of established but idle connections
     */
    public int getNumIdle() {
        return getIdle();
    }

    /**
     * Forces an abandon check on the connection pool. If connections that have been abandoned exists, they will be
     * closed during this run
     */
    public void checkAbandoned() {
        try {
            createPool().checkAbandoned();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * Forces a check for resizing of the idle connections
     */
    public void checkIdle() {
        try {
            createPool().checkIdle();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * Returns the number of connections currently in use by the application.
     *
     * @return number of connections in use by the application
     */
    public int getActive() {
        try {
            return createPool().getActive();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * Returns the number of connections currently in use by the application.
     *
     * @return number of active connections
     */
    public int getNumActive() {
        return getActive();
    }

    /**
     * Returns the number of threads currently waiting for a connection from the pool.
     *
     * @return number of waiting threads
     */
    public int getWaitCount() {
        try {
            return createPool().getWaitCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * Returns the current total number of connections in the pool.
     *
     * @return current pool size
     */
    public int getSize() {
        try {
            return createPool().getSize();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * Performs a validation on idle connections.
     * <p>
     * Note: this operation is safe with concurrent traffic and with a closing pool even when the per-connection lock
     * is disabled (sweeper switched off and {@link PoolProperties#getUseLock()} returning {@code false}): each idle
     * connection is claimed out of the idle queue before it is validated or reconnected and is handed back, released
     * or - if the pool closed in the meantime - disconnected without ever re-entering the queue, as described on
     * {@link ConnectionPool#testAllIdle()}.
     */
    public void testIdle() {
        try {
            createPool().testAllIdle();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * The total number of connections borrowed from this pool.
     *
     * @return the borrowed connection count
     */
    public long getBorrowedCount() {
        try {
            return createPool().getBorrowedCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * The total number of connections returned to this pool.
     *
     * @return the returned connection count
     */
    public long getReturnedCount() {
        try {
            return createPool().getReturnedCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * The total number of connections created by this pool.
     *
     * @return the created connection count
     */
    public long getCreatedCount() {
        try {
            return createPool().getCreatedCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * The total number of connections released from this pool.
     *
     * @return the released connection count
     */
    public long getReleasedCount() {
        try {
            return createPool().getReleasedCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * The total number of connections reconnected by this pool.
     *
     * @return the reconnected connection count
     */
    public long getReconnectedCount() {
        try {
            return createPool().getReconnectedCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * The total number of connections released by remove abandoned.
     *
     * @return the PoolCleaner removed abandoned connection count
     */
    public long getRemoveAbandonedCount() {
        try {
            return createPool().getRemoveAbandonedCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * The total number of connections released by eviction.
     *
     * @return the PoolCleaner evicted idle connection count
     */
    public long getReleasedIdleCount() {
        try {
            return createPool().getReleasedIdleCount();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }

    /**
     * reset the statistics of this pool.
     */
    public void resetStats() {
        try {
            createPool().resetStats();
        } catch (SQLException x) {
            throw new RuntimeException(x);
        }
    }


    @Override
    public String getConnectionProperties() {
        return getPoolProperties().getConnectionProperties();
    }


    /**
     * Returns a copy of the database properties with the password removed. This object doubles as its own JMX/bean
     * surface and {@link #setPassword(String)} stores the password into the database properties, so returning them as
     * they are would expose the plaintext password through a getter while {@link #getPassword()} is explicitly masked -
     * the same protection the pool's own MBean
     * ({@link org.apache.tomcat.jdbc.pool.jmx.ConnectionPool#getDbProperties()}) already applies. {@inheritDoc}
     */
    @Override
    public Properties getDbProperties() {
        return PoolUtilities.cloneWithoutPassword(getPoolProperties().getDbProperties());
    }


    @Override
    public String getDefaultCatalog() {
        return getPoolProperties().getDefaultCatalog();
    }


    @Override
    public int getDefaultTransactionIsolation() {
        return getPoolProperties().getDefaultTransactionIsolation();
    }


    @Override
    public String getDriverClassName() {
        return getPoolProperties().getDriverClassName();
    }


    @Override
    public int getInitialSize() {
        return getPoolProperties().getInitialSize();
    }


    @Override
    public String getInitSQL() {
        return getPoolProperties().getInitSQL();
    }


    @Override
    public String getJdbcInterceptors() {
        return getPoolProperties().getJdbcInterceptors();
    }


    @Override
    public int getMaxActive() {
        return getPoolProperties().getMaxActive();
    }


    @Override
    public int getMaxIdle() {
        return getPoolProperties().getMaxIdle();
    }


    @Override
    public int getMaxWait() {
        return getPoolProperties().getMaxWait();
    }


    @Override
    public int getMinEvictableIdleTimeMillis() {
        return getPoolProperties().getMinEvictableIdleTimeMillis();
    }


    @Override
    public int getMinIdle() {
        return getPoolProperties().getMinIdle();
    }


    @Override
    public long getMaxAge() {
        return getPoolProperties().getMaxAge();
    }


    @Override
    public String getName() {
        return getPoolProperties().getName();
    }


    @Override
    public int getNumTestsPerEvictionRun() {
        return getPoolProperties().getNumTestsPerEvictionRun();
    }

    /**
     * @return DOES NOT RETURN THE PASSWORD, IT WOULD SHOW UP IN JMX
     */
    @Override
    public String getPassword() {
        return "Password not available as DataSource/JMX operation.";
    }


    @Override
    public int getRemoveAbandonedTimeout() {
        return getPoolProperties().getRemoveAbandonedTimeout();
    }


    @Override
    public int getTimeBetweenEvictionRunsMillis() {
        return getPoolProperties().getTimeBetweenEvictionRunsMillis();
    }


    @Override
    public String getUrl() {
        return getPoolProperties().getUrl();
    }


    @Override
    public String getUsername() {
        return getPoolProperties().getUsername();
    }


    @Override
    public long getValidationInterval() {
        return getPoolProperties().getValidationInterval();
    }


    @Override
    public String getValidationQuery() {
        return getPoolProperties().getValidationQuery();
    }


    @Override
    public int getValidationQueryTimeout() {
        return getPoolProperties().getValidationQueryTimeout();
    }


    @Override
    public String getValidatorClassName() {
        return getPoolProperties().getValidatorClassName();
    }


    @Override
    public Validator getValidator() {
        return getPoolProperties().getValidator();
    }

    @Override
    public void setValidator(Validator validator) {
        getPoolProperties().setValidator(validator);
    }


    @Override
    public boolean isAccessToUnderlyingConnectionAllowed() {
        return getPoolProperties().isAccessToUnderlyingConnectionAllowed();
    }


    @Override
    public Boolean isDefaultAutoCommit() {
        return getPoolProperties().isDefaultAutoCommit();
    }


    @Override
    public Boolean isDefaultReadOnly() {
        return getPoolProperties().isDefaultReadOnly();
    }


    @Override
    public boolean isLogAbandoned() {
        return getPoolProperties().isLogAbandoned();
    }


    @Override
    public boolean isPoolSweeperEnabled() {
        return getPoolProperties().isPoolSweeperEnabled();
    }


    @Override
    public boolean isRemoveAbandoned() {
        return getPoolProperties().isRemoveAbandoned();
    }


    @Override
    public int getAbandonWhenPercentageFull() {
        return getPoolProperties().getAbandonWhenPercentageFull();
    }


    @Override
    public boolean isTestOnBorrow() {
        return getPoolProperties().isTestOnBorrow();
    }


    @Override
    public boolean isTestOnConnect() {
        return getPoolProperties().isTestOnConnect();
    }


    @Override
    public boolean isTestOnReturn() {
        return getPoolProperties().isTestOnReturn();
    }


    @Override
    public boolean isTestWhileIdle() {
        return getPoolProperties().isTestWhileIdle();
    }


    @Override
    public Boolean getDefaultAutoCommit() {
        return getPoolProperties().getDefaultAutoCommit();
    }


    @Override
    public Boolean getDefaultReadOnly() {
        return getPoolProperties().getDefaultReadOnly();
    }


    @Override
    public InterceptorDefinition[] getJdbcInterceptorsAsArray() {
        return getPoolProperties().getJdbcInterceptorsAsArray();
    }


    @Override
    public boolean getUseLock() {
        return getPoolProperties().getUseLock();
    }


    @Override
    public boolean isFairQueue() {
        return getPoolProperties().isFairQueue();
    }


    @Override
    public boolean isJmxEnabled() {
        return getPoolProperties().isJmxEnabled();
    }


    @Override
    public boolean isUseEquals() {
        return getPoolProperties().isUseEquals();
    }


    @Override
    public void setAbandonWhenPercentageFull(int percentage) {
        getPoolProperties().setAbandonWhenPercentageFull(percentage);
    }


    @Override
    public void setAccessToUnderlyingConnectionAllowed(boolean accessToUnderlyingConnectionAllowed) {
        getPoolProperties().setAccessToUnderlyingConnectionAllowed(accessToUnderlyingConnectionAllowed);
    }


    @Override
    public void setDbProperties(Properties dbProperties) {
        getPoolProperties().setDbProperties(dbProperties);
    }


    @Override
    public void setDefaultReadOnly(Boolean defaultReadOnly) {
        getPoolProperties().setDefaultReadOnly(defaultReadOnly);
    }


    @Override
    public void setMaxAge(long maxAge) {
        getPoolProperties().setMaxAge(maxAge);
    }


    @Override
    public void setName(String name) {
        getPoolProperties().setName(name);
    }

    @Override
    public void setDataSource(Object ds) {
        getPoolProperties().setDataSource(ds);
    }

    @Override
    public Object getDataSource() {
        return getPoolProperties().getDataSource();
    }


    @Override
    public void setDataSourceJNDI(String jndiDS) {
        getPoolProperties().setDataSourceJNDI(jndiDS);
    }

    @Override
    public String getDataSourceJNDI() {
        return getPoolProperties().getDataSourceJNDI();
    }

    @Override
    public boolean isAlternateUsernameAllowed() {
        return getPoolProperties().isAlternateUsernameAllowed();
    }

    @Override
    public void setAlternateUsernameAllowed(boolean alternateUsernameAllowed) {
        getPoolProperties().setAlternateUsernameAllowed(alternateUsernameAllowed);
    }

    @Override
    public void setCommitOnReturn(boolean commitOnReturn) {
        getPoolProperties().setCommitOnReturn(commitOnReturn);
    }

    @Override
    public boolean getCommitOnReturn() {
        return getPoolProperties().getCommitOnReturn();
    }

    @Override
    public void setRollbackOnReturn(boolean rollbackOnReturn) {
        getPoolProperties().setRollbackOnReturn(rollbackOnReturn);
    }

    @Override
    public boolean getRollbackOnReturn() {
        return getPoolProperties().getRollbackOnReturn();
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated The disposable connection facade is always used, so this property no longer has any effect.
     */
    @Override
    @Deprecated
    public void setUseDisposableConnectionFacade(boolean useDisposableConnectionFacade) {
        getPoolProperties().setUseDisposableConnectionFacade(useDisposableConnectionFacade);
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated The disposable connection facade is always used, so this property no longer has any effect.
     */
    @Override
    @Deprecated
    public boolean getUseDisposableConnectionFacade() {
        return getPoolProperties().getUseDisposableConnectionFacade();
    }

    @Override
    public void setLogValidationErrors(boolean logValidationErrors) {
        getPoolProperties().setLogValidationErrors(logValidationErrors);
    }

    @Override
    public boolean getLogValidationErrors() {
        return getPoolProperties().getLogValidationErrors();
    }

    @Override
    public boolean getPropagateInterruptState() {
        return getPoolProperties().getPropagateInterruptState();
    }

    @Override
    public void setPropagateInterruptState(boolean propagateInterruptState) {
        getPoolProperties().setPropagateInterruptState(propagateInterruptState);
    }

    @Override
    public boolean isIgnoreExceptionOnPreLoad() {
        return getPoolProperties().isIgnoreExceptionOnPreLoad();
    }

    @Override
    public void setIgnoreExceptionOnPreLoad(boolean ignoreExceptionOnPreLoad) {
        getPoolProperties().setIgnoreExceptionOnPreLoad(ignoreExceptionOnPreLoad);
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated The statement facade is always used, so this property no longer has any effect.
     */
    @Override
    @Deprecated
    public boolean getUseStatementFacade() {
        return getPoolProperties().getUseStatementFacade();
    }

    /**
     * {@inheritDoc}
     *
     * @deprecated The statement facade is always used, so this property no longer has any effect.
     */
    @Override
    @Deprecated
    public void setUseStatementFacade(boolean useStatementFacade) {
        getPoolProperties().setUseStatementFacade(useStatementFacade);
    }

    /**
     * Purge all connections from the pool.
     */
    public void purge() {
        try {
            createPool().purge();
        } catch (SQLException x) {
            log.error(MessageFactory.get("dataSourceProxy.unableToPurgePool"), x);
        }
    }

    /**
     * Enable purging of connections on return to the pool.
     */
    public void purgeOnReturn() {
        try {
            createPool().purgeOnReturn();
        } catch (SQLException x) {
            log.error(MessageFactory.get("dataSourceProxy.unableToPurgePool"), x);
        }
    }
}