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