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