/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tomcat.dbcp.dbcp2; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.lang.management.ManagementFactory; import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; import java.util.logging.Logger; import javax.management.InstanceAlreadyExistsException; import javax.management.JMException; import javax.management.MBeanRegistration; import javax.management.MBeanRegistrationException; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.NotCompliantMBeanException; import javax.management.ObjectName; import javax.sql.DataSource; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.dbcp.pool2.PooledObject; import org.apache.tomcat.dbcp.pool2.impl.AbandonedConfig; import org.apache.tomcat.dbcp.pool2.impl.BaseObjectPoolConfig; import org.apache.tomcat.dbcp.pool2.impl.GenericKeyedObjectPoolConfig; import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPool; import org.apache.tomcat.dbcp.pool2.impl.GenericObjectPoolConfig; /** * <p>Basic implementation of <code>javax.sql.DataSource</code> that is * configured via JavaBeans properties. This is not the only way to * combine the <em>commons-dbcp2</em> and <em>commons-pool2</em> packages, * but provides a "one stop shopping" solution for basic requirements.</p> * * @author Glenn L. Nielsen * @author Craig R. McClanahan * @author Dirk Verbeeck * @since 2.0 */ public class BasicDataSource implements DataSource, BasicDataSourceMXBean, MBeanRegistration, AutoCloseable { private static final Log log = LogFactory.getLog(BasicDataSource.class); static { // Attempt to prevent deadlocks - see DBCP - 272 DriverManager.getDrivers(); try { // Load classes now to prevent AccessControlExceptions later // A number of classes are loaded when getConnection() is called // but the following classes are not loaded and therefore require // explicit loading. if (Utils.IS_SECURITY_ENABLED) { final ClassLoader loader = BasicDataSource.class.getClassLoader(); final String dbcpPackageName = BasicDataSource.class.getPackage().getName(); loader.loadClass(dbcpPackageName + ".BasicDataSource$PaGetConnection"); loader.loadClass(dbcpPackageName + ".DelegatingCallableStatement"); loader.loadClass(dbcpPackageName + ".DelegatingDatabaseMetaData"); loader.loadClass(dbcpPackageName + ".DelegatingPreparedStatement"); loader.loadClass(dbcpPackageName + ".DelegatingResultSet"); loader.loadClass(dbcpPackageName + ".PoolableCallableStatement"); loader.loadClass(dbcpPackageName + ".PoolablePreparedStatement"); loader.loadClass(dbcpPackageName + ".PoolingConnection$StatementType"); loader.loadClass(dbcpPackageName + ".PStmtKey"); final String poolPackageName = PooledObject.class.getPackage().getName(); loader.loadClass(poolPackageName + ".impl.LinkedBlockingDeque$Node"); loader.loadClass(poolPackageName + ".impl.GenericKeyedObjectPool$ObjectDeque"); } } catch (final ClassNotFoundException cnfe) { throw new IllegalStateException("Unable to pre-load classes", cnfe); } } // ------------------------------------------------------------- Properties /** * The default auto-commit state of connections created by this pool. */ private volatile Boolean defaultAutoCommit = null; /** * Returns the default auto-commit property. * * @return true if default auto-commit is enabled */ @Override public Boolean getDefaultAutoCommit() { return defaultAutoCommit; } /** * <p>Sets default auto-commit state of connections returned by this * datasource.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param defaultAutoCommit default auto-commit value */ public void setDefaultAutoCommit(final Boolean defaultAutoCommit) { this.defaultAutoCommit = defaultAutoCommit; } /** * The default read-only state of connections created by this pool. */ private transient Boolean defaultReadOnly = null; /** * Returns the default readOnly property. * * @return true if connections are readOnly by default */ @Override public Boolean getDefaultReadOnly() { return defaultReadOnly; } /** * <p>Sets defaultReadonly property.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param defaultReadOnly default read-only value */ public void setDefaultReadOnly(final Boolean defaultReadOnly) { this.defaultReadOnly = defaultReadOnly; } /** * The default TransactionIsolation state of connections created by this pool. */ private volatile int defaultTransactionIsolation = PoolableConnectionFactory.UNKNOWN_TRANSACTIONISOLATION; /** * Returns the default transaction isolation state of returned connections. * * @return the default value for transaction isolation state * @see Connection#getTransactionIsolation */ @Override public int getDefaultTransactionIsolation() { return this.defaultTransactionIsolation; } /** * <p>Sets the default transaction isolation state for returned * connections.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param defaultTransactionIsolation the default transaction isolation * state * @see Connection#getTransactionIsolation */ public void setDefaultTransactionIsolation(final int defaultTransactionIsolation) { this.defaultTransactionIsolation = defaultTransactionIsolation; } private Integer defaultQueryTimeout = null; /** * Obtain the default query timeout that will be used for {@link java.sql.Statement Statement}s * created from this connection. <code>null</code> means that the driver * default will be used. * @return the timoeut */ public Integer getDefaultQueryTimeout() { return defaultQueryTimeout; } /** * Set the default query timeout that will be used for {@link java.sql.Statement Statement}s * created from this connection. <code>null</code> means that the driver * default will be used. * @param defaultQueryTimeout The new default timeout */ public void setDefaultQueryTimeout(final Integer defaultQueryTimeout) { this.defaultQueryTimeout = defaultQueryTimeout; } /** * The default "catalog" of connections created by this pool. */ private volatile String defaultCatalog = null; /** * Returns the default catalog. * * @return the default catalog */ @Override public String getDefaultCatalog() { return this.defaultCatalog; } /** * <p>Sets the default catalog.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param defaultCatalog the default catalog */ public void setDefaultCatalog(final String defaultCatalog) { if (defaultCatalog != null && defaultCatalog.trim().length() > 0) { this.defaultCatalog = defaultCatalog; } else { this.defaultCatalog = null; } } /** * The property that controls if the pooled connections cache some state * rather than query the database for current state to improve performance. */ private boolean cacheState = true; /** * Returns the state caching flag. * * @return the state caching flag */ @Override public boolean getCacheState() { return cacheState; } /** * Sets the state caching flag. * * @param cacheState The new value for the state caching flag */ public void setCacheState(final boolean cacheState) { this.cacheState = cacheState; } /** * The instance of the JDBC Driver to use. */ private Driver driver = null; /** * Returns the JDBC Driver that has been configured for use by this pool. * <p> * Note: This getter only returns the last value set by a call to * {@link #setDriver(Driver)}. It does not return any driver instance that * may have been created from the value set via * {@link #setDriverClassName(String)}. * * @return the JDBC Driver that has been configured for use by this pool */ public synchronized Driver getDriver() { return driver; } /** * Sets the JDBC Driver instance to use for this pool. * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param driver The driver to use */ public synchronized void setDriver(final Driver driver) { this.driver = driver; } /** * The fully qualified Java class name of the JDBC driver to be used. */ private String driverClassName = null; /** * Returns the JDBC driver class name. * <p> * Note: This getter only returns the last value set by a call to * {@link #setDriverClassName(String)}. It does not return the class name of * any driver that may have been set via {@link #setDriver(Driver)}. * * @return the JDBC driver class name */ @Override public synchronized String getDriverClassName() { return this.driverClassName; } /** * <p>Sets the JDBC driver class name.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param driverClassName the class name of the JDBC driver */ public synchronized void setDriverClassName(final String driverClassName) { if (driverClassName != null && driverClassName.trim().length() > 0) { this.driverClassName = driverClassName; } else { this.driverClassName = null; } } /** * The class loader instance to use to load the JDBC driver. If not * specified, {@link Class#forName(String)} is used to load the JDBC driver. * If specified, {@link Class#forName(String, boolean, ClassLoader)} is * used. */ private ClassLoader driverClassLoader = null; /** * Returns the class loader specified for loading the JDBC driver. Returns * <code>null</code> if no class loader has been explicitly specified. * <p> * Note: This getter only returns the last value set by a call to * {@link #setDriverClassLoader(ClassLoader)}. It does not return the class * loader of any driver that may have been set via * {@link #setDriver(Driver)}. * @return the classloader */ public synchronized ClassLoader getDriverClassLoader() { return this.driverClassLoader; } /** * <p>Sets the class loader to be used to load the JDBC driver.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param driverClassLoader the class loader with which to load the JDBC * driver */ public synchronized void setDriverClassLoader( final ClassLoader driverClassLoader) { this.driverClassLoader = driverClassLoader; } /** * True means that borrowObject returns the most recently used ("last in") * connection in the pool (if there are idle connections available). False * means that the pool behaves as a FIFO queue - connections are taken from * the idle instance pool in the order that they are returned to the pool. */ private boolean lifo = BaseObjectPoolConfig.DEFAULT_LIFO; /** * Returns the LIFO property. * * @return <code>true</code> if connection pool behaves as a LIFO queue. */ @Override public synchronized boolean getLifo() { return this.lifo; } /** * Sets the LIFO property. True means the pool behaves as a LIFO queue; * false means FIFO. * * @param lifo the new value for the LIFO property */ public synchronized void setLifo(final boolean lifo) { this.lifo = lifo; if (connectionPool != null) { connectionPool.setLifo(lifo); } } /** * The maximum number of active connections that can be allocated from * this pool at the same time, or negative for no limit. */ private int maxTotal = GenericObjectPoolConfig.DEFAULT_MAX_TOTAL; /** * <p>Returns the maximum number of active connections that can be * allocated at the same time. * </p> * <p>A negative number means that there is no limit.</p> * * @return the maximum number of active connections */ @Override public synchronized int getMaxTotal() { return this.maxTotal; } /** * Sets the maximum total number of idle and borrows connections that can be * active at the same time. Use a negative value for no limit. * * @param maxTotal the new value for maxTotal * @see #getMaxTotal() */ public synchronized void setMaxTotal(final int maxTotal) { this.maxTotal = maxTotal; if (connectionPool != null) { connectionPool.setMaxTotal(maxTotal); } } /** * The maximum number of connections that can remain idle in the * pool, without extra ones being destroyed, or negative for no limit. * If maxIdle is set too low on heavily loaded systems it is possible you * will see connections being closed and almost immediately new connections * being opened. This is a result of the active threads momentarily closing * connections faster than they are opening them, causing the number of idle * connections to rise above maxIdle. The best value for maxIdle for heavily * loaded system will vary but the default is a good starting point. */ private int maxIdle = GenericObjectPoolConfig.DEFAULT_MAX_IDLE; /** * <p>Returns the maximum number of connections that can remain idle in the * pool. Excess idle connections are destroyed on return to the pool. * </p> * <p>A negative value indicates that there is no limit</p> * * @return the maximum number of idle connections */ @Override public synchronized int getMaxIdle() { return this.maxIdle; } /** * Sets the maximum number of connections that can remain idle in the * pool. Excess idle connections are destroyed on return to the pool. * * @see #getMaxIdle() * @param maxIdle the new value for maxIdle */ public synchronized void setMaxIdle(final int maxIdle) { this.maxIdle = maxIdle; if (connectionPool != null) { connectionPool.setMaxIdle(maxIdle); } } /** * The minimum number of active connections that can remain idle in the * pool, without extra ones being created when the evictor runs, or 0 to create none. * The pool attempts to ensure that minIdle connections are available when the idle object evictor * runs. The value of this property has no effect unless {@link #timeBetweenEvictionRunsMillis} * has a positive value. */ private int minIdle = GenericObjectPoolConfig.DEFAULT_MIN_IDLE; /** * Returns the minimum number of idle connections in the pool. The pool attempts * to ensure that minIdle connections are available when the idle object evictor * runs. The value of this property has no effect unless {@link #timeBetweenEvictionRunsMillis} * has a positive value. * * @return the minimum number of idle connections * @see GenericObjectPool#getMinIdle() */ @Override public synchronized int getMinIdle() { return this.minIdle; } /** * Sets the minimum number of idle connections in the pool. The pool attempts * to ensure that minIdle connections are available when the idle object evictor * runs. The value of this property has no effect unless {@link #timeBetweenEvictionRunsMillis} * has a positive value. * * @param minIdle the new value for minIdle * @see GenericObjectPool#setMinIdle(int) */ public synchronized void setMinIdle(final int minIdle) { this.minIdle = minIdle; if (connectionPool != null) { connectionPool.setMinIdle(minIdle); } } /** * The initial number of connections that are created when the pool * is started. */ private int initialSize = 0; /** * Returns the initial size of the connection pool. * * @return the number of connections created when the pool is initialized */ @Override public synchronized int getInitialSize() { return this.initialSize; } /** * <p>Sets the initial size of the connection pool.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param initialSize the number of connections created when the pool * is initialized */ public synchronized void setInitialSize(final int initialSize) { this.initialSize = initialSize; } /** * The maximum number of milliseconds that the pool will wait (when there * are no available connections) for a connection to be returned before * throwing an exception, or <= 0 to wait indefinitely. */ private long maxWaitMillis = BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS; /** * Returns the maximum number of milliseconds that the pool will wait * for a connection to be returned before throwing an exception. A value * less than or equal to zero means the pool is set to wait indefinitely. * * @return the maxWaitMillis property value */ @Override public synchronized long getMaxWaitMillis() { return this.maxWaitMillis; } /** * Sets the MaxWaitMillis property. Use -1 to make the pool wait * indefinitely. * * @param maxWaitMillis the new value for MaxWaitMillis * @see #getMaxWaitMillis() */ public synchronized void setMaxWaitMillis(final long maxWaitMillis) { this.maxWaitMillis = maxWaitMillis; if (connectionPool != null) { connectionPool.setMaxWaitMillis(maxWaitMillis); } } /** * Prepared statement pooling for this pool. When this property is set to <code>true</code> * both PreparedStatements and CallableStatements are pooled. */ private boolean poolPreparedStatements = false; /** * Returns true if we are pooling statements. * * @return <code>true</code> if prepared and callable statements are pooled */ @Override public synchronized boolean isPoolPreparedStatements() { return this.poolPreparedStatements; } /** * <p>Sets whether to pool statements or not.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param poolingStatements pooling on or off */ public synchronized void setPoolPreparedStatements(final boolean poolingStatements) { this.poolPreparedStatements = poolingStatements; } /** * <p>The maximum number of open statements that can be allocated from * the statement pool at the same time, or negative for no limit. Since * a connection usually only uses one or two statements at a time, this is * mostly used to help detect resource leaks.</p> * * <p>Note: As of version 1.3, CallableStatements (those produced by {@link Connection#prepareCall}) * are pooled along with PreparedStatements (produced by {@link Connection#prepareStatement}) * and <code>maxOpenPreparedStatements</code> limits the total number of prepared or callable statements * that may be in use at a given time.</p> */ private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL; /** * Gets the value of the <code>maxOpenPreparedStatements</code> property. * * @return the maximum number of open statements */ @Override public synchronized int getMaxOpenPreparedStatements() { return this.maxOpenPreparedStatements; } /** * <p>Sets the value of the <code>maxOpenPreparedStatements</code> * property.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param maxOpenStatements the new maximum number of prepared statements */ public synchronized void setMaxOpenPreparedStatements(final int maxOpenStatements) { this.maxOpenPreparedStatements = maxOpenStatements; } /** * The indication of whether objects will be validated as soon as they have * been created by the pool. If the object fails to validate, the borrow * operation that triggered the creation will fail. */ private boolean testOnCreate = false; /** * Returns the {@link #testOnCreate} property. * * @return <code>true</code> if objects are validated immediately * after they are created by the pool * * @see #testOnCreate */ @Override public synchronized boolean getTestOnCreate() { return this.testOnCreate; } /** * Sets the {@link #testOnCreate} property. This property determines * whether or not the pool will validate objects immediately after they are * created by the pool * * @param testOnCreate new value for testOnCreate property */ public synchronized void setTestOnCreate(final boolean testOnCreate) { this.testOnCreate = testOnCreate; if (connectionPool != null) { connectionPool.setTestOnCreate(testOnCreate); } } /** * The indication of whether objects will be validated before being * borrowed from the pool. If the object fails to validate, it will be * dropped from the pool, and we will attempt to borrow another. */ private boolean testOnBorrow = true; /** * Returns the {@link #testOnBorrow} property. * * @return <code>true</code> if objects are validated before being * borrowed from the pool * * @see #testOnBorrow */ @Override public synchronized boolean getTestOnBorrow() { return this.testOnBorrow; } /** * Sets the {@link #testOnBorrow} property. This property determines * whether or not the pool will validate objects before they are borrowed * from the pool. * * @param testOnBorrow new value for testOnBorrow property */ public synchronized void setTestOnBorrow(final boolean testOnBorrow) { this.testOnBorrow = testOnBorrow; if (connectionPool != null) { connectionPool.setTestOnBorrow(testOnBorrow); } } /** * The indication of whether objects will be validated before being * returned to the pool. */ private boolean testOnReturn = false; /** * Returns the value of the {@link #testOnReturn} property. * * @return <code>true</code> if objects are validated before being * returned to the pool * @see #testOnReturn */ public synchronized boolean getTestOnReturn() { return this.testOnReturn; } /** * Sets the <code>testOnReturn</code> property. This property determines * whether or not the pool will validate objects before they are returned * to the pool. * * @param testOnReturn new value for testOnReturn property */ public synchronized void setTestOnReturn(final boolean testOnReturn) { this.testOnReturn = testOnReturn; if (connectionPool != null) { connectionPool.setTestOnReturn(testOnReturn); } } /** * The number of milliseconds to sleep between runs of the idle object * evictor thread. When non-positive, no idle object evictor thread will * be run. */ private long timeBetweenEvictionRunsMillis = BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS; /** * Returns the value of the {@link #timeBetweenEvictionRunsMillis} * property. * * @return the time (in milliseconds) between evictor runs * @see #timeBetweenEvictionRunsMillis */ @Override public synchronized long getTimeBetweenEvictionRunsMillis() { return this.timeBetweenEvictionRunsMillis; } /** * Sets the {@link #timeBetweenEvictionRunsMillis} property. * * @param timeBetweenEvictionRunsMillis the new time between evictor runs * @see #timeBetweenEvictionRunsMillis */ public synchronized void setTimeBetweenEvictionRunsMillis(final long timeBetweenEvictionRunsMillis) { this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; if (connectionPool != null) { connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); } } /** * The number of objects to examine during each run of the idle object * evictor thread (if any). */ private int numTestsPerEvictionRun = BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; /** * Returns the value of the {@link #numTestsPerEvictionRun} property. * * @return the number of objects to examine during idle object evictor * runs * @see #numTestsPerEvictionRun */ @Override public synchronized int getNumTestsPerEvictionRun() { return this.numTestsPerEvictionRun; } /** * Sets the value of the {@link #numTestsPerEvictionRun} property. * * @param numTestsPerEvictionRun the new {@link #numTestsPerEvictionRun} * value * @see #numTestsPerEvictionRun */ public synchronized void setNumTestsPerEvictionRun(final int numTestsPerEvictionRun) { this.numTestsPerEvictionRun = numTestsPerEvictionRun; if (connectionPool != null) { connectionPool.setNumTestsPerEvictionRun(numTestsPerEvictionRun); } } /** * The minimum amount of time an object may sit idle in the pool before it * is eligible for eviction by the idle object evictor (if any). */ private long minEvictableIdleTimeMillis = BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS; /** * Returns the {@link #minEvictableIdleTimeMillis} property. * * @return the value of the {@link #minEvictableIdleTimeMillis} property * @see #minEvictableIdleTimeMillis */ @Override public synchronized long getMinEvictableIdleTimeMillis() { return this.minEvictableIdleTimeMillis; } /** * Sets the {@link #minEvictableIdleTimeMillis} property. * * @param minEvictableIdleTimeMillis the minimum amount of time an object * may sit idle in the pool * @see #minEvictableIdleTimeMillis */ public synchronized void setMinEvictableIdleTimeMillis(final long minEvictableIdleTimeMillis) { this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; if (connectionPool != null) { connectionPool.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); } } /** * The minimum amount of time a connection may sit idle in the pool before * it is eligible for eviction by the idle object evictor, with the extra * condition that at least "minIdle" connections remain in the pool. * Note that {@code minEvictableIdleTimeMillis} takes precedence over this * parameter. See {@link #getSoftMinEvictableIdleTimeMillis()}. */ private long softMinEvictableIdleTimeMillis = BaseObjectPoolConfig.DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS; /** * Sets the minimum amount of time a connection may sit idle in the pool * before it is eligible for eviction by the idle object evictor, with the * extra condition that at least "minIdle" connections remain in the pool. * * @param softMinEvictableIdleTimeMillis minimum amount of time a * connection may sit idle in the pool before it is eligible for eviction, * assuming there are minIdle idle connections in the pool. * @see #getSoftMinEvictableIdleTimeMillis */ public synchronized void setSoftMinEvictableIdleTimeMillis(final long softMinEvictableIdleTimeMillis) { this.softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis; if (connectionPool != null) { connectionPool.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis); } } /** * <p>Returns the minimum amount of time a connection may sit idle in the * pool before it is eligible for eviction by the idle object evictor, with * the extra condition that at least "minIdle" connections remain in the * pool.</p> * * <p>When {@link #getMinEvictableIdleTimeMillis() minEvictableIdleTimeMillis} * is set to a positive value, minEvictableIdleTimeMillis is examined * first by the idle connection evictor - i.e. when idle connections are * visited by the evictor, idle time is first compared against * {@code minEvictableIdleTimeMillis} (without considering the number of idle * connections in the pool) and then against * {@code softMinEvictableIdleTimeMillis}, including the {@code minIdle}, * constraint.</p> * * @return minimum amount of time a connection may sit idle in the pool before * it is eligible for eviction, assuming there are minIdle idle connections * in the pool */ @Override public synchronized long getSoftMinEvictableIdleTimeMillis() { return softMinEvictableIdleTimeMillis; } private String evictionPolicyClassName = BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME; /** * Gets the EvictionPolicy implementation in use with this connection pool. * @return the eviction policy */ public synchronized String getEvictionPolicyClassName() { return evictionPolicyClassName; } /** * Sets the EvictionPolicy implementation to use with this connection pool. * * @param evictionPolicyClassName The fully qualified class name of the * EvictionPolicy implementation */ public synchronized void setEvictionPolicyClassName( final String evictionPolicyClassName) { if (connectionPool != null) { connectionPool.setEvictionPolicyClassName(evictionPolicyClassName); } this.evictionPolicyClassName = evictionPolicyClassName; } /** * The indication of whether objects will be validated by the idle object * evictor (if any). If an object fails to validate, it will be dropped * from the pool. */ private boolean testWhileIdle = false; /** * Returns the value of the {@link #testWhileIdle} property. * * @return <code>true</code> if objects examined by * the idle object evictor are validated * @see #testWhileIdle */ @Override public synchronized boolean getTestWhileIdle() { return this.testWhileIdle; } /** * Sets the <code>testWhileIdle</code> property. This property determines * whether or not the idle object evictor will validate connections. * * @param testWhileIdle new value for testWhileIdle property */ public synchronized void setTestWhileIdle(final boolean testWhileIdle) { this.testWhileIdle = testWhileIdle; if (connectionPool != null) { connectionPool.setTestWhileIdle(testWhileIdle); } } /** * [Read Only] The current number of active connections that have been * allocated from this data source. * * @return the current number of active connections */ @Override public int getNumActive() { // Copy reference to avoid NPE if close happens after null check final GenericObjectPool<PoolableConnection> pool = connectionPool; if (pool != null) { return pool.getNumActive(); } return 0; } /** * [Read Only] The current number of idle connections that are waiting * to be allocated from this data source. * * @return the current number of idle connections */ @Override public int getNumIdle() { // Copy reference to avoid NPE if close happens after null check final GenericObjectPool<PoolableConnection> pool = connectionPool; if (pool != null) { return pool.getNumIdle(); } return 0; } /** * The connection password to be passed to our JDBC driver to establish * a connection. */ private volatile String password = null; /** * Returns the password passed to the JDBC driver to establish connections. * * @return the connection password */ @Override public String getPassword() { return this.password; } /** * <p>Sets the {@link #password}.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param password new value for the password */ public void setPassword(final String password) { this.password = password; } /** * The connection URL to be passed to our JDBC driver to establish * a connection. */ private String url = null; /** * Returns the JDBC connection {@link #url} property. * * @return the {@link #url} passed to the JDBC driver to establish * connections */ @Override public synchronized String getUrl() { return this.url; } /** * <p>Sets the {@link #url}.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param url the new value for the JDBC connection url */ public synchronized void setUrl(final String url) { this.url = url; } /** * The connection username to be passed to our JDBC driver to * establish a connection. */ private String username = null; /** * Returns the JDBC connection {@link #username} property. * * @return the {@link #username} passed to the JDBC driver to establish * connections */ @Override public String getUsername() { return this.username; } /** * <p>Sets the {@link #username}.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param username the new value for the JDBC connection username */ public void setUsername(final String username) { this.username = username; } /** * The SQL query that will be used to validate connections from this pool * before returning them to the caller. If specified, this query * <strong>MUST</strong> be an SQL SELECT statement that returns at least * one row. If not specified, {@link Connection#isValid(int)} will be used * to validate connections. */ private volatile String validationQuery = null; /** * Returns the validation query used to validate connections before * returning them. * * @return the SQL validation query * @see #validationQuery */ @Override public String getValidationQuery() { return this.validationQuery; } /** * <p>Sets the {@link #validationQuery}.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param validationQuery the new value for the validation query */ public void setValidationQuery(final String validationQuery) { if (validationQuery != null && validationQuery.trim().length() > 0) { this.validationQuery = validationQuery; } else { this.validationQuery = null; } } /** * Timeout in seconds before connection validation queries fail. */ private volatile int validationQueryTimeout = -1; /** * Returns the validation query timeout. * * @return the timeout in seconds before connection validation queries fail. */ @Override public int getValidationQueryTimeout() { return validationQueryTimeout; } /** * Sets the validation query timeout, the amount of time, in seconds, that * connection validation will wait for a response from the database when * executing a validation query. Use a value less than or equal to 0 for * no timeout. * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param timeout new validation query timeout value in seconds */ public void setValidationQueryTimeout(final int timeout) { this.validationQueryTimeout = timeout; } /** * These SQL statements run once after a Connection is created. * <p> * This property can be used for example to run ALTER SESSION SET * NLS_SORT=XCYECH in an Oracle Database only once after connection * creation. * </p> */ private volatile List<String> connectionInitSqls; /** * Returns the list of SQL statements executed when a physical connection * is first created. Returns an empty list if there are no initialization * statements configured. * * @return initialization SQL statements */ public List<String> getConnectionInitSqls() { final List<String> result = connectionInitSqls; if (result == null) { return Collections.emptyList(); } return result; } /** * Provides the same data as {@link #getConnectionInitSqls()} but in an * array so it is accessible via JMX. */ @Override public String[] getConnectionInitSqlsAsArray() { final Collection<String> result = getConnectionInitSqls(); return result.toArray(new String[result.size()]); } /** * Sets the list of SQL statements to be executed when a physical * connection is first created. * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param connectionInitSqls Collection of SQL statements to execute * on connection creation */ public void setConnectionInitSqls(final Collection<String> connectionInitSqls) { if (connectionInitSqls != null && connectionInitSqls.size() > 0) { ArrayList<String> newVal = null; for (final String s : connectionInitSqls) { if (s != null && s.trim().length() > 0) { if (newVal == null) { newVal = new ArrayList<>(); } newVal.add(s); } } this.connectionInitSqls = newVal; } else { this.connectionInitSqls = null; } } /** * Controls access to the underlying connection. */ private boolean accessToUnderlyingConnectionAllowed = false; /** * Returns the value of the accessToUnderlyingConnectionAllowed property. * * @return true if access to the underlying connection is allowed, false * otherwise. */ @Override public synchronized boolean isAccessToUnderlyingConnectionAllowed() { return this.accessToUnderlyingConnectionAllowed; } /** * <p>Sets the value of the accessToUnderlyingConnectionAllowed property. * It controls if the PoolGuard allows access to the underlying connection. * (Default: false)</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * * @param allow Access to the underlying connection is granted when true. */ public synchronized void setAccessToUnderlyingConnectionAllowed(final boolean allow) { this.accessToUnderlyingConnectionAllowed = allow; } private long maxConnLifetimeMillis = -1; /** * Returns the maximum permitted lifetime of a connection in milliseconds. A * value of zero or less indicates an infinite lifetime. */ @Override public long getMaxConnLifetimeMillis() { return maxConnLifetimeMillis; } private boolean logExpiredConnections = true; /** * When {@link #getMaxConnLifetimeMillis()} is set to limit connection lifetime, * this property determines whether or not log messages are generated when the * pool closes connections due to maximum lifetime exceeded. * * @since 2.1 */ @Override public boolean getLogExpiredConnections() { return logExpiredConnections; } /** * <p>Sets the maximum permitted lifetime of a connection in * milliseconds. A value of zero or less indicates an infinite lifetime.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> * @param maxConnLifetimeMillis The maximum connection lifetime */ public void setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) { this.maxConnLifetimeMillis = maxConnLifetimeMillis; } /** * When {@link #getMaxConnLifetimeMillis()} is set to limit connection lifetime, * this property determines whether or not log messages are generated when the * pool closes connections due to maximum lifetime exceeded. Set this property * to false to suppress log messages when connections expire. * @param logExpiredConnections <code>true</code> to log expired connections */ public void setLogExpiredConnections(final boolean logExpiredConnections) { this.logExpiredConnections = logExpiredConnections; } private String jmxName = null; /** * @return the JMX name that has been requested for this DataSource. If the * requested name is not valid, an alternative may be chosen. */ public String getJmxName() { return jmxName; } /** * Sets the JMX name that has been requested for this DataSource. If the * requested name is not valid, an alternative may be chosen. This * DataSource will attempt to register itself using this name. If another * component registers this DataSource with JMX and this name is valid this * name will be used in preference to any specified by the other component. * @param jmxName The JMX name */ public void setJmxName(final String jmxName) { this.jmxName = jmxName; } private boolean enableAutoCommitOnReturn = true; /** * Returns the value of the flag that controls whether or not connections * being returned to the pool will be checked and configured with * {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} * if the auto commit setting is {@code false} when the connection * is returned. It is <code>true</code> by default. * @return <code>true</code> to commit automatically */ public boolean getEnableAutoCommitOnReturn() { return enableAutoCommitOnReturn; } /** * Sets the value of the flag that controls whether or not connections * being returned to the pool will be checked and configured with * {@link Connection#setAutoCommit(boolean) Connection.setAutoCommit(true)} * if the auto commit setting is {@code false} when the connection * is returned. It is <code>true</code> by default. * @param enableAutoCommitOnReturn The new value */ public void setEnableAutoCommitOnReturn(final boolean enableAutoCommitOnReturn) { this.enableAutoCommitOnReturn = enableAutoCommitOnReturn; } private boolean rollbackOnReturn = true; /** * Gets the current value of the flag that controls if a connection will be * rolled back when it is returned to the pool if auto commit is not enabled * and the connection is not read only. * @return <code>true</code> to rollback non committed connections */ public boolean getRollbackOnReturn() { return rollbackOnReturn; } /** * Sets the flag that controls if a connection will be rolled back when it * is returned to the pool if auto commit is not enabled and the connection * is not read only. * @param rollbackOnReturn The new value */ public void setRollbackOnReturn(final boolean rollbackOnReturn) { this.rollbackOnReturn = rollbackOnReturn; } private volatile Set<String> disconnectionSqlCodes; /** * Returns the set of SQL_STATE codes considered to signal fatal conditions. * @return fatal disconnection state codes * @see #setDisconnectionSqlCodes(Collection) * @since 2.1 */ public Set<String> getDisconnectionSqlCodes() { final Set<String> result = disconnectionSqlCodes; if (result == null) { return Collections.emptySet(); } return result; } /** * Provides the same data as {@link #getDisconnectionSqlCodes} but in an * array so it is accessible via JMX. * @return fatal disconnection state codes * @since 2.1 */ @Override public String[] getDisconnectionSqlCodesAsArray() { final Collection<String> result = getDisconnectionSqlCodes(); return result.toArray(new String[result.size()]); } /** * Sets the SQL_STATE codes considered to signal fatal conditions. * <p> * Overrides the defaults in {@link Utils#DISCONNECTION_SQL_CODES} * (plus anything starting with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}). * If this property is non-null and {@link #getFastFailValidation()} is * {@code true}, whenever connections created by this datasource generate exceptions * with SQL_STATE codes in this list, they will be marked as "fatally disconnected" * and subsequent validations will fail fast (no attempt at isValid or validation * query).</p> * <p> * If {@link #getFastFailValidation()} is {@code false} setting this property has no * effect.</p> * <p> * Note: this method currently has no effect once the pool has been * initialized. The pool is initialized the first time one of the * following methods is invoked: {@code getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter}.</p> * * @param disconnectionSqlCodes SQL_STATE codes considered to signal fatal conditions * @since 2.1 */ public void setDisconnectionSqlCodes(final Collection<String> disconnectionSqlCodes) { if (disconnectionSqlCodes != null && disconnectionSqlCodes.size() > 0) { HashSet<String> newVal = null; for (final String s : disconnectionSqlCodes) { if (s != null && s.trim().length() > 0) { if (newVal == null) { newVal = new HashSet<>(); } newVal.add(s); } } this.disconnectionSqlCodes = newVal; } else { this.disconnectionSqlCodes = null; } } private boolean fastFailValidation; /** * True means that validation will fail immediately for connections that * have previously thrown SQLExceptions with SQL_STATE indicating fatal * disconnection errors. * * @return true if connections created by this datasource will fast fail validation. * @see #setDisconnectionSqlCodes(Collection) * @since 2.1 */ @Override public boolean getFastFailValidation() { return fastFailValidation; } /** * @see #getFastFailValidation() * @param fastFailValidation true means connections created by this factory will * fast fail validation * @since 2.1 */ public void setFastFailValidation(final boolean fastFailValidation) { this.fastFailValidation = fastFailValidation; } // ----------------------------------------------------- Instance Variables /** * The object pool that internally manages our connections. */ private volatile GenericObjectPool<PoolableConnection> connectionPool = null; protected GenericObjectPool<PoolableConnection> getConnectionPool() { return connectionPool; } /** * The connection properties that will be sent to our JDBC driver when * establishing new connections. <strong>NOTE</strong> - The "user" and * "password" properties will be passed explicitly, so they do not need * to be included here. */ private Properties connectionProperties = new Properties(); // For unit testing Properties getConnectionProperties() { return connectionProperties; } /** * The data source we will use to manage connections. This object should * be acquired <strong>ONLY</strong> by calls to the * <code>createDataSource()</code> method. */ private volatile DataSource dataSource = null; /** * The PrintWriter to which log messages should be directed. */ private volatile PrintWriter logWriter = new PrintWriter(new OutputStreamWriter( System.out, StandardCharsets.UTF_8)); // ----------------------------------------------------- DataSource Methods /** * Create (if necessary) and return a connection to the database. * * @throws SQLException if a database access error occurs * @return a database connection */ @Override public Connection getConnection() throws SQLException { if (Utils.IS_SECURITY_ENABLED) { final PrivilegedExceptionAction<Connection> action = new PaGetConnection(); try { return AccessController.doPrivileged(action); } catch (final PrivilegedActionException e) { final Throwable cause = e.getCause(); if (cause instanceof SQLException) { throw (SQLException) cause; } throw new SQLException(e); } } return createDataSource().getConnection(); } /** * <strong>BasicDataSource does NOT support this method. </strong> * * @param user Database user on whose behalf the Connection * is being made * @param pass The database user's password * * @throws UnsupportedOperationException This is not supported * @throws SQLException if a database access error occurs * @return nothing - always throws UnsupportedOperationException */ @Override public Connection getConnection(final String user, final String pass) throws SQLException { // This method isn't supported by the PoolingDataSource returned by // the createDataSource throw new UnsupportedOperationException("Not supported by BasicDataSource"); } /** * <strong>BasicDataSource does NOT support this method. </strong> * * <p>Returns the login timeout (in seconds) for connecting to the database. * </p> * <p>Calls {@link #createDataSource()}, so has the side effect * of initializing the connection pool.</p> * * @throws SQLException if a database access error occurs * @throws UnsupportedOperationException If the DataSource implementation * does not support the login timeout feature. * @return login timeout in seconds */ @Override public int getLoginTimeout() throws SQLException { // This method isn't supported by the PoolingDataSource returned by // the createDataSource throw new UnsupportedOperationException("Not supported by BasicDataSource"); } /** * <p>Returns the log writer being used by this data source.</p> * <p> * Calls {@link #createDataSource()}, so has the side effect * of initializing the connection pool.</p> * * @throws SQLException if a database access error occurs * @return log writer in use */ @Override public PrintWriter getLogWriter() throws SQLException { return createDataSource().getLogWriter(); } /** * <strong>BasicDataSource does NOT support this method. </strong> * * <p>Set the login timeout (in seconds) for connecting to the * database.</p> * <p> * Calls {@link #createDataSource()}, so has the side effect * of initializing the connection pool.</p> * * @param loginTimeout The new login timeout, or zero for no timeout * @throws UnsupportedOperationException If the DataSource implementation * does not support the login timeout feature. * @throws SQLException if a database access error occurs */ @Override public void setLoginTimeout(final int loginTimeout) throws SQLException { // This method isn't supported by the PoolingDataSource returned by // the createDataSource throw new UnsupportedOperationException("Not supported by BasicDataSource"); } /** * <p>Sets the log writer being used by this data source.</p> * <p> * Calls {@link #createDataSource()}, so has the side effect * of initializing the connection pool.</p> * * @param logWriter The new log writer * @throws SQLException if a database access error occurs */ @Override public void setLogWriter(final PrintWriter logWriter) throws SQLException { createDataSource().setLogWriter(logWriter); this.logWriter = logWriter; } private AbandonedConfig abandonedConfig; /** * <p>Flag to remove abandoned connections if they exceed the * removeAbandonedTimeout when borrowObject is invoked.</p> * * <p>The default value is false.</p> * * <p>If set to true a connection is considered abandoned and eligible * for removal if it has not been used for more than * {@link #getRemoveAbandonedTimeout() removeAbandonedTimeout} seconds.</p> * * <p>Abandoned connections are identified and removed when * {@link #getConnection()} is invoked and all of the following conditions hold: * </p> * <ul><li>{@link #getRemoveAbandonedOnBorrow()} </li> * <li>{@link #getNumActive()} > {@link #getMaxTotal()} - 3 </li> * <li>{@link #getNumIdle()} < 2 </li></ul> * * @see #getRemoveAbandonedTimeout() */ @Override public boolean getRemoveAbandonedOnBorrow() { if (abandonedConfig != null) { return abandonedConfig.getRemoveAbandonedOnBorrow(); } return false; } /** * @param removeAbandonedOnMaintenance true means abandoned connections may * be removed on pool maintenance. * @see #getRemoveAbandonedOnMaintenance() */ public void setRemoveAbandonedOnMaintenance( final boolean removeAbandonedOnMaintenance) { if (abandonedConfig == null) { abandonedConfig = new AbandonedConfig(); } abandonedConfig.setRemoveAbandonedOnMaintenance( removeAbandonedOnMaintenance); } /** * <p>Flag to remove abandoned connections if they exceed the * removeAbandonedTimeout during pool maintenance.</p> * * <p>The default value is false.</p> * * <p>If set to true a connection is considered abandoned and eligible * for removal if it has not been used for more than * {@link #getRemoveAbandonedTimeout() removeAbandonedTimeout} seconds.</p> * * @see #getRemoveAbandonedTimeout() */ @Override public boolean getRemoveAbandonedOnMaintenance() { if (abandonedConfig != null) { return abandonedConfig.getRemoveAbandonedOnMaintenance(); } return false; } /** * @param removeAbandonedOnBorrow true means abandoned connections may be * removed when connections are borrowed from the pool. * @see #getRemoveAbandonedOnBorrow() */ public void setRemoveAbandonedOnBorrow(final boolean removeAbandonedOnBorrow) { if (abandonedConfig == null) { abandonedConfig = new AbandonedConfig(); } abandonedConfig.setRemoveAbandonedOnBorrow(removeAbandonedOnBorrow); } /** * <p>Timeout in seconds before an abandoned connection can be removed.</p> * * <p>Creating a Statement, PreparedStatement or CallableStatement or using * one of these to execute a query (using one of the execute methods) * resets the lastUsed property of the parent connection.</p> * * <p>Abandoned connection cleanup happens when:</p> * <ul> * <li>{@link #getRemoveAbandonedOnBorrow()} or * {@link #getRemoveAbandonedOnMaintenance()} = true</li> * <li>{@link #getNumIdle() numIdle} < 2</li> * <li>{@link #getNumActive() numActive} > {@link #getMaxTotal() maxTotal} - 3</li> * </ul> * * <p>The default value is 300 seconds.</p> */ @Override public int getRemoveAbandonedTimeout() { if (abandonedConfig != null) { return abandonedConfig.getRemoveAbandonedTimeout(); } return 300; } /** * <p>Sets the timeout in seconds before an abandoned connection can be * removed.</p> * * <p>Setting this property has no effect if * {@link #getRemoveAbandonedOnBorrow()} and * {@link #getRemoveAbandonedOnMaintenance()} are false.</p> * * @param removeAbandonedTimeout new abandoned timeout in seconds * @see #getRemoveAbandonedTimeout() * @see #getRemoveAbandonedOnBorrow() * @see #getRemoveAbandonedOnMaintenance() */ public void setRemoveAbandonedTimeout(final int removeAbandonedTimeout) { if (abandonedConfig == null) { abandonedConfig = new AbandonedConfig(); } abandonedConfig.setRemoveAbandonedTimeout(removeAbandonedTimeout); } /** * <p>Flag to log stack traces for application code which abandoned * a Statement or Connection. * </p> * <p>Defaults to false. * </p> * <p>Logging of abandoned Statements and Connections adds overhead * for every Connection open or new Statement because a stack * trace has to be generated. </p> */ @Override public boolean getLogAbandoned() { if (abandonedConfig != null) { return abandonedConfig.getLogAbandoned(); } return false; } /** * @param logAbandoned new logAbandoned property value */ public void setLogAbandoned(final boolean logAbandoned) { if (abandonedConfig == null) { abandonedConfig = new AbandonedConfig(); } abandonedConfig.setLogAbandoned(logAbandoned); } /** * Gets the log writer to be used by this configuration to log * information on abandoned objects. * @return the log writer */ public PrintWriter getAbandonedLogWriter() { if (abandonedConfig != null) { return abandonedConfig.getLogWriter(); } return null; } /** * Sets the log writer to be used by this configuration to log * information on abandoned objects. * * @param logWriter The new log writer */ public void setAbandonedLogWriter(final PrintWriter logWriter) { if (abandonedConfig == null) { abandonedConfig = new AbandonedConfig(); } abandonedConfig.setLogWriter(logWriter); } /** * If the connection pool implements {@link org.apache.tomcat.dbcp.pool2.UsageTracking UsageTracking}, should the * connection pool record a stack trace every time a method is called on a * pooled connection and retain the most recent stack trace to aid debugging * of abandoned connections? * * @return <code>true</code> if usage tracking is enabled */ @Override public boolean getAbandonedUsageTracking() { if (abandonedConfig != null) { return abandonedConfig.getUseUsageTracking(); } return false; } /** * If the connection pool implements {@link org.apache.tomcat.dbcp.pool2.UsageTracking UsageTracking}, configure * whether the connection pool should record a stack trace every time a * method is called on a pooled connection and retain the most recent stack * trace to aid debugging of abandoned connections. * * @param usageTracking A value of <code>true</code> will enable * the recording of a stack trace on every use * of a pooled connection */ public void setAbandonedUsageTracking(final boolean usageTracking) { if (abandonedConfig == null) { abandonedConfig = new AbandonedConfig(); } abandonedConfig.setUseUsageTracking(usageTracking); } // --------------------------------------------------------- Public Methods /** * Add a custom connection property to the set that will be passed to our * JDBC driver. This <strong>MUST</strong> be called before the first * connection is retrieved (along with all the other configuration * property setters). Calls to this method after the connection pool * has been initialized have no effect. * * @param name Name of the custom connection property * @param value Value of the custom connection property */ public void addConnectionProperty(final String name, final String value) { connectionProperties.put(name, value); } /** * Remove a custom connection property. * * @param name Name of the custom connection property to remove * @see #addConnectionProperty(String, String) */ public void removeConnectionProperty(final String name) { connectionProperties.remove(name); } /** * Sets the connection properties passed to driver.connect(...). * * Format of the string must be [propertyName=property;]* * * NOTE - The "user" and "password" properties will be added * explicitly, so they do not need to be included here. * * @param connectionProperties the connection properties used to * create new connections */ public void setConnectionProperties(final String connectionProperties) { if (connectionProperties == null) { throw new NullPointerException("connectionProperties is null"); } final String[] entries = connectionProperties.split(";"); final Properties properties = new Properties(); for (final String entry : entries) { if (entry.length() > 0) { final int index = entry.indexOf('='); if (index > 0) { final String name = entry.substring(0, index); final String value = entry.substring(index + 1); properties.setProperty(name, value); } else { // no value is empty string which is how java.util.Properties works properties.setProperty(entry, ""); } } } this.connectionProperties = properties; } private boolean closed; /** * <p>Closes and releases all idle connections that are currently stored in the connection pool * associated with this data source.</p> * * <p>Connections that are checked out to clients when this method is invoked are not affected. * When client applications subsequently invoke {@link Connection#close()} to return * these connections to the pool, the underlying JDBC connections are closed.</p> * * <p>Attempts to acquire connections using {@link #getConnection()} after this method has been * invoked result in SQLExceptions.</p> * * <p>This method is idempotent - i.e., closing an already closed BasicDataSource has no effect * and does not generate exceptions.</p> * * @throws SQLException if an error occurs closing idle connections */ @Override public synchronized void close() throws SQLException { if (registeredJmxName != null) { final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); try { mbs.unregisterMBean(registeredJmxName); } catch (final JMException e) { log.warn("Failed to unregister the JMX name: " + registeredJmxName, e); } finally { registeredJmxName = null; } } closed = true; final GenericObjectPool<?> oldpool = connectionPool; connectionPool = null; dataSource = null; try { if (oldpool != null) { oldpool.close(); } } catch(final RuntimeException e) { throw e; } catch(final Exception e) { throw new SQLException(Utils.getMessage("pool.close.fail"), e); } } /** * If true, this data source is closed and no more connections can be retrieved from this datasource. * @return true, if the data source is closed; false otherwise */ @Override public synchronized boolean isClosed() { return closed; } @Override public boolean isWrapperFor(final Class<?> iface) throws SQLException { return false; } @Override public <T> T unwrap(final Class<T> iface) throws SQLException { throw new SQLException("BasicDataSource is not a wrapper."); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { throw new SQLFeatureNotSupportedException(); } /** * Manually invalidates a connection, effectively requesting the pool to try * to close it, remove it from the pool and reclaim pool capacity. * @param connection The connection to close * @throws IllegalStateException * if invalidating the connection failed. * @since 2.1 */ public void invalidateConnection(final Connection connection) throws IllegalStateException { if (connection == null) { return; } if (connectionPool == null) { throw new IllegalStateException("Cannot invalidate connection: ConnectionPool is null."); } final PoolableConnection poolableConnection; try { poolableConnection = connection.unwrap(PoolableConnection.class); if (poolableConnection == null) { throw new IllegalStateException( "Cannot invalidate connection: Connection is not a poolable connection."); } } catch (final SQLException e) { throw new IllegalStateException("Cannot invalidate connection: Unwrapping poolable connection failed.", e); } try { connectionPool.invalidateObject(poolableConnection); } catch (final Exception e) { throw new IllegalStateException("Invalidating connection threw unexpected exception", e); } } // ------------------------------------------------------ Protected Methods /** * <p>Create (if necessary) and return the internal data source we are * using to manage our connections.</p> * @return the data source * @throws SQLException if the object pool cannot be created. */ protected DataSource createDataSource() throws SQLException { if (closed) { throw new SQLException("Data source is closed"); } // Return the pool if we have already created it // This is double-checked locking. This is safe since dataSource is // volatile and the code is targeted at Java 5 onwards. if (dataSource != null) { return dataSource; } synchronized (this) { if (dataSource != null) { return dataSource; } jmxRegister(); // create factory which returns raw physical connections final ConnectionFactory driverConnectionFactory = createConnectionFactory(); // Set up the poolable connection factory boolean success = false; PoolableConnectionFactory poolableConnectionFactory; try { poolableConnectionFactory = createPoolableConnectionFactory( driverConnectionFactory); poolableConnectionFactory.setPoolStatements( poolPreparedStatements); poolableConnectionFactory.setMaxOpenPrepatedStatements( maxOpenPreparedStatements); success = true; } catch (final SQLException se) { throw se; } catch (final RuntimeException rte) { throw rte; } catch (final Exception ex) { throw new SQLException("Error creating connection factory", ex); } if (success) { // create a pool for our connections createConnectionPool(poolableConnectionFactory); } // Create the pooling data source to manage connections DataSource newDataSource; success = false; try { newDataSource = createDataSourceInstance(); newDataSource.setLogWriter(logWriter); success = true; } catch (final SQLException se) { throw se; } catch (final RuntimeException rte) { throw rte; } catch (final Exception ex) { throw new SQLException("Error creating datasource", ex); } finally { if (!success) { closeConnectionPool(); } } // If initialSize > 0, preload the pool try { for (int i = 0 ; i < initialSize ; i++) { connectionPool.addObject(); } } catch (final Exception e) { closeConnectionPool(); throw new SQLException("Error preloading the connection pool", e); } // If timeBetweenEvictionRunsMillis > 0, start the pool's evictor task startPoolMaintenance(); dataSource = newDataSource; return dataSource; } } /** * Creates a JDBC connection factory for this datasource. The JDBC driver * is loaded using the following algorithm: * <ol> * <li>If a Driver instance has been specified via * {@link #setDriver(Driver)} use it</li> * <li>If no Driver instance was specified and {@link #driverClassName} is * specified that class is loaded using the {@link ClassLoader} of this * class or, if {@link #driverClassLoader} is set, {@link #driverClassName} * is loaded with the specified {@link ClassLoader}.</li> * <li>If {@link #driverClassName} is specified and the previous attempt * fails, the class is loaded using the context class loader of the current * thread.</li> * <li>If a driver still isn't loaded one is loaded via the * {@link DriverManager} using the specified {@link #url}. * </ol> * This method exists so subclasses can replace the implementation class. * @return the connection factory * @throws SQLException Error creating connection factory */ protected ConnectionFactory createConnectionFactory() throws SQLException { // Load the JDBC driver class Driver driverToUse = this.driver; if (driverToUse == null) { Class<?> driverFromCCL = null; if (driverClassName != null) { try { try { if (driverClassLoader == null) { driverFromCCL = Class.forName(driverClassName); } else { driverFromCCL = Class.forName( driverClassName, true, driverClassLoader); } } catch (final ClassNotFoundException cnfe) { driverFromCCL = Thread.currentThread( ).getContextClassLoader().loadClass( driverClassName); } } catch (final Exception t) { final String message = "Cannot load JDBC driver class '" + driverClassName + "'"; logWriter.println(message); t.printStackTrace(logWriter); throw new SQLException(message, t); } } try { if (driverFromCCL == null) { driverToUse = DriverManager.getDriver(url); } else { // Usage of DriverManager is not possible, as it does not // respect the ContextClassLoader // N.B. This cast may cause ClassCastException which is handled below driverToUse = (Driver) driverFromCCL.newInstance(); if (!driverToUse.acceptsURL(url)) { throw new SQLException("No suitable driver", "08001"); } } } catch (final Exception t) { final String message = "Cannot create JDBC driver of class '" + (driverClassName != null ? driverClassName : "") + "' for connect URL '" + url + "'"; logWriter.println(message); t.printStackTrace(logWriter); throw new SQLException(message, t); } } // Set up the driver connection factory we will use final String user = username; if (user != null) { connectionProperties.put("user", user); } else { log("DBCP DataSource configured without a 'username'"); } final String pwd = password; if (pwd != null) { connectionProperties.put("password", pwd); } else { log("DBCP DataSource configured without a 'password'"); } final ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driverToUse, url, connectionProperties); return driverConnectionFactory; } /** * Creates a connection pool for this datasource. This method only exists * so subclasses can replace the implementation class. * * This implementation configures all pool properties other than * timeBetweenEvictionRunsMillis. Setting that property is deferred to * {@link #startPoolMaintenance()}, since setting timeBetweenEvictionRunsMillis * to a positive value causes {@link GenericObjectPool}'s eviction timer * to be started. * @param factory The connection factory */ protected void createConnectionPool(final PoolableConnectionFactory factory) { // Create an object pool to contain our active connections final GenericObjectPoolConfig config = new GenericObjectPoolConfig(); updateJmxName(config); config.setJmxEnabled(registeredJmxName != null); // Disable JMX on the underlying pool if the DS is not registered. GenericObjectPool<PoolableConnection> gop; if (abandonedConfig != null && (abandonedConfig.getRemoveAbandonedOnBorrow() || abandonedConfig.getRemoveAbandonedOnMaintenance())) { gop = new GenericObjectPool<>(factory, config, abandonedConfig); } else { gop = new GenericObjectPool<>(factory, config); } gop.setMaxTotal(maxTotal); gop.setMaxIdle(maxIdle); gop.setMinIdle(minIdle); gop.setMaxWaitMillis(maxWaitMillis); gop.setTestOnCreate(testOnCreate); gop.setTestOnBorrow(testOnBorrow); gop.setTestOnReturn(testOnReturn); gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun); gop.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); gop.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis); gop.setTestWhileIdle(testWhileIdle); gop.setLifo(lifo); gop.setSwallowedExceptionListener(new SwallowedExceptionLogger(log, logExpiredConnections)); gop.setEvictionPolicyClassName(evictionPolicyClassName); factory.setPool(gop); connectionPool = gop; } /** * Closes the connection pool, silently swallowing any exception that occurs. */ private void closeConnectionPool() { final GenericObjectPool<?> oldpool = connectionPool; connectionPool = null; try { if (oldpool != null) { oldpool.close(); } } catch(final Exception e) { /* Ignore */ } } /** * Starts the connection pool maintenance task, if configured. */ protected void startPoolMaintenance() { if (connectionPool != null && timeBetweenEvictionRunsMillis > 0) { connectionPool.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); } } /** * Creates the actual data source instance. This method only exists so * that subclasses can replace the implementation class. * @return the data source * @throws SQLException if unable to create a datasource instance */ protected DataSource createDataSourceInstance() throws SQLException { final PoolingDataSource<PoolableConnection> pds = new PoolingDataSource<>(connectionPool); pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); return pds; } /** * Creates the PoolableConnectionFactory and attaches it to the connection pool. This method only exists * so subclasses can replace the default implementation. * * @param driverConnectionFactory JDBC connection factory * @return the connection factory * @throws SQLException if an error occurs creating the PoolableConnectionFactory */ protected PoolableConnectionFactory createPoolableConnectionFactory( final ConnectionFactory driverConnectionFactory) throws SQLException { PoolableConnectionFactory connectionFactory = null; try { connectionFactory = new PoolableConnectionFactory(driverConnectionFactory, registeredJmxName); connectionFactory.setValidationQuery(validationQuery); connectionFactory.setValidationQueryTimeout(validationQueryTimeout); connectionFactory.setConnectionInitSql(connectionInitSqls); connectionFactory.setDefaultReadOnly(defaultReadOnly); connectionFactory.setDefaultAutoCommit(defaultAutoCommit); connectionFactory.setDefaultTransactionIsolation(defaultTransactionIsolation); connectionFactory.setDefaultCatalog(defaultCatalog); connectionFactory.setCacheState(cacheState); connectionFactory.setPoolStatements(poolPreparedStatements); connectionFactory.setMaxOpenPrepatedStatements(maxOpenPreparedStatements); connectionFactory.setMaxConnLifetimeMillis(maxConnLifetimeMillis); connectionFactory.setRollbackOnReturn(getRollbackOnReturn()); connectionFactory.setEnableAutoCommitOnReturn(getEnableAutoCommitOnReturn()); connectionFactory.setDefaultQueryTimeout(getDefaultQueryTimeout()); connectionFactory.setFastFailValidation(fastFailValidation); connectionFactory.setDisconnectionSqlCodes(disconnectionSqlCodes); validateConnectionFactory(connectionFactory); } catch (final RuntimeException e) { throw e; } catch (final Exception e) { throw new SQLException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e); } return connectionFactory; } protected static void validateConnectionFactory( final PoolableConnectionFactory connectionFactory) throws Exception { PoolableConnection conn = null; PooledObject<PoolableConnection> p = null; try { p = connectionFactory.makeObject(); conn = p.getObject(); connectionFactory.activateObject(p); connectionFactory.validateConnection(conn); connectionFactory.passivateObject(p); } finally { if (p != null) { connectionFactory.destroyObject(p); } } } protected void log(final String message) { if (logWriter != null) { logWriter.println(message); } } /** * Actual name under which this component has been registered. */ private ObjectName registeredJmxName = null; private void jmxRegister() { // Return immediately if this DataSource has already been registered if (registeredJmxName != null) { return; } // Return immediately if no JMX name has been specified final String requestedName = getJmxName(); if (requestedName == null) { return; } ObjectName oname; try { oname = new ObjectName(requestedName); } catch (final MalformedObjectNameException e) { log.warn("The requested JMX name [" + requestedName + "] was not valid and will be ignored."); return; } final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); try { mbs.registerMBean(this, oname); } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { log.warn("Failed to complete JMX registration", e); } } @Override public ObjectName preRegister(final MBeanServer server, final ObjectName name) { final String requestedName = getJmxName(); if (requestedName != null) { try { registeredJmxName = new ObjectName(requestedName); } catch (final MalformedObjectNameException e) { log.warn("The requested JMX name [" + requestedName + "] was not valid and will be ignored."); } } if (registeredJmxName == null) { registeredJmxName = name; } return registeredJmxName; } @Override public void postRegister(final Boolean registrationDone) { // NO-OP } @Override public void preDeregister() throws Exception { // NO-OP } @Override public void postDeregister() { // NO-OP } private void updateJmxName(final GenericObjectPoolConfig config) { if (registeredJmxName == null) { return; } final StringBuilder base = new StringBuilder(registeredJmxName.toString()); base.append(Constants.JMX_CONNECTION_POOL_BASE_EXT); config.setJmxNameBase(base.toString()); config.setJmxNamePrefix(Constants.JMX_CONNECTION_POOL_PREFIX); } protected ObjectName getRegisteredJmxName() { return registeredJmxName; } /** * @since 2.0 */ private class PaGetConnection implements PrivilegedExceptionAction<Connection> { @Override public Connection run() throws SQLException { return createDataSource().getConnection(); } } }