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