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    *      https://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.Connection;
22  import java.sql.DriverManager;
23  import java.sql.PreparedStatement;
24  import java.sql.SQLException;
25  import java.sql.SQLFeatureNotSupportedException;
26  import java.time.Duration;
27  import java.util.Hashtable;
28  import java.util.Properties;
29  import java.util.logging.Logger;
30  
31  import javax.naming.Context;
32  import javax.naming.Name;
33  import javax.naming.NamingException;
34  import javax.naming.RefAddr;
35  import javax.naming.Reference;
36  import javax.naming.Referenceable;
37  import javax.naming.StringRefAddr;
38  import javax.naming.spi.ObjectFactory;
39  import javax.sql.ConnectionPoolDataSource;
40  import javax.sql.DataSource;
41  import javax.sql.PooledConnection;
42  
43  import org.apache.commons.dbcp2.Constants;
44  import org.apache.commons.dbcp2.DelegatingPreparedStatement;
45  import org.apache.commons.dbcp2.PStmtKey;
46  import org.apache.commons.dbcp2.Utils;
47  import org.apache.commons.pool2.KeyedObjectPool;
48  import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
49  import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
50  import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
51  
52  /**
53   * <p>
54   * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but
55   * still include a {@link java.sql.DriverManager} implementation. {@link ConnectionPoolDataSource}s are not used
56   * within general applications. They are used by {@link DataSource} implementations that pool
57   * {@link Connection}s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
58   * will normally provide some method of initializing the {@link ConnectionPoolDataSource} whose attributes are
59   * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical
60   * connections to the database, when the pooling {@link DataSource} needs to create a new physical connection.
61   * </p>
62   * <p>
63   * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any
64   * bean and then attached directly to a pooling {@link DataSource}. {@code Jdbc2PoolDataSource} can use the
65   * {@link ConnectionPoolDataSource} with or without the use of JNDI.
66   * </p>
67   * <p>
68   * The DriverAdapterCPDS also provides {@link PreparedStatement} pooling which is not generally available in jdbc2
69   * {@link ConnectionPoolDataSource} implementation, but is addressed within the JDBC 3 specification. The
70   * {@link PreparedStatement} pool in DriverAdapterCPDS has been in the DBCP package for some time, but it has not
71   * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled
72   * with the poolPreparedStatements attribute.
73   * </p>
74   * <p>
75   * The <a href="package-summary.html">package documentation</a> contains an example using Apache Catalina and JNDI. The
76   * <a href="../datasources/package-summary.html">datasources package documentation</a> shows how to use
77   * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI.
78   * </p>
79   *
80   * @since 2.0
81   */
82  public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory {
83  
84      private static final String KEY_MIN_EVICTABLE_IDLE_DURATION = "minEvictableIdleDuration";
85  
86      private static final String KEY_DURATION_BETWEEN_EVICTION_RUNS = "durationBetweenEvictionRuns";
87  
88      private static final String KEY_LOGIN_TIMEOUT = "loginTimeout";
89  
90      private static final String KEY_URL = "url";
91  
92      private static final String KEY_DRIVER = "driver";
93  
94      private static final String KEY_DESCRIPTION = "description";
95  
96      private static final String KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";
97  
98      private static final String KEY_MAX_PREPARED_STATEMENTS = "maxPreparedStatements";
99  
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 }