View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.dbcp2.cpdsadapter;
18  
19  import java.io.PrintWriter;
20  import java.io.Serializable;
21  import java.sql.DriverManager;
22  import java.sql.SQLException;
23  import java.sql.SQLFeatureNotSupportedException;
24  import java.time.Duration;
25  import java.util.Hashtable;
26  import java.util.Properties;
27  import java.util.logging.Logger;
28  
29  import javax.naming.Context;
30  import javax.naming.Name;
31  import javax.naming.NamingException;
32  import javax.naming.RefAddr;
33  import javax.naming.Reference;
34  import javax.naming.Referenceable;
35  import javax.naming.StringRefAddr;
36  import javax.naming.spi.ObjectFactory;
37  import javax.sql.ConnectionPoolDataSource;
38  import javax.sql.PooledConnection;
39  
40  import org.apache.commons.dbcp2.Constants;
41  import org.apache.commons.dbcp2.DelegatingPreparedStatement;
42  import org.apache.commons.dbcp2.PStmtKey;
43  import org.apache.commons.dbcp2.Utils;
44  import org.apache.commons.pool2.KeyedObjectPool;
45  import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
46  import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
47  import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
48  
49  /**
50   * <p>
51   * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but
52   * still include a {@link java.sql.DriverManager} implementation. {@code ConnectionPoolDataSource}s are not used
53   * within general applications. They are used by {@code DataSource} implementations that pool
54   * {@code Connection}s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
55   * will normally provide some method of initializing the {@code ConnectionPoolDataSource} whose attributes are
56   * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical
57   * connections to the database, when the pooling {@code DataSource} needs to create a new physical connection.
58   * </p>
59   * <p>
60   * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any
61   * bean and then attached directly to a pooling {@code DataSource}. {@code Jdbc2PoolDataSource} can use the
62   * {@code ConnectionPoolDataSource} with or without the use of JNDI.
63   * </p>
64   * <p>
65   * The DriverAdapterCPDS also provides {@code PreparedStatement} pooling which is not generally available in jdbc2
66   * {@code ConnectionPoolDataSource} implementation, but is addressed within the JDBC 3 specification. The
67   * {@code PreparedStatement} pool in DriverAdapterCPDS has been in the DBCP package for some time, but it has not
68   * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled
69   * with the poolPreparedStatements attribute.
70   * </p>
71   * <p>
72   * The <a href="package-summary.html">package documentation</a> contains an example using Apache Catalina and JNDI. The
73   * <a href="../datasources/package-summary.html">datasources package documentation</a> shows how to use
74   * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI.
75   * </p>
76   *
77   * @since 2.0
78   */
79  public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory {
80  
81      private static final String KEY_MIN_EVICTABLE_IDLE_DURATION = "minEvictableIdleDuration";
82  
83      private static final String KEY_DURATION_BETWEEN_EVICTION_RUNS = "durationBetweenEvictionRuns";
84  
85      private static final String KEY_LOGIN_TIMEOUT = "loginTimeout";
86  
87      private static final String KEY_URL = "url";
88  
89      private static final String KEY_DRIVER = "driver";
90  
91      private static final String KEY_DESCRIPTION = "description";
92  
93      private static final String KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";
94  
95      private static final String KEY_MAX_PREPARED_STATEMENTS = "maxPreparedStatements";
96  
97      private static final String KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";
98  
99      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 }