DriverAdapterCPDS.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.dbcp2.cpdsadapter;

  18. import java.io.PrintWriter;
  19. import java.io.Serializable;
  20. import java.sql.DriverManager;
  21. import java.sql.SQLException;
  22. import java.sql.SQLFeatureNotSupportedException;
  23. import java.time.Duration;
  24. import java.util.Hashtable;
  25. import java.util.Properties;
  26. import java.util.logging.Logger;

  27. import javax.naming.Context;
  28. import javax.naming.Name;
  29. import javax.naming.NamingException;
  30. import javax.naming.RefAddr;
  31. import javax.naming.Reference;
  32. import javax.naming.Referenceable;
  33. import javax.naming.StringRefAddr;
  34. import javax.naming.spi.ObjectFactory;
  35. import javax.sql.ConnectionPoolDataSource;
  36. import javax.sql.PooledConnection;

  37. import org.apache.commons.dbcp2.Constants;
  38. import org.apache.commons.dbcp2.DelegatingPreparedStatement;
  39. import org.apache.commons.dbcp2.PStmtKey;
  40. import org.apache.commons.dbcp2.Utils;
  41. import org.apache.commons.pool2.KeyedObjectPool;
  42. import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
  43. import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
  44. import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;

  45. /**
  46.  * <p>
  47.  * An adapter for JDBC drivers that do not include an implementation of {@link javax.sql.ConnectionPoolDataSource}, but
  48.  * still include a {@link java.sql.DriverManager} implementation. {@code ConnectionPoolDataSource}s are not used
  49.  * within general applications. They are used by {@code DataSource} implementations that pool
  50.  * {@code Connection}s, such as {@link org.apache.commons.dbcp2.datasources.SharedPoolDataSource}. A J2EE container
  51.  * will normally provide some method of initializing the {@code ConnectionPoolDataSource} whose attributes are
  52.  * presented as bean getters/setters and then deploying it via JNDI. It is then available as a source of physical
  53.  * connections to the database, when the pooling {@code DataSource} needs to create a new physical connection.
  54.  * </p>
  55.  * <p>
  56.  * Although normally used within a JNDI environment, the DriverAdapterCPDS can be instantiated and initialized as any
  57.  * bean and then attached directly to a pooling {@code DataSource}. {@code Jdbc2PoolDataSource} can use the
  58.  * {@code ConnectionPoolDataSource} with or without the use of JNDI.
  59.  * </p>
  60.  * <p>
  61.  * The DriverAdapterCPDS also provides {@code PreparedStatement} pooling which is not generally available in jdbc2
  62.  * {@code ConnectionPoolDataSource} implementation, but is addressed within the JDBC 3 specification. The
  63.  * {@code PreparedStatement} pool in DriverAdapterCPDS has been in the DBCP package for some time, but it has not
  64.  * undergone extensive testing in the configuration used here. It should be considered experimental and can be toggled
  65.  * with the poolPreparedStatements attribute.
  66.  * </p>
  67.  * <p>
  68.  * The <a href="package-summary.html">package documentation</a> contains an example using Apache Catalina and JNDI. The
  69.  * <a href="../datasources/package-summary.html">datasources package documentation</a> shows how to use
  70.  * {@code DriverAdapterCPDS} as a source for {@code Jdbc2PoolDataSource} without the use of JNDI.
  71.  * </p>
  72.  *
  73.  * @since 2.0
  74.  */
  75. public class DriverAdapterCPDS implements ConnectionPoolDataSource, Referenceable, Serializable, ObjectFactory {

  76.     private static final String KEY_MIN_EVICTABLE_IDLE_DURATION = "minEvictableIdleDuration";

  77.     private static final String KEY_DURATION_BETWEEN_EVICTION_RUNS = "durationBetweenEvictionRuns";

  78.     private static final String KEY_LOGIN_TIMEOUT = "loginTimeout";

  79.     private static final String KEY_URL = "url";

  80.     private static final String KEY_DRIVER = "driver";

  81.     private static final String KEY_DESCRIPTION = "description";

  82.     private static final String KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED = "accessToUnderlyingConnectionAllowed";

  83.     private static final String KEY_MAX_PREPARED_STATEMENTS = "maxPreparedStatements";

  84.     private static final String KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS = "minEvictableIdleTimeMillis";

  85.     private static final String KEY_NUM_TESTS_PER_EVICTION_RUN = "numTestsPerEvictionRun";

  86.     private static final String KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS = "timeBetweenEvictionRunsMillis";

  87.     private static final String KEY_MAX_IDLE = "maxIdle";

  88.     private static final String KEY_POOL_PREPARED_STATEMENTS = "poolPreparedStatements";

  89.     private static final long serialVersionUID = -4820523787212147844L;

  90.     private static final String GET_CONNECTION_CALLED = "A PooledConnection was already requested from this source, further initialization is not allowed.";

  91.     static {
  92.         // Attempt to prevent deadlocks - see DBCP-272
  93.         DriverManager.getDrivers();
  94.     }

  95.     /** Description */
  96.     private String description;

  97.     /** Connection string */
  98.     private String connectionString;

  99.     /** User name */
  100.     private String userName;

  101.     /** User password */
  102.     private char[] userPassword;

  103.     /** Driver class name */
  104.     private String driver;

  105.     /** Login TimeOut in seconds */
  106.     private int loginTimeout;

  107.     /** Log stream. NOT USED */
  108.     private transient PrintWriter logWriter;

  109.     // PreparedStatement pool properties
  110.     private boolean poolPreparedStatements;
  111.     private int maxIdle = 10;
  112.     private Duration durationBetweenEvictionRuns = BaseObjectPoolConfig.DEFAULT_DURATION_BETWEEN_EVICTION_RUNS;
  113.     private int numTestsPerEvictionRun = -1;
  114.     private Duration minEvictableIdleDuration = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_DURATION;

  115.     private int maxPreparedStatements = -1;

  116.     /** Whether or not getConnection has been called */
  117.     private volatile boolean getConnectionCalled;

  118.     /** Connection properties passed to JDBC Driver */
  119.     private Properties connectionProperties;

  120.     /**
  121.      * Controls access to the underlying connection
  122.      */
  123.     private boolean accessToUnderlyingConnectionAllowed;

  124.     /**
  125.      * Default no-argument constructor for Serialization
  126.      */
  127.     public DriverAdapterCPDS() {
  128.     }

  129.     /**
  130.      * Throws an IllegalStateException, if a PooledConnection has already been requested.
  131.      */
  132.     private void assertInitializationAllowed() throws IllegalStateException {
  133.         if (getConnectionCalled) {
  134.             throw new IllegalStateException(GET_CONNECTION_CALLED);
  135.         }
  136.     }

  137.     private boolean getBooleanContentString(final RefAddr ra) {
  138.         return Boolean.parseBoolean(getStringContent(ra));
  139.     }

  140.     /**
  141.      * Gets the connection properties passed to the JDBC driver.
  142.      *
  143.      * @return the JDBC connection properties used when creating connections.
  144.      */
  145.     public Properties getConnectionProperties() {
  146.         return connectionProperties;
  147.     }

  148.     /**
  149.      * Gets the value of description. This property is here for use by the code which will deploy this data source. It
  150.      * is not used internally.
  151.      *
  152.      * @return value of description, may be null.
  153.      * @see #setDescription(String)
  154.      */
  155.     public String getDescription() {
  156.         return description;
  157.     }

  158.     /**
  159.      * Gets the driver class name.
  160.      *
  161.      * @return value of driver.
  162.      */
  163.     public String getDriver() {
  164.         return driver;
  165.     }

  166.     /**
  167.      * Gets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
  168.      * idle object evictor thread will be run.
  169.      *
  170.      * @return the value of the evictor thread timer
  171.      * @see #setDurationBetweenEvictionRuns(Duration)
  172.      * @since 2.9.0
  173.      */
  174.     public Duration getDurationBetweenEvictionRuns() {
  175.         return durationBetweenEvictionRuns;
  176.     }

  177.     private int getIntegerStringContent(final RefAddr ra) {
  178.         return Integer.parseInt(getStringContent(ra));
  179.     }

  180.     /**
  181.      * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database. NOT
  182.      * USED.
  183.      */
  184.     @Override
  185.     public int getLoginTimeout() {
  186.         return loginTimeout;
  187.     }

  188.     /**
  189.      * Gets the log writer for this data source. NOT USED.
  190.      */
  191.     @Override
  192.     public PrintWriter getLogWriter() {
  193.         return logWriter;
  194.     }

  195.     /**
  196.      * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
  197.      * negative for no limit.
  198.      *
  199.      * @return the value of maxIdle
  200.      */
  201.     public int getMaxIdle() {
  202.         return maxIdle;
  203.     }

  204.     /**
  205.      * Gets the maximum number of prepared statements.
  206.      *
  207.      * @return maxPrepartedStatements value
  208.      */
  209.     public int getMaxPreparedStatements() {
  210.         return maxPreparedStatements;
  211.     }

  212.     /**
  213.      * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
  214.      * idle object evictor (if any).
  215.      *
  216.      * @see #setMinEvictableIdleDuration
  217.      * @see #setDurationBetweenEvictionRuns
  218.      * @return the minimum amount of time a statement may sit idle in the pool.
  219.      * @since 2.9.0
  220.      */
  221.     public Duration getMinEvictableIdleDuration() {
  222.         return minEvictableIdleDuration;
  223.     }

  224.     /**
  225.      * Gets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
  226.      * idle object evictor (if any).
  227.      *
  228.      * @see #setMinEvictableIdleTimeMillis
  229.      * @see #setTimeBetweenEvictionRunsMillis
  230.      * @return the minimum amount of time a statement may sit idle in the pool.
  231.      * @deprecated USe {@link #getMinEvictableIdleDuration()}.
  232.      */
  233.     @Deprecated
  234.     public int getMinEvictableIdleTimeMillis() {
  235.         return (int) minEvictableIdleDuration.toMillis();
  236.     }

  237.     /**
  238.      * Gets the number of statements to examine during each run of the idle object evictor thread (if any.)
  239.      *
  240.      * @see #setNumTestsPerEvictionRun
  241.      * @see #setTimeBetweenEvictionRunsMillis
  242.      * @return the number of statements to examine during each run of the idle object evictor thread (if any.)
  243.      */
  244.     public int getNumTestsPerEvictionRun() {
  245.         return numTestsPerEvictionRun;
  246.     }

  247.     /**
  248.      * Implements {@link ObjectFactory} to create an instance of this class
  249.      */
  250.     @Override
  251.     public Object getObjectInstance(final Object refObj, final Name name, final Context context, final Hashtable<?, ?> env) throws ClassNotFoundException {
  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(KEY_DESCRIPTION);
  259.                 if (isNotEmpty(ra)) {
  260.                     setDescription(getStringContent(ra));
  261.                 }

  262.                 ra = ref.get(KEY_DRIVER);
  263.                 if (isNotEmpty(ra)) {
  264.                     setDriver(getStringContent(ra));
  265.                 }
  266.                 ra = ref.get(KEY_URL);
  267.                 if (isNotEmpty(ra)) {
  268.                     setUrl(getStringContent(ra));
  269.                 }
  270.                 ra = ref.get(Constants.KEY_USER);
  271.                 if (isNotEmpty(ra)) {
  272.                     setUser(getStringContent(ra));
  273.                 }
  274.                 ra = ref.get(Constants.KEY_PASSWORD);
  275.                 if (isNotEmpty(ra)) {
  276.                     setPassword(getStringContent(ra));
  277.                 }

  278.                 ra = ref.get(KEY_POOL_PREPARED_STATEMENTS);
  279.                 if (isNotEmpty(ra)) {
  280.                     setPoolPreparedStatements(getBooleanContentString(ra));
  281.                 }
  282.                 ra = ref.get(KEY_MAX_IDLE);
  283.                 if (isNotEmpty(ra)) {
  284.                     setMaxIdle(getIntegerStringContent(ra));
  285.                 }

  286.                 ra = ref.get(KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
  287.                 if (isNotEmpty(ra)) {
  288.                     setTimeBetweenEvictionRunsMillis(getIntegerStringContent(ra));
  289.                 }

  290.                 ra = ref.get(KEY_NUM_TESTS_PER_EVICTION_RUN);
  291.                 if (isNotEmpty(ra)) {
  292.                     setNumTestsPerEvictionRun(getIntegerStringContent(ra));
  293.                 }

  294.                 ra = ref.get(KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS);
  295.                 if (isNotEmpty(ra)) {
  296.                     setMinEvictableIdleTimeMillis(getIntegerStringContent(ra));
  297.                 }

  298.                 ra = ref.get(KEY_MAX_PREPARED_STATEMENTS);
  299.                 if (isNotEmpty(ra)) {
  300.                     setMaxPreparedStatements(getIntegerStringContent(ra));
  301.                 }

  302.                 ra = ref.get(KEY_ACCESS_TO_UNDERLYING_CONNECTION_ALLOWED);
  303.                 if (isNotEmpty(ra)) {
  304.                     setAccessToUnderlyingConnectionAllowed(getBooleanContentString(ra));
  305.                 }

  306.                 cpds = this;
  307.             }
  308.         }
  309.         return cpds;
  310.     }

  311.     @Override
  312.     public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  313.         throw new SQLFeatureNotSupportedException();
  314.     }

  315.     /**
  316.      * Gets the value of password for the default user.
  317.      *
  318.      * @return value of password.
  319.      */
  320.     public String getPassword() {
  321.         return Utils.toString(userPassword);
  322.     }

  323.     /**
  324.      * Gets the value of password for the default user.
  325.      *
  326.      * @return value of password.
  327.      * @since 2.4.0
  328.      */
  329.     public char[] getPasswordCharArray() {
  330.         return Utils.clone(userPassword);
  331.     }

  332.     /**
  333.      * Attempts to establish a database connection using the default user and password.
  334.      */
  335.     @Override
  336.     public PooledConnection getPooledConnection() throws SQLException {
  337.         return getPooledConnection(getUser(), getPassword());
  338.     }

  339.     /**
  340.      * Attempts to establish a database connection.
  341.      *
  342.      * @param pooledUserName name to be used for the connection
  343.      * @param pooledUserPassword password to be used fur the connection
  344.      */
  345.     @Override
  346.     public PooledConnection getPooledConnection(final String pooledUserName, final String pooledUserPassword) throws SQLException {
  347.         getConnectionCalled = true;
  348.         if (connectionProperties != null) {
  349.             update(connectionProperties, Constants.KEY_USER, pooledUserName);
  350.             update(connectionProperties, Constants.KEY_PASSWORD, pooledUserPassword);
  351.         }
  352.         // Workaround for buggy WebLogic 5.1 class loader - ignore ClassCircularityError upon first invocation.
  353.         PooledConnectionImpl pooledConnection = null;
  354.         try {
  355.             pooledConnection = getPooledConnectionImpl(pooledUserName, pooledUserPassword);
  356.         } catch (final ClassCircularityError e) {
  357.             pooledConnection = getPooledConnectionImpl(pooledUserName, pooledUserPassword);
  358.         }
  359.         if (isPoolPreparedStatements()) {
  360.             final GenericKeyedObjectPoolConfig<DelegatingPreparedStatement> config = new GenericKeyedObjectPoolConfig<>();
  361.             config.setMaxTotalPerKey(Integer.MAX_VALUE);
  362.             config.setBlockWhenExhausted(false);
  363.             config.setMaxWait(Duration.ZERO);
  364.             config.setMaxIdlePerKey(getMaxIdle());
  365.             if (getMaxPreparedStatements() <= 0) {
  366.                 // Since there is no limit, create a prepared statement pool with an eviction thread;
  367.                 // evictor settings are the same as the connection pool settings.
  368.                 config.setTimeBetweenEvictionRuns(getDurationBetweenEvictionRuns());
  369.                 config.setNumTestsPerEvictionRun(getNumTestsPerEvictionRun());
  370.                 config.setMinEvictableIdleDuration(getMinEvictableIdleDuration());
  371.             } else {
  372.                 // Since there is a limit, create a prepared statement pool without an eviction thread;
  373.                 // pool has LRU functionality so when the limit is reached, 15% of the pool is cleared.
  374.                 // see org.apache.commons.pool2.impl.GenericKeyedObjectPool.clearOldest method
  375.                 config.setMaxTotal(getMaxPreparedStatements());
  376.                 config.setTimeBetweenEvictionRuns(Duration.ofMillis(-1));
  377.                 config.setNumTestsPerEvictionRun(0);
  378.                 config.setMinEvictableIdleDuration(Duration.ZERO);
  379.             }
  380.             @SuppressWarnings("resource") // PooledConnectionImpl closes
  381.             final KeyedObjectPool<PStmtKey, DelegatingPreparedStatement> stmtPool = new GenericKeyedObjectPool<>(pooledConnection, config);
  382.             pooledConnection.setStatementPool(stmtPool);
  383.         }
  384.         return pooledConnection;
  385.     }

  386.     @SuppressWarnings("resource") // Caller closes
  387.     private PooledConnectionImpl getPooledConnectionImpl(final String pooledUserName, final String pooledUserPassword) throws SQLException {
  388.         PooledConnectionImpl pooledConnection;
  389.         if (connectionProperties != null) {
  390.             pooledConnection = new PooledConnectionImpl(DriverManager.getConnection(getUrl(), connectionProperties));
  391.         } else {
  392.             pooledConnection = new PooledConnectionImpl(DriverManager.getConnection(getUrl(), pooledUserName, pooledUserPassword));
  393.         }
  394.         pooledConnection.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
  395.         return pooledConnection;
  396.     }

  397.     /**
  398.      * Implements {@link Referenceable}.
  399.      */
  400.     @Override
  401.     public Reference getReference() throws NamingException {
  402.         // this class implements its own factory
  403.         final String factory = getClass().getName();

  404.         final Reference ref = new Reference(getClass().getName(), factory, null);

  405.         ref.add(new StringRefAddr(KEY_DESCRIPTION, getDescription()));
  406.         ref.add(new StringRefAddr(KEY_DRIVER, getDriver()));
  407.         ref.add(new StringRefAddr(KEY_LOGIN_TIMEOUT, String.valueOf(getLoginTimeout())));
  408.         ref.add(new StringRefAddr(Constants.KEY_PASSWORD, getPassword()));
  409.         ref.add(new StringRefAddr(Constants.KEY_USER, getUser()));
  410.         ref.add(new StringRefAddr(KEY_URL, getUrl()));

  411.         ref.add(new StringRefAddr(KEY_POOL_PREPARED_STATEMENTS, String.valueOf(isPoolPreparedStatements())));
  412.         ref.add(new StringRefAddr(KEY_MAX_IDLE, String.valueOf(getMaxIdle())));
  413.         ref.add(new StringRefAddr(KEY_NUM_TESTS_PER_EVICTION_RUN, String.valueOf(getNumTestsPerEvictionRun())));
  414.         ref.add(new StringRefAddr(KEY_MAX_PREPARED_STATEMENTS, String.valueOf(getMaxPreparedStatements())));
  415.         //
  416.         // Pair of current and deprecated.
  417.         ref.add(new StringRefAddr(KEY_DURATION_BETWEEN_EVICTION_RUNS, String.valueOf(getDurationBetweenEvictionRuns())));
  418.         ref.add(new StringRefAddr(KEY_TIME_BETWEEN_EVICTION_RUNS_MILLIS, String.valueOf(getTimeBetweenEvictionRunsMillis())));
  419.         //
  420.         // Pair of current and deprecated.
  421.         ref.add(new StringRefAddr(KEY_MIN_EVICTABLE_IDLE_DURATION, String.valueOf(getMinEvictableIdleDuration())));
  422.         ref.add(new StringRefAddr(KEY_MIN_EVICTABLE_IDLE_TIME_MILLIS, String.valueOf(getMinEvictableIdleTimeMillis())));

  423.         return ref;
  424.     }

  425.     private String getStringContent(final RefAddr ra) {
  426.         return ra.getContent().toString();
  427.     }

  428.     /**
  429.      * Gets the number of milliseconds to sleep between runs of the idle object evictor thread. When non-positive, no
  430.      * idle object evictor thread will be run.
  431.      *
  432.      * @return the value of the evictor thread timer
  433.      * @see #setDurationBetweenEvictionRuns(Duration)
  434.      * @deprecated Use {@link #getDurationBetweenEvictionRuns()}.
  435.      */
  436.     @Deprecated
  437.     public long getTimeBetweenEvictionRunsMillis() {
  438.         return durationBetweenEvictionRuns.toMillis();
  439.     }

  440.     /**
  441.      * Gets the value of connection string used to locate the database for this data source.
  442.      *
  443.      * @return value of connection string.
  444.      */
  445.     public String getUrl() {
  446.         return connectionString;
  447.     }

  448.     /**
  449.      * Gets the value of default user (login or user name).
  450.      *
  451.      * @return value of user.
  452.      */
  453.     public String getUser() {
  454.         return userName;
  455.     }

  456.     /**
  457.      * Returns the value of the accessToUnderlyingConnectionAllowed property.
  458.      *
  459.      * @return true if access to the underlying is allowed, false otherwise.
  460.      */
  461.     public synchronized boolean isAccessToUnderlyingConnectionAllowed() {
  462.         return this.accessToUnderlyingConnectionAllowed;
  463.     }

  464.     private boolean isNotEmpty(final RefAddr ra) {
  465.         return ra != null && ra.getContent() != null;
  466.     }

  467.     /**
  468.      * Whether to toggle the pooling of {@code PreparedStatement}s
  469.      *
  470.      * @return value of poolPreparedStatements.
  471.      */
  472.     public boolean isPoolPreparedStatements() {
  473.         return poolPreparedStatements;
  474.     }

  475.     /**
  476.      * Sets the value of the accessToUnderlyingConnectionAllowed property. It controls if the PoolGuard allows access to
  477.      * the underlying connection. (Default: false)
  478.      *
  479.      * @param allow Access to the underlying connection is granted when true.
  480.      */
  481.     public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) {
  482.         this.accessToUnderlyingConnectionAllowed = allow;
  483.     }

  484.     /**
  485.      * Sets the connection properties passed to the JDBC driver.
  486.      * <p>
  487.      * If {@code props} contains "user" and/or "password" properties, the corresponding instance properties are
  488.      * set. If these properties are not present, they are filled in using {@link #getUser()}, {@link #getPassword()}
  489.      * when {@link #getPooledConnection()} is called, or using the actual parameters to the method call when
  490.      * {@link #getPooledConnection(String, String)} is called. Calls to {@link #setUser(String)} or
  491.      * {@link #setPassword(String)} overwrite the values of these properties if {@code connectionProperties} is not
  492.      * null.
  493.      * </p>
  494.      *
  495.      * @param props Connection properties to use when creating new connections.
  496.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  497.      */
  498.     public void setConnectionProperties(final Properties props) {
  499.         assertInitializationAllowed();
  500.         connectionProperties = props;
  501.         if (connectionProperties != null) {
  502.             final String user = connectionProperties.getProperty(Constants.KEY_USER);
  503.             if (user != null) {
  504.                 setUser(user);
  505.             }
  506.             final String password = connectionProperties.getProperty(Constants.KEY_PASSWORD);
  507.             if (password != null) {
  508.                 setPassword(password);
  509.             }
  510.         }
  511.     }

  512.     /**
  513.      * Sets the value of description. This property is here for use by the code which will deploy this datasource. It is
  514.      * not used internally.
  515.      *
  516.      * @param description Value to assign to description.
  517.      */
  518.     public void setDescription(final String description) {
  519.         this.description = description;
  520.     }

  521.     /**
  522.      * Sets the driver class name. Setting the driver class name cause the driver to be registered with the
  523.      * DriverManager.
  524.      *
  525.      * @param driver Value to assign to driver.
  526.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  527.      * @throws ClassNotFoundException if the class cannot be located
  528.      */
  529.     public void setDriver(final String driver) throws ClassNotFoundException {
  530.         assertInitializationAllowed();
  531.         this.driver = driver;
  532.         // make sure driver is registered
  533.         Class.forName(driver);
  534.     }

  535.     /**
  536.      * Sets the duration to sleep between runs of the idle object evictor thread. When non-positive, no
  537.      * idle object evictor thread will be run.
  538.      *
  539.      * @param durationBetweenEvictionRuns The duration to sleep between runs of the idle object evictor
  540.      *        thread. When non-positive, no idle object evictor thread will be run.
  541.      * @see #getDurationBetweenEvictionRuns()
  542.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  543.      * @since 2.9.0
  544.      */
  545.     public void setDurationBetweenEvictionRuns(final Duration durationBetweenEvictionRuns) {
  546.         assertInitializationAllowed();
  547.         this.durationBetweenEvictionRuns = durationBetweenEvictionRuns;
  548.     }

  549.     /**
  550.      * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database. NOT
  551.      * USED.
  552.      */
  553.     @Override
  554.     public void setLoginTimeout(final int seconds) {
  555.         this.loginTimeout = seconds;
  556.     }

  557.     /**
  558.      * Sets the log writer for this data source. NOT USED.
  559.      */
  560.     @Override
  561.     public void setLogWriter(final PrintWriter logWriter) {
  562.         this.logWriter = logWriter;
  563.     }

  564.     /**
  565.      * Gets the maximum number of statements that can remain idle in the pool, without extra ones being released, or
  566.      * negative for no limit.
  567.      *
  568.      * @param maxIdle The maximum number of statements that can remain idle
  569.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  570.      */
  571.     public void setMaxIdle(final int maxIdle) {
  572.         assertInitializationAllowed();
  573.         this.maxIdle = maxIdle;
  574.     }

  575.     /**
  576.      * Sets the maximum number of prepared statements.
  577.      *
  578.      * @param maxPreparedStatements the new maximum number of prepared statements
  579.      */
  580.     public void setMaxPreparedStatements(final int maxPreparedStatements) {
  581.         this.maxPreparedStatements = maxPreparedStatements;
  582.     }

  583.     /**
  584.      * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
  585.      * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
  586.      *
  587.      * @param minEvictableIdleDuration minimum time to set in milliseconds.
  588.      * @see #getMinEvictableIdleDuration()
  589.      * @see #setDurationBetweenEvictionRuns(Duration)
  590.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called.
  591.      * @since 2.9.0
  592.      */
  593.     public void setMinEvictableIdleDuration(final Duration minEvictableIdleDuration) {
  594.         assertInitializationAllowed();
  595.         this.minEvictableIdleDuration = minEvictableIdleDuration;
  596.     }

  597.     /**
  598.      * Sets the minimum amount of time a statement may sit idle in the pool before it is eligible for eviction by the
  599.      * idle object evictor (if any). When non-positive, no objects will be evicted from the pool due to idle time alone.
  600.      *
  601.      * @param minEvictableIdleTimeMillis minimum time to set in milliseconds.
  602.      * @see #getMinEvictableIdleDuration()
  603.      * @see #setDurationBetweenEvictionRuns(Duration)
  604.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  605.      * @deprecated Use {@link #setMinEvictableIdleDuration(Duration)}.
  606.      */
  607.     @Deprecated
  608.     public void setMinEvictableIdleTimeMillis(final int minEvictableIdleTimeMillis) {
  609.         assertInitializationAllowed();
  610.         this.minEvictableIdleDuration = Duration.ofMillis(minEvictableIdleTimeMillis);
  611.     }

  612.     /**
  613.      * Sets the number of statements to examine during each run of the idle object evictor thread (if any).
  614.      * <p>
  615.      * When a negative value is supplied,
  616.      * {@code ceil({@link BasicDataSource#getNumIdle})/abs({@link #getNumTestsPerEvictionRun})} tests will be run.
  617.      * I.e., when the value is <em>-n</em>, roughly one <em>n</em>th of the idle objects will be tested per run.
  618.      * </p>
  619.      *
  620.      * @param numTestsPerEvictionRun number of statements to examine per run
  621.      * @see #getNumTestsPerEvictionRun()
  622.      * @see #setDurationBetweenEvictionRuns(Duration)
  623.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  624.      */
  625.     public void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) {
  626.         assertInitializationAllowed();
  627.         this.numTestsPerEvictionRun = numTestsPerEvictionRun;
  628.     }

  629.     /**
  630.      * Sets the value of password for the default user.
  631.      *
  632.      * @param userPassword Value to assign to password.
  633.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  634.      */
  635.     public void setPassword(final char[] userPassword) {
  636.         assertInitializationAllowed();
  637.         this.userPassword = Utils.clone(userPassword);
  638.         update(connectionProperties, Constants.KEY_PASSWORD, Utils.toString(this.userPassword));
  639.     }

  640.     /**
  641.      * Sets the value of password for the default user.
  642.      *
  643.      * @param userPassword Value to assign to password.
  644.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  645.      */
  646.     public void setPassword(final String userPassword) {
  647.         assertInitializationAllowed();
  648.         this.userPassword = Utils.toCharArray(userPassword);
  649.         update(connectionProperties, Constants.KEY_PASSWORD, userPassword);
  650.     }

  651.     /**
  652.      * Whether to toggle the pooling of {@code PreparedStatement}s
  653.      *
  654.      * @param poolPreparedStatements true to pool statements.
  655.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  656.      */
  657.     public void setPoolPreparedStatements(final boolean poolPreparedStatements) {
  658.         assertInitializationAllowed();
  659.         this.poolPreparedStatements = poolPreparedStatements;
  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 #getDurationBetweenEvictionRuns()
  668.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  669.      * @deprecated Use {@link #setDurationBetweenEvictionRuns(Duration)}.
  670.      */
  671.     @Deprecated
  672.     public void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) {
  673.         assertInitializationAllowed();
  674.         this.durationBetweenEvictionRuns = Duration.ofMillis(timeBetweenEvictionRunsMillis);
  675.     }

  676.     /**
  677.      * Sets the value of URL string used to locate the database for this data source.
  678.      *
  679.      * @param connectionString Value to assign to connection string.
  680.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  681.      */
  682.     public void setUrl(final String connectionString) {
  683.         assertInitializationAllowed();
  684.         this.connectionString = connectionString;
  685.     }

  686.     /**
  687.      * Sets the value of default user (login or user name).
  688.      *
  689.      * @param userName Value to assign to user.
  690.      * @throws IllegalStateException if {@link #getPooledConnection()} has been called
  691.      */
  692.     public void setUser(final String userName) {
  693.         assertInitializationAllowed();
  694.         this.userName = userName;
  695.         update(connectionProperties, Constants.KEY_USER, userName);
  696.     }

  697.     /**
  698.      * Does not print the userName and userPassword field nor the 'user' or 'password' in the connectionProperties.
  699.      *
  700.      * @since 2.6.0
  701.      */
  702.     @Override
  703.     public synchronized String toString() {
  704.         final StringBuilder builder = new StringBuilder(super.toString());
  705.         builder.append("[description=");
  706.         builder.append(description);
  707.         builder.append(", connectionString=");
  708.         // TODO What if the connection string contains a 'user' or 'password' query parameter but that connection string
  709.         // is not in a legal URL format?
  710.         builder.append(connectionString);
  711.         builder.append(", driver=");
  712.         builder.append(driver);
  713.         builder.append(", loginTimeout=");
  714.         builder.append(loginTimeout);
  715.         builder.append(", poolPreparedStatements=");
  716.         builder.append(poolPreparedStatements);
  717.         builder.append(", maxIdle=");
  718.         builder.append(maxIdle);
  719.         builder.append(", timeBetweenEvictionRunsMillis=");
  720.         builder.append(durationBetweenEvictionRuns);
  721.         builder.append(", numTestsPerEvictionRun=");
  722.         builder.append(numTestsPerEvictionRun);
  723.         builder.append(", minEvictableIdleTimeMillis=");
  724.         builder.append(minEvictableIdleDuration);
  725.         builder.append(", maxPreparedStatements=");
  726.         builder.append(maxPreparedStatements);
  727.         builder.append(", getConnectionCalled=");
  728.         builder.append(getConnectionCalled);
  729.         builder.append(", connectionProperties=");
  730.         builder.append(Utils.cloneWithoutCredentials(connectionProperties));
  731.         builder.append(", accessToUnderlyingConnectionAllowed=");
  732.         builder.append(accessToUnderlyingConnectionAllowed);
  733.         builder.append("]");
  734.         return builder.toString();
  735.     }

  736.     private void update(final Properties properties, final String key, final String value) {
  737.         if (properties != null && key != null) {
  738.             if (value == null) {
  739.                 properties.remove(key);
  740.             } else {
  741.                 properties.setProperty(key, value);
  742.             }
  743.         }
  744.     }
  745. }