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