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