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