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