    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.dbcp2.cpdsadapter;
    019
    020import java.io.PrintWriter;
    021import java.io.Serializable;
    022import java.sql.DriverManager;
    023import java.sql.SQLException;
    024import java.sql.SQLFeatureNotSupportedException;
    025import java.util.Hashtable;
    026import java.util.Properties;
    027import java.util.logging.Logger;
    028
    029import javax.naming.Context;
    030import javax.naming.Name;
    031import javax.naming.NamingException;
    032import javax.naming.RefAddr;
    033import javax.naming.Reference;
    034import javax.naming.Referenceable;
    035import javax.naming.StringRefAddr;
    036import javax.naming.spi.ObjectFactory;
    037import javax.sql.ConnectionPoolDataSource;
    038import javax.sql.PooledConnection;
    039
    040import org.apache.commons.dbcp2.DelegatingPreparedStatement;
    041import org.apache.commons.dbcp2.PStmtKey;
    042import org.apache.commons.dbcp2.Utils;
    043import org.apache.commons.pool2.KeyedObjectPool;
    044import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
    045import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
    046import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
    047
    048/**
    049 * <p>
    050 * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but
    051 * still include a {@link java.sql.DriverManager} implementation. <code>ConnectionPoolDataSource</code>s are not used
    052 * within general applications. They are used by <code>DataSource</code> implementations that pool
    053 * <code>Connection</code>s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
    054 * will normally provide some method of initializing the <code>ConnectionPoolDataSource</code> whose attributes are
    055 * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical
    056 * connections to the database, when the pooling <code>DataSource</code> needs to create a new physical connection.
    057 * </p>
    058 * <p>
    059 * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any
    060 * bean and then attached directly to a pooling <code>DataSource</code>. <code>Jdbc2PoolDataSource</code> can use the
    061 * <code>ConnectionPoolDataSource</code> with or without the use of JNDI.
    062 * </p>
    063 * <p>
    064 * The DriverAdapterCPDS also provides <code>PreparedStatement</code> pooling which is not generally available in jdbc2
    065 * <code>ConnectionPoolDataSource</code> implementation, but is addressed within the jdbc3 specification. The
    066 * <code>PreparedStatement</code> pool in DriverAdapterCPDS has been in the dbcp package for some time, but it has not
    067 * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled
    068 * with the poolPreparedStatements attribute.
    069 * </p>
    070 * <p>
    071 * The <a href="package-summary.html">package documentation</a> contains an example using catalina and JNDI. The
    072 * <a href="../datasources/package-summary.html">datasources package documentation</a> shows how to use
    073 * <code>DriverAdapterCPDS</code> as a source for <code>Jdbc2PoolDataSource</code> without the use of JNDI.
    074 * </p>
    075 *
    076 * @since 2.0
    077 */
    078public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory {
    079
    080    private static final String KEY_USER = "user";
    081
    082    private static final String KEY_PASSWORD = "password";
    083
    084    private static final long serialVersionUID = -4820523787212147844L;
    085
    086    private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, "
    087            + "further initialization is not allowed.";
    088
    089    /** Description */
    090    private String description;
    091
    092    /** Url name */
    093    private String url;
    094
    095    /** User name */
    096    private String userName;
    097
    098    /** User password */
    099    private char[] userPassword;
    100
    101    /** Driver class name */
    102    private String driver;
    103
    104    /** Login TimeOut in seconds */
    105    private int loginTimeout;
    106
    107    /** Log stream. NOT USED */
    108    private transient PrintWriter logWriter;
    109
    110    // PreparedStatement pool properties
    111    private boolean poolPreparedStatements;
    112    private int maxIdle = 10;
    113    private long timeBetweenEvictionRunsMillis = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
    114    private int numTestsPerEvictionRun = -1;
    115    private int minEvictableIdleTimeMillis = -1;
    116    private int maxPreparedStatements = -1;
    117
    118    /** Whether or not getConnection has been called */
    119    private volatile boolean getConnectionCalled;
    120
    121    /** Connection properties passed to JDBC Driver */
    122    private Properties connectionProperties;
    123
    124    static {
    125        // Attempt to prevent deadlocks - see DBCP - 272
    126        DriverManager.getDrivers();
    127    }
    128
    129    /**
    130     * Controls access to the underlying connection
    131     */
    132    private boolean accessToUnderlyingConnectionAllowed;
    133
    134    /**
    135     * Default no-arg constructor for Serialization
    136     */
    137    public DriverAdapterCPDS() {
    138    }
    139
    140    /**
    141     * Attempts to establish a database connection using the default user and password.
    142     */
    143    @Override
    144    public PooledConnection getPooledConnection() throws SQLException {
    145        return getPooledConnection(getUser(), getPassword());
    146    }
    147
    148    /**
    149     * Attempt to establish a database connection.
    150     *
    151     * @param pooledUserName
    152     *            name to be used for the connection
    153     * @param pooledUserPassword
    154     *            password to be used fur the connection
    155     */
    156    @Override
    157    public PooledConnection getPooledConnection(final String pooledUserName, final String pooledUserPassword)
    158            throws SQLException {
    159        getConnectionCalled = true;
    160        PooledConnectionImpl pooledConnection = null;
    161        // Workaround for buggy WebLogic 5.1 classloader - ignore the exception upon first invocation.
    162        try {
    163            if (connectionProperties != null) {
    164                update(connectionProperties, KEY_USER, pooledUserName);
    165                update(connectionProperties, KEY_PASSWORD, pooledUserPassword);
    166                pooledConnection = new PooledConnectionImpl(
    167                        DriverManager.getConnection(getUrl(), connectionProperties));
    168            } else {
    169                pooledConnection = new PooledConnectionImpl(
    170                        DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword));
    171            }
    172            pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
    173        } catch (final ClassCircularityError e) {
    174            if (connectionProperties != null) {
    175                pooledConnection = new PooledConnectionImpl(
    176                        DriverManager.getConnection(getUrl(), connectionProperties));
    177            } else {
    178                pooledConnection = new PooledConnectionImpl(
    179                        DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword));
    180            }
    181            pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
    182        }
    183        KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = null;
    184        if (isPoolPreparedStatements()) {
    185            final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
    186            config.setMaxTotalPerKey(Integer.MAX_VALUE);
    187            config.setBlockWhenExhausted(false);
    188            config.setMaxWaitMillis(0);
    189            config.setMaxIdlePerKey(getMaxIdle());
    190            if (getMaxPreparedStatements() <= 0) {
    191                // since there is no limit, create a prepared statement pool with an eviction thread;
    192                // evictor settings are the same as the connection pool settings.
    193                config.setTimeBetweenEvictionRunsMillis(getTimeBetweenEvictionRunsMillis());
    194                config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
    195                config.setMinEvictableIdleTimeMillis(getMinEvictableIdleTimeMillis());
    196            } else {
    197                // since there is a limit, create a prepared statement pool without an eviction thread;
    198                // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
    199                // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method
    200                config.setMaxTotal(getMaxPreparedStatements());
    201                config.setTimeBetweenEvictionRunsMillis(-1);
    202                config.setNumTestsPerEvictionRun(0);
    203                config.setMinEvictableIdleTimeMillis(0);
    204            }
    205            stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config);
    206            pooledConnection.setStatementPool(stmtPool);
    207        }
    208        return pooledConnection;
    209    }
    210
    211    @Override
    212    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
    213        throw new SQLFeatureNotSupportedException();
    214    }
    215
    216    // ----------------------------------------------------------------------
    217    // Referenceable implementation
    218
    219    /**
    220     * <CODE>Referenceable</CODE> implementation.
    221     */
    222    @Override
    223    public Reference getReference() throws NamingException {
    224        // this class implements its own factory
    225        final String factory = getClass().getName();
    226
    227        final Reference ref = new Reference(getClass().getName(), factory, null);
    228
    229        ref.add(new StringRefAddr("description", getDescription()));
    230        ref.add(new StringRefAddr("driver", getDriver()));
    231        ref.add(new StringRefAddr("loginTimeout", String.valueOf(getLoginTimeout())));
    232        ref.add(new StringRefAddr(KEY_PASSWORD, getPassword()));
    233        ref.add(new StringRefAddr(KEY_USER, getUser()));
    234        ref.add(new StringRefAddr("url", getUrl()));
    235
    236        ref.add(new StringRefAddr("poolPreparedStatements", String.valueOf(isPoolPreparedStatements())));
    237        ref.add(new StringRefAddr("maxIdle", String.valueOf(getMaxIdle())));
    238        ref.add(new StringRefAddr("timeBetweenEvictionRunsMillis", String.valueOf(getTimeBetweenEvictionRunsMillis())));
    239        ref.add(new StringRefAddr("numTestsPerEvictionRun", String.valueOf(getNumTestsPerEvictionRun())));
    240        ref.add(new StringRefAddr("minEvictableIdleTimeMillis", String.valueOf(getMinEvictableIdleTimeMillis())));
    241        ref.add(new StringRefAddr("maxPreparedStatements", String.valueOf(getMaxPreparedStatements())));
    242
    243        return ref;
    244    }
    245
    246    // ----------------------------------------------------------------------
    247    // ObjectFactory implementation
    248
    249    /**
    250     * implements ObjectFactory to create an instance of this class
    251     */
    252    @Override
    253    public Object getObjectInstance(final Object refObj, final Name name, final Context context,
    254            final Hashtable<?, ?> env) throws Exception {
    255        // The spec says to return null if we can't create an instance
    256        // of the reference
    257        DriverAdapterCPDS cpds = null;
    258        if (refObj instanceof Reference) {
    259            final Reference ref = (Reference) refObj;
    260            if (ref.getClassName().equals(getClass().getName())) {
    261                RefAddr ra = ref.get("description");
    262                if (ra != null && ra.getContent() != null) {
    263                    setDescription(ra.getContent().toString());
    264                }
    265
    266                ra = ref.get("driver");
    267                if (ra != null && ra.getContent() != null) {
    268                    setDriver(ra.getContent().toString());
    269                }
    270                ra = ref.get("url");
    271                if (ra != null && ra.getContent() != null) {
    272                    setUrl(ra.getContent().toString());
    273                }
    274                ra = ref.get(KEY_USER);
    275                if (ra != null && ra.getContent() != null) {
    276                    setUser(ra.getContent().toString());
    277                }
    278                ra = ref.get(KEY_PASSWORD);
    279                if (ra != null && ra.getContent() != null) {
    280                    setPassword(ra.getContent().toString());
    281                }
    282
    283                ra = ref.get("poolPreparedStatements");
    284                if (ra != null && ra.getContent() != null) {
    285                    setPoolPreparedStatements(Boolean.valueOf(ra.getContent().toString()).booleanValue());
    286                }
    287                ra = ref.get("maxIdle");
    288                if (ra != null && ra.getContent() != null) {
    289                    setMaxIdle(Integer.parseInt(ra.getContent().toString()));
    290                }
    291
    292                ra = ref.get("timeBetweenEvictionRunsMillis");
    293                if (ra != null && ra.getContent() != null) {
    294                    setTimeBetweenEvictionRunsMillis(Integer.parseInt(ra.getContent().toString()));
    295                }
    296
    297                ra = ref.get("numTestsPerEvictionRun");
    298                if (ra != null && ra.getContent() != null) {
    299                    setNumTestsPerEvictionRun(Integer.parseInt(ra.getContent().toString()));
    300                }
    301
    302                ra = ref.get("minEvictableIdleTimeMillis");
    303                if (ra != null && ra.getContent() != null) {
    304                    setMinEvictableIdleTimeMillis(Integer.parseInt(ra.getContent().toString()));
    305                }
    306                ra = ref.get("maxPreparedStatements");
    307                if (ra != null && ra.getContent() != null) {
    308                    setMaxPreparedStatements(Integer.parseInt(ra.getContent().toString()));
    309                }
    310
    311                ra = ref.get("accessToUnderlyingConnectionAllowed");
    312                if (ra != null && ra.getContent() != null) {
    313                    setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(ra.getContent().toString()).booleanValue());
    314                }
    315
    316                cpds = this;
    317            }
    318        }
    319        return cpds;
    320    }
    321
    322    /**
    323     * Throws an IllegalStateException, if a PooledConnection has already been requested.
    324     */
    325    private void assertInitializationAllowed() throws IllegalStateException {
    326        if (getConnectionCalled) {
    327            throw new IllegalStateException(GET_CONNECTION_CALLED);
    328        }
    329    }
    330
    331    // ----------------------------------------------------------------------
    332    // Properties
    333
    334    /**
    335     * Gets the connection properties passed to the JDBC driver.
    336     *
    337     * @return the JDBC connection properties used when creating connections.
    338     */
    339    public Properties getConnectionProperties() {
    340        return connectionProperties;
    341    }
    342
    343    /**
    344     * <p>
    345     * Sets the connection properties passed to the JDBC driver.
    346     * </p>
    347     *
    348     * <p>
    349     * If <code>props</code> contains "user" and/or "password" properties, the corresponding instance properties are
    350     * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()}
    351     * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when
    352     * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or
    353     * {@link #setPassword(String)} overwrite the values of these properties if <code>connectionProperties</code> is not
    354     * null.
    355     * </p>
    356     *
    357     * @param props
    358     *            Connection properties to use when creating new connections.
    359     * @throws IllegalStateException
    360     *             if {@link #getPooledConnection()} has been called
    361     */
    362    public void setConnectionProperties(final Properties props) {
    363        assertInitializationAllowed();
    364        connectionProperties = props;
    365        if (connectionProperties != null) {
    366            if (connectionProperties.containsKey(KEY_USER)) {
    367                setUser(connectionProperties.getProperty(KEY_USER));
    368            }
    369            if (connectionProperties.containsKey(KEY_PASSWORD)) {
    370                setPassword(connectionProperties.getProperty(KEY_PASSWORD));
    371            }
    372        }
    373    }
    374
    375    /**
    376     * Gets the value of description. This property is here for use by the code which will deploy this datasource. It is
    377     * not used internally.
    378     *
    379     * @return value of description, may be null.
    380     * @see #setDescription(String)
    381     */
    382    public String getDescription() {
    383        return description;
    384    }
    385
    386    /**
    387     * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is
    388     * not used internally.
    389     *
    390     * @param v
    391     *            Value to assign to description.
    392     */
    393    public void setDescription(final String v) {
    394        this.description = v;
    395    }
    396
    397    /**
    398     * Gets the value of password for the default user.
    399     *
    400     * @return value of password.
    401     * @since 2.4.0
    402     */
    403    public char[] getPasswordCharArray() {
    404        return userPassword;
    405    }
    406
    407    /**
    408     * Gets the value of password for the default user.
    409     *
    410     * @return value of password.
    411     */
    412    public String getPassword() {
    413        return Utils.toString(userPassword);
    414    }
    415
    416    /**
    417     * Sets the value of password for the default user.
    418     *
    419     * @param userPassword
    420     *            Value to assign to password.
    421     * @throws IllegalStateException
    422     *             if {@link #getPooledConnection()} has been called
    423     */
    424    public void setPassword(final char[] userPassword) {
    425        assertInitializationAllowed();
    426        this.userPassword = Utils.clone(userPassword);
    427        update(connectionProperties, KEY_PASSWORD, Utils.toString(this.userPassword));
    428    }
    429
    430    /**
    431     * Sets the value of password for the default user.
    432     *
    433     * @param userPassword
    434     *            Value to assign to password.
    435     * @throws IllegalStateException
    436     *             if {@link #getPooledConnection()} has been called
    437     */
    438    public void setPassword(final String userPassword) {
    439        assertInitializationAllowed();
    440        this.userPassword = Utils.toCharArray(userPassword);
    441        update(connectionProperties, KEY_PASSWORD, userPassword);
    442    }
    443
    444    /**
    445     * Gets the value of url used to locate the database for this datasource.
    446     *
    447     * @return value of url.
    448     */
    449    public String getUrl() {
    450        return url;
    451    }
    452
    453    /**
    454     * Sets the value of URL string used to locate the database for this datasource.
    455     *
    456     * @param v
    457     *            Value to assign to url.
    458     * @throws IllegalStateException
    459     *             if {@link #getPooledConnection()} has been called
    460     */
    461    public void setUrl(final String v) {
    462        assertInitializationAllowed();
    463        this.url = v;
    464    }
    465
    466    /**
    467     * Gets the value of default user (login or user name).
    468     *
    469     * @return value of user.
    470     */
    471    public String getUser() {
    472        return userName;
    473    }
    474
    475    /**
    476     * Sets the value of default user (login or user name).
    477     *
    478     * @param v
    479     *            Value to assign to user.
    480     * @throws IllegalStateException
    481     *             if {@link #getPooledConnection()} has been called
    482     */
    483    public void setUser(final String v) {
    484        assertInitializationAllowed();
    485        this.userName = v;
    486        update(connectionProperties, KEY_USER, v);
    487    }
    488
    489    /**
    490     * Gets the driver class name.
    491     *
    492     * @return value of driver.
    493     */
    494    public String getDriver() {
    495        return driver;
    496    }
    497
    498    /**
    499     * Sets the driver class name. Setting the driver class name cause the driver to be registered with the
    500     * DriverManager.
    501     *
    502     * @param v
    503     *            Value to assign to driver.
    504     * @throws IllegalStateException
    505     *             if {@link #getPooledConnection()} has been called
    506     * @throws ClassNotFoundException
    507     *             if the class cannot be located
    508     */
    509    public void setDriver(final String v) throws ClassNotFoundException {
    510        assertInitializationAllowed();
    511        this.driver = v;
    512        // make sure driver is registered
    513        Class.forName(v);
    514    }
    515
    516    /**
    517     * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database. NOT
    518     * USED.
    519     */
    520    @Override
    521    public int getLoginTimeout() {
    522        return loginTimeout;
    523    }
    524
    525    /**
    526     * Gets the log writer for this data source. NOT USED.
    527     */
    528    @Override
    529    public PrintWriter getLogWriter() {
    530        return logWriter;
    531    }
    532
    533    /**
    534     * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. NOT
    535     * USED.
    536     */
    537    @Override
    538    public void setLoginTimeout(final int seconds) {
    539        loginTimeout = seconds;
    540    }
    541
    542    /**
    543     * Sets the log writer for this data source. NOT USED.
    544     */
    545    @Override
    546    public void setLogWriter(final PrintWriter out) {
    547        logWriter = out;
    548    }
    549
    550    // ------------------------------------------------------------------
    551    // PreparedStatement pool properties
    552
    553    /**
    554     * Flag to toggle the pooling of <code>PreparedStatement</code>s
    555     *
    556     * @return value of poolPreparedStatements.
    557     */
    558    public boolean isPoolPreparedStatements() {
    559        return poolPreparedStatements;
    560    }
    561
    562    /**
    563     * Flag to toggle the pooling of <code>PreparedStatement</code>s
    564     *
    565     * @param poolPreparedStatements
    566     *            true to pool statements.
    567     * @throws IllegalStateException
    568     *             if {@link #getPooledConnection()} has been called
    569     */
    570    public void setPoolPreparedStatements(final boolean poolPreparedStatements) {
    571        assertInitializationAllowed();
    572        this.poolPreparedStatements = poolPreparedStatements;
    573    }
    574
    575    /**
    576     * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
    577     * negative for no limit.
    578     *
    579     * @return the value of maxIdle
    580     */
    581    public int getMaxIdle() {
    582        return this.maxIdle;
    583    }
    584
    585    /**
    586     * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
    587     * negative for no limit.
    588     *
    589     * @param maxIdle
    590     *            The maximum number of statements that can remain idle
    591     * @throws IllegalStateException
    592     *             if {@link #getPooledConnection()} has been called
    593     */
    594    public void setMaxIdle(final int maxIdle) {
    595        assertInitializationAllowed();
    596        this.maxIdle = maxIdle;
    597    }
    598
    599    /**
    600     * Gets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no
    601     * idle object evictor thread will be run.
    602     *
    603     * @return the value of the evictor thread timer
    604     * @see #setTimeBetweenEvictionRunsMillis(long)
    605     */
    606    public long getTimeBetweenEvictionRunsMillis() {
    607        return timeBetweenEvictionRunsMillis;
    608    }
    609
    610    /**
    611     * Sets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no
    612     * idle object evictor thread will be run.
    613     *
    614     * @param timeBetweenEvictionRunsMillis
    615     *            The number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive,
    616     *            no idle object evictor thread will be run.
    617     * @see #getTimeBetweenEvictionRunsMillis()
    618     * @throws IllegalStateException
    619     *             if {@link #getPooledConnection()} has been called
    620     */
    621    public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
    622        assertInitializationAllowed();
    623        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    624    }
    625
    626    /**
    627     * Gets the number of statements to examine during each run of the idle object evictor thread (if any.)
    628     *
    629     * @see #setNumTestsPerEvictionRun
    630     * @see #setTimeBetweenEvictionRunsMillis
    631     * @return the number of statements to examine during each run of the idle object evictor thread (if any.)
    632     */
    633    public int getNumTestsPerEvictionRun() {
    634        return numTestsPerEvictionRun;
    635    }
    636
    637    /**
    638     * Sets the number of statements to examine during each run of the idle object evictor thread (if any).
    639     * <p>
    640     * When a negative value is supplied, <tt>ceil({*link #numIdle})/abs({*link #getNumTestsPerEvictionRun})</tt> tests
    641     * will be run. I.e., when the value is <i>-n</i>, roughly one <i>n</i>th of the idle objects will be tested per
    642     * run.
    643     * </p>
    644     *
    645     * @param numTestsPerEvictionRun
    646     *            number of statements to examine per run
    647     * @see #getNumTestsPerEvictionRun()
    648     * @see #setTimeBetweenEvictionRunsMillis(long)
    649     * @throws IllegalStateException
    650     *             if {@link #getPooledConnection()} has been called
    651     */
    652    public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
    653        assertInitializationAllowed();
    654        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
    655    }
    656
    657    /**
    658     * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
    659     * idle object evictor (if any).
    660     *
    661     * @see #setMinEvictableIdleTimeMillis
    662     * @see #setTimeBetweenEvictionRunsMillis
    663     * @return the minimum amount of time a statement may sit idle in the pool.
    664     */
    665    public int getMinEvictableIdleTimeMillis() {
    666        return minEvictableIdleTimeMillis;
    667    }
    668
    669    /**
    670     * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
    671     * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
    672     *
    673     * @param minEvictableIdleTimeMillis
    674     *            minimum time to set (in ms)
    675     * @see #getMinEvictableIdleTimeMillis()
    676     * @see #setTimeBetweenEvictionRunsMillis(long)
    677     * @throws IllegalStateException
    678     *             if {@link #getPooledConnection()} has been called
    679     */
    680    public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) {
    681        assertInitializationAllowed();
    682        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    683    }
    684
    685    /**
    686     * Returns the value of the accessToUnderlyingConnectionAllowed property.
    687     *
    688     * @return true if access to the underlying is allowed, false otherwise.
    689     */
    690    public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
    691        return this.accessToUnderlyingConnectionAllowed;
    692    }
    693
    694    /**
    695     * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
    696     * the underlying connection. (Default: false)
    697     *
    698     * @param allow
    699     *            Access to the underlying connection is granted when true.
    700     */
    701    public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
    702        this.accessToUnderlyingConnectionAllowed = allow;
    703    }
    704
    705    /**
    706     * Gets the maximum number of prepared statements.
    707     *
    708     * @return maxPrepartedStatements value
    709     */
    710    public int getMaxPreparedStatements() {
    711        return maxPreparedStatements;
    712    }
    713
    714    /**
    715     * Sets the maximum number of prepared statements.
    716     *
    717     * @param maxPreparedStatements
    718     *            the new maximum number of prepared statements
    719     */
    720    public void setMaxPreparedStatements(final int maxPreparedStatements) {
    721        this.maxPreparedStatements = maxPreparedStatements;
    722    }
    723
    724    private void update(final Properties properties, final String key, final String value) {
    725        if (properties != null) {
    726            if (value == null) {
    727                properties.remove(key);
    728            } else {
    729                properties.setProperty(key, value);
    730            }
    731        }
    732    }
    733
    734    /**
    735     * Does not print the userName and userPassword field nor the 'user' or 'password' in the connectionProperties.
    736     *
    737     * @since 2.6.0
    738     */
    739    @Override
    740    public synchronized String toString() {
    741        final StringBuilder builder = new StringBuilder(super.toString());
    742        builder.append("[description=");
    743        builder.append(description);
    744        builder.append(", url=");
    745        // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string is not in a legal URL format?
    746        builder.append(url);
    747        builder.append(", driver=");
    748        builder.append(driver);
    749        builder.append(", loginTimeout=");
    750        builder.append(loginTimeout);
    751        builder.append(", poolPreparedStatements=");
    752        builder.append(poolPreparedStatements);
    753        builder.append(", maxIdle=");
    754        builder.append(maxIdle);
    755        builder.append(", timeBetweenEvictionRunsMillis=");
    756        builder.append(timeBetweenEvictionRunsMillis);
    757        builder.append(", numTestsPerEvictionRun=");
    758        builder.append(numTestsPerEvictionRun);
    759        builder.append(", minEvictableIdleTimeMillis=");
    760        builder.append(minEvictableIdleTimeMillis);
    761        builder.append(", maxPreparedStatements=");
    762        builder.append(maxPreparedStatements);
    763        builder.append(", getConnectionCalled=");
    764        builder.append(getConnectionCalled);
    765        builder.append(", connectionProperties=");
    766        Properties tmpProps = connectionProperties;
    767        final String pwdKey = "password";
    768        if (connectionProperties.contains(pwdKey)) {
    769            tmpProps = (Properties) connectionProperties.clone();
    770            tmpProps.remove(pwdKey);
    771        }
    772        builder.append(tmpProps);
    773        builder.append(", accessToUnderlyingConnectionAllowed=");
    774        builder.append(accessToUnderlyingConnectionAllowed);
    775        builder.append("]");
    776        return builder.toString();
    777    }
    778}