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