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_TIME_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.setMinEvictableIdleTime(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.setMinEvictableIdleTime(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}