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