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