Author: markt Date: Wed Jul 24 21:58:57 2013 New Revision: 1506741 URL: http://svn.apache.org/r1506741 Log: DBCP-156 Implement a configuration option - disabled by default - that sets a maximum permitted lifetime for a connection after which the connection will automatically fail the next validation, activation or passivation.
Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSource.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/LocalStrings.properties commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSource.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSource.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSource.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSource.java Wed Jul 24 21:58:57 2013 @@ -1103,6 +1103,31 @@ public class BasicDataSource implements this.restartNeeded = true; } + + private long maxConnLifetimeMillis = -1; + + /** + * Returns the maximum permitted lifetime of a connection in milliseconds. A + * value of zero or less indicates an infinite lifetime. + */ + public long getMaxConnLifetimeMillis() { + return maxConnLifetimeMillis; + } + + /** + * <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> + */ + public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { + this.maxConnLifetimeMillis = maxConnLifetimeMillis; + } + + // ----------------------------------------------------- Instance Variables // TODO: review & make isRestartNeeded() public, restartNeeded protected @@ -1833,6 +1858,7 @@ public class BasicDataSource implements connectionFactory.setPoolStatements(poolPreparedStatements); connectionFactory.setMaxOpenPrepatedStatements( maxOpenPreparedStatements); + connectionFactory.setMaxConnLifetimeMillis(maxConnLifetimeMillis); validateConnectionFactory(connectionFactory); } catch (RuntimeException e) { throw e; Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java Wed Jul 24 21:58:57 2013 @@ -5,9 +5,9 @@ * 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. @@ -84,6 +84,7 @@ public class BasicDataSourceFactory impl private final static String PROP_POOLPREPAREDSTATEMENTS = "poolPreparedStatements"; private final static String PROP_MAXOPENPREPAREDSTATEMENTS = "maxOpenPreparedStatements"; private final static String PROP_CONNECTIONPROPERTIES = "connectionProperties"; + private final static String PROP_MAXCONNLIFETIMEMILLIS = "maxConnLifetimeMillis"; private final static String[] ALL_PROPERTIES = { PROP_DEFAULTAUTOCOMMIT, @@ -118,7 +119,8 @@ public class BasicDataSourceFactory impl PROP_LOGABANDONED, PROP_POOLPREPAREDSTATEMENTS, PROP_MAXOPENPREPAREDSTATEMENTS, - PROP_CONNECTIONPROPERTIES + PROP_CONNECTIONPROPERTIES, + PROP_MAXCONNLIFETIMEMILLIS }; // -------------------------------------------------- ObjectFactory Methods @@ -169,7 +171,7 @@ public class BasicDataSourceFactory impl /** * Creates and configures a {@link BasicDataSource} instance based on the * given properties. - * + * * @param properties the datasource configuration properties * @throws Exception if an error occurs creating the data source */ @@ -237,7 +239,7 @@ public class BasicDataSourceFactory impl if (value != null) { dataSource.setLifo(Boolean.valueOf(value).booleanValue()); } - + value = properties.getProperty(PROP_MAXACTIVE); if (value != null) { dataSource.setMaxTotal(Integer.parseInt(value)); @@ -287,7 +289,7 @@ public class BasicDataSourceFactory impl if (value != null) { dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(value)); } - + value = properties.getProperty(PROP_SOFTMINEVICTABLEIDLETIMEMILLIS); if (value != null) { dataSource.setSoftMinEvictableIdleTimeMillis(Long.parseLong(value)); @@ -322,7 +324,7 @@ public class BasicDataSourceFactory impl if (value != null) { dataSource.setValidationQueryTimeout(Integer.parseInt(value)); } - + value = properties.getProperty(PROP_ACCESSTOUNDERLYINGCONNECTIONALLOWED); if (value != null) { dataSource.setAccessToUnderlyingConnectionAllowed(Boolean.valueOf(value).booleanValue()); @@ -339,7 +341,7 @@ public class BasicDataSourceFactory impl } value = properties.getProperty(PROP_REMOVEABANDONEDTIMEOUT); - if (value != null) { + if (value != null) { dataSource.setRemoveAbandonedTimeout(Integer.parseInt(value)); } @@ -381,6 +383,11 @@ public class BasicDataSourceFactory impl } } + value = properties.getProperty(PROP_MAXCONNLIFETIMEMILLIS); + if (value != null) { + dataSource.setMaxConnLifetimeMillis(Long.parseLong(value)); + } + // DBCP-215 // Trick to make sure that initialSize connections are created if (dataSource.getInitialSize() > 0) { Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/LocalStrings.properties URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/LocalStrings.properties?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/LocalStrings.properties (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/LocalStrings.properties Wed Jul 24 21:58:57 2013 @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +connectionFactory.lifetimeExceeded=The lifetime of the connection [{0}] milliseconds exceeds the maximum permitted value of [{1}] milliseconds + poolableConnectionFactory.validateObject.fail=Failed to validate a poolable connection \ No newline at end of file Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java Wed Jul 24 21:58:57 2013 @@ -167,6 +167,16 @@ public class PoolableConnectionFactory this.maxOpenPreparedStatements = maxOpenPreparedStatements; } + /** + * Sets the maximum lifetime in milliseconds of a connection after which the + * connection will always fail activation, passivation and validation. A + * value of zero or less indicates an infinite lifetime. The default value + * is -1. + */ + public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { + this.maxConnLifetimeMillis = maxConnLifetimeMillis; + } + @Override public PooledObject<PoolableConnection> makeObject() throws Exception { Connection conn = _connFactory.createConnection(); @@ -228,9 +238,11 @@ public class PoolableConnectionFactory @Override public boolean validateObject(PooledObject<PoolableConnection> p) { try { + validateLifetime(p); + validateConnection(p.getObject()); return true; - } catch(Exception e) { + } catch (Exception e) { if (log.isDebugEnabled()) { log.debug(Utils.getMessage( "poolableConnectionFactory.validateObject.fail"), e); @@ -278,6 +290,9 @@ public class PoolableConnectionFactory @Override public void passivateObject(PooledObject<PoolableConnection> p) throws Exception { + + validateLifetime(p); + PoolableConnection conn = p.getObject(); if(!conn.getAutoCommit() && !conn.isReadOnly()) { conn.rollback(); @@ -294,6 +309,8 @@ public class PoolableConnectionFactory public void activateObject(PooledObject<PoolableConnection> p) throws Exception { + validateLifetime(p); + PoolableConnection conn = p.getObject(); conn.activate(); @@ -315,6 +332,20 @@ public class PoolableConnectionFactory } } + private void validateLifetime(PooledObject<PoolableConnection> p) + throws Exception { + if (maxConnLifetimeMillis > 0) { + long lifetime = System.currentTimeMillis() - p.getCreateTime(); + if (lifetime > maxConnLifetimeMillis) { + throw new Exception(Utils.getMessage( + "connectionFactory.lifetimeExceeded", + Long.valueOf(lifetime), + Long.valueOf(maxConnLifetimeMillis))); + } + } + } + + protected volatile ConnectionFactory _connFactory = null; protected volatile String _validationQuery = null; protected volatile int _validationQueryTimeout = -1; @@ -328,6 +359,7 @@ public class PoolableConnectionFactory protected boolean poolStatements = false; protected int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY; + private long maxConnLifetimeMillis = -1; /** * Internal constant to indicate the level is not set. Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/CPDSConnectionFactory.java Wed Jul 24 21:58:57 2013 @@ -30,6 +30,7 @@ import javax.sql.ConnectionEventListener import javax.sql.ConnectionPoolDataSource; import javax.sql.PooledConnection; +import org.apache.commons.dbcp2.PoolableConnection; import org.apache.commons.dbcp2.Utils; import org.apache.commons.pool2.ObjectPool; import org.apache.commons.pool2.PoolableObjectFactory; @@ -58,6 +59,8 @@ class CPDSConnectionFactory private ObjectPool<PooledConnectionAndInfo> _pool; private final String _username; private String _password = null; + private long maxConnLifetimeMillis = -1; + /** * Map of PooledConnections for which close events are ignored. @@ -157,6 +160,11 @@ class CPDSConnectionFactory @Override public boolean validateObject(PooledObject<PooledConnectionAndInfo> p) { + try { + validateLifetime(p); + } catch (Exception e) { + return false; + } boolean valid = false; PooledConnection pconn = p.getObject().getPooledConnection(); String query = _validationQuery; @@ -196,11 +204,15 @@ class CPDSConnectionFactory } @Override - public void passivateObject(PooledObject<PooledConnectionAndInfo> p) { + public void passivateObject(PooledObject<PooledConnectionAndInfo> p) + throws Exception { + validateLifetime(p); } @Override - public void activateObject(PooledObject<PooledConnectionAndInfo> p) { + public void activateObject(PooledObject<PooledConnectionAndInfo> p) + throws Exception { + validateLifetime(p); } // *********************************************************************** @@ -302,6 +314,16 @@ class CPDSConnectionFactory } /** + * Sets the maximum lifetime in milliseconds of a connection after which the + * connection will always fail activation, passivation and validation. A + * value of zero or less indicates an infinite lifetime. The default value + * is -1. + */ + public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { + this.maxConnLifetimeMillis = maxConnLifetimeMillis; + } + + /** * Verifies that the username matches the user whose connections are being managed by this * factory and closes the pool if this is the case; otherwise does nothing. */ @@ -319,4 +341,16 @@ class CPDSConnectionFactory } } + private void validateLifetime(PooledObject<PooledConnectionAndInfo> p) + throws Exception { + if (maxConnLifetimeMillis > 0) { + long lifetime = System.currentTimeMillis() - p.getCreateTime(); + if (lifetime > maxConnLifetimeMillis) { + throw new Exception(Utils.getMessage( + "connectionFactory.lifetimeExceeded", + Long.valueOf(lifetime), + Long.valueOf(maxConnLifetimeMillis))); + } + } + } } Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/InstanceKeyDataSource.java Wed Jul 24 21:58:57 2013 @@ -5,9 +5,9 @@ * 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. @@ -39,7 +39,7 @@ import javax.sql.PooledConnection; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; /** - * <p>The base class for <code>SharedPoolDataSource</code> and + * <p>The base class for <code>SharedPoolDataSource</code> and * <code>PerUserPoolDataSource</code>. Many of the configuration properties * are shared and defined here. This class is declared public in order * to allow particular usage with commons-beanutils; do not make direct @@ -50,7 +50,7 @@ import org.apache.commons.pool2.impl.Gen * A J2EE container will normally provide some method of initializing the * <code>DataSource</code> whose attributes are presented * as bean getters/setters and then deploying it via JNDI. It is then - * available to an application as a source of pooled logical connections to + * available to an application as a source of pooled logical connections to * the database. The pool needs a source of physical connections. This * source is in the form of a <code>ConnectionPoolDataSource</code> that * can be specified via the {@link #setDataSourceName(String)} used to @@ -59,25 +59,25 @@ import org.apache.commons.pool2.impl.Gen * * <p> * Although normally used within a JNDI environment, A DataSource - * can be instantiated and initialized as any bean. In this case the + * can be instantiated and initialized as any bean. In this case the * <code>ConnectionPoolDataSource</code> will likely be instantiated in * a similar manner. This class allows the physical source of connections - * to be attached directly to this pool using the + * to be attached directly to this pool using the * {@link #setConnectionPoolDataSource(ConnectionPoolDataSource)} method. * </p> * * <p> - * The dbcp package contains an adapter, + * The dbcp package contains an adapter, * {@link org.apache.commons.dbcp2.cpdsadapter.DriverAdapterCPDS}, * that can be used to allow the use of <code>DataSource</code>'s based on this - * class with jdbc driver implementations that do not supply a + * class with jdbc driver implementations that do not supply a * <code>ConnectionPoolDataSource</code>, but still * provide a {@link java.sql.Driver} implementation. * </p> * * <p> - * The <a href="package-summary.html">package documentation</a> contains an - * example using Apache Tomcat and JNDI and it also contains a non-JNDI example. + * The <a href="package-summary.html">package documentation</a> contains an + * example using Apache Tomcat and JNDI and it also contains a non-JNDI example. * </p> * * @author John D. McNally @@ -86,42 +86,42 @@ import org.apache.commons.pool2.impl.Gen public abstract class InstanceKeyDataSource implements DataSource, Referenceable, Serializable { private static final long serialVersionUID = -4243533936955098795L; - private static final String GET_CONNECTION_CALLED - = "A Connection was already requested from this source, " + private static final String GET_CONNECTION_CALLED + = "A Connection was already requested from this source, " + "further initialization is not allowed."; private static final String BAD_TRANSACTION_ISOLATION = "The requested TransactionIsolation level is invalid."; /** - * Internal constant to indicate the level is not set. + * Internal constant to indicate the level is not set. */ protected static final int UNKNOWN_TRANSACTIONISOLATION = -1; - + /** Guards property setters - once true, setters throw IllegalStateException */ private volatile boolean getConnectionCalled = false; /** Underlying source of PooledConnections */ private ConnectionPoolDataSource dataSource = null; - + /** DataSource Name used to find the ConnectionPoolDataSource */ private String dataSourceName = null; - + // Default connection properties private boolean defaultAutoCommit = false; private int defaultTransactionIsolation = UNKNOWN_TRANSACTIONISOLATION; private boolean defaultReadOnly = false; - + /** Description */ private String description = null; - + /** Environment that may be used to set up a jndi initial context. */ Properties jndiEnvironment = null; - + /** Login TimeOut in seconds */ private int loginTimeout = 0; - + /** Log stream */ private PrintWriter logWriter = null; - + // Pool properties private boolean _testOnBorrow = GenericObjectPoolConfig.DEFAULT_TEST_ON_BORROW; @@ -130,7 +130,7 @@ public abstract class InstanceKeyDataSou private int _timeBetweenEvictionRunsMillis = (int) Math.min(Integer.MAX_VALUE, GenericObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS); - private int _numTestsPerEvictionRun = + private int _numTestsPerEvictionRun = GenericObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN; private int _minEvictableIdleTimeMillis = (int) Math.min(Integer.MAX_VALUE, @@ -139,7 +139,9 @@ public abstract class InstanceKeyDataSou GenericObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE; private String validationQuery = null; private boolean rollbackAfterValidation = false; - + private long maxConnLifetimeMillis = -1; + + /** true iff one of the setters for testOnBorrow, testOnReturn, testWhileIdle has been called. */ private boolean testPositionSet = false; @@ -168,7 +170,7 @@ public abstract class InstanceKeyDataSou * Close the connection pool being maintained by this datasource. */ public abstract void close() throws Exception; - + protected abstract PooledConnectionManager getConnectionManager(UserPassKey upkey); /* JDBC_4_ANT_KEY_BEGIN */ @@ -200,7 +202,7 @@ public abstract class InstanceKeyDataSou public ConnectionPoolDataSource getConnectionPoolDataSource() { return dataSource; } - + /** * Set the backend ConnectionPoolDataSource. This property should not be * set if using jndi to access the datasource. @@ -213,7 +215,7 @@ public abstract class InstanceKeyDataSou throw new IllegalStateException( "Cannot set the DataSource, if JNDI is used."); } - if (dataSource != null) + if (dataSource != null) { throw new IllegalStateException( "The CPDS has already been set. It cannot be altered."); @@ -224,7 +226,7 @@ public abstract class InstanceKeyDataSou /** * Get the name of the ConnectionPoolDataSource which backs this pool. - * This name is used to look up the datasource from a jndi service + * This name is used to look up the datasource from a jndi service * provider. * * @return value of dataSourceName. @@ -232,10 +234,10 @@ public abstract class InstanceKeyDataSou public String getDataSourceName() { return dataSourceName; } - + /** * Set the name of the ConnectionPoolDataSource which backs this pool. - * This name is used to look up the datasource from a jndi service + * This name is used to look up the datasource from a jndi service * provider. * * @param v Value to assign to dataSourceName. @@ -247,18 +249,18 @@ public abstract class InstanceKeyDataSou "Cannot set the JNDI name for the DataSource, if already " + "set using setConnectionPoolDataSource."); } - if (dataSourceName != null) + if (dataSourceName != null) { throw new IllegalStateException( - "The DataSourceName has already been set. " + + "The DataSourceName has already been set. " + "It cannot be altered."); } this.dataSourceName = v; instanceKey = InstanceKeyObjectFactory.registerNewInstance(this); } - /** - * Get the value of defaultAutoCommit, which defines the state of + /** + * Get the value of defaultAutoCommit, which defines the state of * connections handed out from this pool. The value can be changed * on the Connection using Connection.setAutoCommit(boolean). * The default is true. @@ -268,9 +270,9 @@ public abstract class InstanceKeyDataSou public boolean isDefaultAutoCommit() { return defaultAutoCommit; } - + /** - * Set the value of defaultAutoCommit, which defines the state of + * Set the value of defaultAutoCommit, which defines the state of * connections handed out from this pool. The value can be changed * on the Connection using Connection.setAutoCommit(boolean). * The default is true. @@ -283,7 +285,7 @@ public abstract class InstanceKeyDataSou } /** - * Get the value of defaultReadOnly, which defines the state of + * Get the value of defaultReadOnly, which defines the state of * connections handed out from this pool. The value can be changed * on the Connection using Connection.setReadOnly(boolean). * The default is false. @@ -293,9 +295,9 @@ public abstract class InstanceKeyDataSou public boolean isDefaultReadOnly() { return defaultReadOnly; } - + /** - * Set the value of defaultReadOnly, which defines the state of + * Set the value of defaultReadOnly, which defines the state of * connections handed out from this pool. The value can be changed * on the Connection using Connection.setReadOnly(boolean). * The default is false. @@ -312,7 +314,7 @@ public abstract class InstanceKeyDataSou * connections handed out from this pool. The value can be changed * on the Connection using Connection.setTransactionIsolation(int). * If this method returns -1, the default is JDBC driver dependent. - * + * * @return value of defaultTransactionIsolation. */ public int getDefaultTransactionIsolation() { @@ -324,7 +326,7 @@ public abstract class InstanceKeyDataSou * connections handed out from this pool. The value can be changed * on the Connection using Connection.setTransactionIsolation(int). * The default is JDBC driver dependent. - * + * * @param v Value to assign to defaultTransactionIsolation */ public void setDefaultTransactionIsolation(int v) { @@ -341,7 +343,7 @@ public abstract class InstanceKeyDataSou } this.defaultTransactionIsolation = v; } - + /** * Get the description. This property is defined by jdbc as for use with * GUI (or other) tools that might deploy the datasource. It serves no @@ -352,18 +354,18 @@ public abstract class InstanceKeyDataSou public String getDescription() { return description; } - + /** * Set the description. This property is defined by jdbc as for use with * GUI (or other) tools that might deploy the datasource. It serves no * internal purpose. - * + * * @param v Value to assign to description. */ public void setDescription(String v) { this.description = v; } - + /** * Get the value of jndiEnvironment which is used when instantiating * a jndi InitialContext. This InitialContext is used to locate the @@ -378,12 +380,12 @@ public abstract class InstanceKeyDataSou } return value; } - + /** * Sets the value of the given JNDI environment property to be used when * instantiating a JNDI InitialContext. This InitialContext is used to * locate the backend ConnectionPoolDataSource. - * + * * @param key the JNDI environment property to set. * @param value the value assigned to specified JNDI environment property. */ @@ -393,7 +395,7 @@ public abstract class InstanceKeyDataSou } jndiEnvironment.setProperty(key, value); } - + /** * Get the value of loginTimeout. * @return value of loginTimeout. @@ -402,7 +404,7 @@ public abstract class InstanceKeyDataSou public int getLoginTimeout() { return loginTimeout; } - + /** * Set the value of loginTimeout. * @param v Value to assign to loginTimeout. @@ -411,7 +413,7 @@ public abstract class InstanceKeyDataSou public void setLoginTimeout(int v) { this.loginTimeout = v; } - + /** * Get the value of logWriter. * @return value of logWriter. @@ -420,10 +422,10 @@ public abstract class InstanceKeyDataSou public PrintWriter getLogWriter() { if (logWriter == null) { logWriter = new PrintWriter(System.out); - } + } return logWriter; } - + /** * Set the value of logWriter. * @param v Value to assign to logWriter. @@ -432,14 +434,14 @@ public abstract class InstanceKeyDataSou public void setLogWriter(PrintWriter v) { this.logWriter = v; } - + /** * @see #getTestOnBorrow */ public final boolean isTestOnBorrow() { return getTestOnBorrow(); } - + /** * When <tt>true</tt>, objects will be * {*link PoolableObjectFactory#validateObject validated} @@ -478,7 +480,7 @@ public abstract class InstanceKeyDataSou public final boolean isTestOnReturn() { return getTestOnReturn(); } - + /** * When <tt>true</tt>, objects will be * {*link PoolableObjectFactory#validateObject validated} @@ -527,7 +529,7 @@ public abstract class InstanceKeyDataSou * * @see #getTimeBetweenEvictionRunsMillis */ - public void + public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) { assertInitializationAllowed(); _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; @@ -593,7 +595,7 @@ public abstract class InstanceKeyDataSou public final boolean isTestWhileIdle() { return getTestWhileIdle(); } - + /** * When <tt>true</tt>, objects will be * {*link PoolableObjectFactory#validateObject validated} @@ -651,10 +653,10 @@ public abstract class InstanceKeyDataSou } /** - * Whether a rollback will be issued after executing the SQL query + * Whether a rollback will be issued after executing the SQL query * that will be used to validate connections from this pool * before returning them to the caller. - * + * * @return true if a rollback will be issued after executing the * validation query * @since 1.2.2 @@ -664,12 +666,12 @@ public abstract class InstanceKeyDataSou } /** - * Whether a rollback will be issued after executing the SQL query + * Whether a rollback will be issued after executing the SQL query * that will be used to validate connections from this pool * before returning them to the caller. Default behavior is NOT * to issue a rollback. The setting will only have an effect * if a validation query is set - * + * * @param rollbackAfterValidation new property value * @since 1.2.2 */ @@ -678,11 +680,32 @@ public abstract class InstanceKeyDataSou this.rollbackAfterValidation = rollbackAfterValidation; } + /** + * Returns the maximum permitted lifetime of a connection in milliseconds. A + * value of zero or less indicates an infinite lifetime. + */ + public long getMaxConnLifetimeMillis() { + return maxConnLifetimeMillis; + } + + /** + * <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> + */ + public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { + this.maxConnLifetimeMillis = maxConnLifetimeMillis; + } + // ---------------------------------------------------------------------- // Instrumentation Methods // ---------------------------------------------------------------------- - // DataSource implementation + // DataSource implementation /** * Attempt to establish a database connection. @@ -701,14 +724,14 @@ public abstract class InstanceKeyDataSou * did not match the password used to create the pooled connection. If the connection attempt succeeds, this * means that the database password has been changed. In this case, the <code>PooledConnectionAndInfo</code> * instance retrieved with the old password is destroyed and the <code>getPooledConnectionAndInfo</code> is - * repeatedly invoked until a <code>PooledConnectionAndInfo</code> instance with the new password is returned. - * + * repeatedly invoked until a <code>PooledConnectionAndInfo</code> instance with the new password is returned. + * */ @Override public Connection getConnection(String username, String password) - throws SQLException { + throws SQLException { if (instanceKey == null) { - throw new SQLException("Must set the ConnectionPoolDataSource " + throw new SQLException("Must set the ConnectionPoolDataSource " + "through setDataSourceName or setConnectionPoolDataSource" + " before calling getConnection."); } @@ -722,15 +745,15 @@ public abstract class InstanceKeyDataSou } catch (RuntimeException e) { closeDueToException(info); throw e; - } catch (SQLException e) { + } catch (SQLException e) { closeDueToException(info); throw e; } catch (Exception e) { closeDueToException(info); throw new SQLException("Cannot borrow connection from pool", e); } - - if (!(null == password ? null == info.getPassword() + + if (!(null == password ? null == info.getPassword() : password.equals(info.getPassword()))) { // Password on PooledConnectionAndInfo does not match try { // See if password has changed by attempting connection testCPDS(username, password); @@ -752,7 +775,7 @@ public abstract class InstanceKeyDataSou manager.invalidate(info.getPooledConnection()); // Destroy and remove from pool manager.setPassword(upkey.getPassword()); // Reset the password on the factory if using CPDSConnectionFactory info = null; - for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return + for (int i = 0; i < 10; i++) { // Bound the number of retries - only needed if bad instances return try { info = getPooledConnectionAndInfo(username, password); } catch (NoSuchElementException e) { @@ -761,7 +784,7 @@ public abstract class InstanceKeyDataSou } catch (RuntimeException e) { closeDueToException(info); throw e; - } catch (SQLException e) { + } catch (SQLException e) { closeDueToException(info); throw e; } catch (Exception e) { @@ -776,21 +799,21 @@ public abstract class InstanceKeyDataSou } info = null; } - } + } if (info == null) { throw new SQLException("Cannot borrow connection from pool - password change failure."); } } Connection con = info.getPooledConnection().getConnection(); - try { + try { setupDefaults(con, username); con.clearWarnings(); return con; - } catch (SQLException ex) { + } catch (SQLException ex) { try { con.close(); - } catch (Exception exc) { + } catch (Exception exc) { getLogWriter().println( "ignoring exception during close: " + exc); } @@ -798,14 +821,14 @@ public abstract class InstanceKeyDataSou } } - protected abstract PooledConnectionAndInfo + protected abstract PooledConnectionAndInfo getPooledConnectionAndInfo(String username, String password) throws SQLException; - protected abstract void setupDefaults(Connection con, String username) + protected abstract void setupDefaults(Connection con, String username) throws SQLException; - + private void closeDueToException(PooledConnectionAndInfo info) { if (info != null) { try { @@ -815,20 +838,20 @@ public abstract class InstanceKeyDataSou // of handling another exception. But record it because // it potentially leaks connections from the pool. getLogWriter().println("[ERROR] Could not return connection to " - + "pool during exception handling. " + e.getMessage()); + + "pool during exception handling. " + e.getMessage()); } } } - protected ConnectionPoolDataSource + protected ConnectionPoolDataSource testCPDS(String username, String password) throws javax.naming.NamingException, SQLException { // The source of physical db connections ConnectionPoolDataSource cpds = this.dataSource; - if (cpds == null) { + if (cpds == null) { Context ctx = null; if (jndiEnvironment == null) { - ctx = new InitialContext(); + ctx = new InitialContext(); } else { ctx = new InitialContext(jndiEnvironment); } @@ -842,7 +865,7 @@ public abstract class InstanceKeyDataSou + " doesn't implement javax.sql.ConnectionPoolDataSource"); } } - + // try to get a connection with the supplied username/password PooledConnection conn = null; try { @@ -871,7 +894,7 @@ public abstract class InstanceKeyDataSou } // ---------------------------------------------------------------------- - // Referenceable implementation + // Referenceable implementation /** * Retrieves the Reference of this object. @@ -886,11 +909,11 @@ public abstract class InstanceKeyDataSou */ // TODO: Remove the implementation of this method at next major // version release. - + @Override public Reference getReference() throws NamingException { final String className = getClass().getName(); - final String factoryName = className + "Factory"; // XXX: not robust + final String factoryName = className + "Factory"; // XXX: not robust Reference ref = new Reference(className, factoryName, null); ref.add(new StringRefAddr("instanceKey", instanceKey)); return ref; Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/KeyedCPDSConnectionFactory.java Wed Jul 24 21:58:57 2013 @@ -55,6 +55,7 @@ class KeyedCPDSConnectionFactory private final String _validationQuery; private final boolean _rollbackAfterValidation; private KeyedObjectPool<UserPassKey,PooledConnectionAndInfo> _pool; + private long maxConnLifetimeMillis = -1; /** * Map of PooledConnections for which close events are ignored. @@ -165,6 +166,11 @@ class KeyedCPDSConnectionFactory @Override public boolean validateObject(UserPassKey key, PooledObject<PooledConnectionAndInfo> p) { + try { + validateLifetime(p); + } catch (Exception e) { + return false; + } boolean valid = false; PooledConnection pconn = p.getObject().getPooledConnection(); String query = _validationQuery; @@ -204,11 +210,15 @@ class KeyedCPDSConnectionFactory } @Override - public void passivateObject(UserPassKey key, PooledObject<PooledConnectionAndInfo> p) { + public void passivateObject(UserPassKey key, + PooledObject<PooledConnectionAndInfo> p) throws Exception { + validateLifetime(p); } @Override - public void activateObject(UserPassKey key, PooledObject<PooledConnectionAndInfo> p) { + public void activateObject(UserPassKey key, + PooledObject<PooledConnectionAndInfo> p) throws Exception { + validateLifetime(p); } // *********************************************************************** @@ -309,6 +319,16 @@ class KeyedCPDSConnectionFactory } /** + * Sets the maximum lifetime in milliseconds of a connection after which the + * connection will always fail activation, passivation and validation. A + * value of zero or less indicates an infinite lifetime. The default value + * is -1. + */ + public void setMaxConnLifetimeMillis(long maxConnLifetimeMillis) { + this.maxConnLifetimeMillis = maxConnLifetimeMillis; + } + + /** * This implementation does not fully close the KeyedObjectPool, as * this would affect all users. Instead, it clears the pool associated * with the given user. This method is not currently used. @@ -322,4 +342,16 @@ class KeyedCPDSConnectionFactory } } + private void validateLifetime(PooledObject<PooledConnectionAndInfo> p) + throws Exception { + if (maxConnLifetimeMillis > 0) { + long lifetime = System.currentTimeMillis() - p.getCreateTime(); + if (lifetime > maxConnLifetimeMillis) { + throw new Exception(Utils.getMessage( + "connectionFactory.lifetimeExceeded", + Long.valueOf(lifetime), + Long.valueOf(maxConnLifetimeMillis))); + } + } + } } Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/PerUserPoolDataSource.java Wed Jul 24 21:58:57 2013 @@ -519,6 +519,7 @@ public class PerUserPoolDataSource exten CPDSConnectionFactory factory = new CPDSConnectionFactory(cpds, getValidationQuery(), isRollbackAfterValidation(), username, password); + factory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis()); // Create an object pool to contain our PooledConnections GenericObjectPool<PooledConnectionAndInfo> pool = Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/datasources/SharedPoolDataSource.java Wed Jul 24 21:58:57 2013 @@ -5,9 +5,9 @@ * 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. @@ -35,11 +35,11 @@ import org.apache.commons.pool2.impl.Gen /** * <p>A pooling <code>DataSource</code> appropriate for deployment within * J2EE environment. There are many configuration options, most of which are - * defined in the parent class. All users (based on username) share a single + * defined in the parent class. All users (based on username) share a single * maximum number of Connections in this datasource.</p> - * + * * <p>User passwords can be changed without re-initializing the datasource. - * When a <code>getConnection(username, password)</code> request is processed + * When a <code>getConnection(username, password)</code> request is processed * with a password that is different from those used to create connections in the * pool associated with <code>username</code>, an attempt is made to create a * new connection using the supplied password and if this succeeds, idle connections @@ -120,7 +120,7 @@ public class SharedPoolDataSource /** * 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 -1 to wait indefinitely. Will fail + * throwing an exception, or -1 to wait indefinitely. Will fail * immediately if value is 0. * The default is -1. */ @@ -131,7 +131,7 @@ public class SharedPoolDataSource /** * 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 -1 to wait indefinitely. Will fail + * throwing an exception, or -1 to wait indefinitely. Will fail * immediately if value is 0. * The default is -1. */ @@ -161,10 +161,10 @@ public class SharedPoolDataSource // Inherited abstract methods @Override - protected PooledConnectionAndInfo + protected PooledConnectionAndInfo getPooledConnectionAndInfo(String username, String password) throws SQLException { - + synchronized(this) { if (pool == null) { try { @@ -176,9 +176,9 @@ public class SharedPoolDataSource } PooledConnectionAndInfo info = null; - + UserPassKey key = new UserPassKey(username, password); - + try { info = pool.borrowObject(key); } @@ -188,7 +188,7 @@ public class SharedPoolDataSource } return info; } - + @Override protected PooledConnectionManager getConnectionManager(UserPassKey upkey) { return factory; @@ -196,7 +196,7 @@ public class SharedPoolDataSource /** * Returns a <code>SharedPoolDataSource</code> {@link Reference}. - * + * * @since 1.2.2 */ @Override @@ -206,9 +206,9 @@ public class SharedPoolDataSource ref.add(new StringRefAddr("instanceKey", instanceKey)); return ref; } - + private void registerPool( - String username, String password) + String username, String password) throws javax.naming.NamingException, SQLException { ConnectionPoolDataSource cpds = testCPDS(username, password); @@ -216,6 +216,7 @@ public class SharedPoolDataSource // Create an object pool to contain our PooledConnections factory = new KeyedCPDSConnectionFactory(cpds, getValidationQuery(), isRollbackAfterValidation()); + factory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis()); GenericKeyedObjectPoolConfig config = new GenericKeyedObjectPoolConfig(); config.setMaxTotalPerKey(getMaxTotal()); config.setMaxIdlePerKey(getMaxIdle()); @@ -268,7 +269,7 @@ public class SharedPoolDataSource */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - try + try { in.defaultReadObject(); SharedPoolDataSource oldDS = (SharedPoolDataSource) Modified: commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java URL: http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java?rev=1506741&r1=1506740&r2=1506741&view=diff ============================================================================== --- commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java (original) +++ commons/proper/dbcp/trunk/src/java/org/apache/commons/dbcp2/managed/BasicManagedDataSource.java Wed Jul 24 21:58:57 2013 @@ -61,7 +61,7 @@ public class BasicManagedDataSource exte /** * Gets the XADataSource instance used by the XAConnectionFactory. - * + * * @return the XADataSource */ public synchronized XADataSource getXaDataSourceInstance() { @@ -75,7 +75,7 @@ public class BasicManagedDataSource exte * initialized. The pool is initialized the first time one of the * following methods is invoked: <code>getConnection, setLogwriter, * setLoginTimeout, getLoginTimeout, getLogWriter.</code></p> - * + * * @param xaDataSourceInstance XADataSource instance */ public synchronized void setXaDataSourceInstance(XADataSource xaDataSourceInstance) { @@ -90,7 +90,7 @@ public class BasicManagedDataSource exte public TransactionManager getTransactionManager() { return transactionManager; } - + /** * Gets the transaction registry. * @return the transaction registry associating XAResources with managed connections @@ -146,7 +146,7 @@ public class BasicManagedDataSource exte String message = "Cannot load XA data source class '" + xaDataSource + "'"; throw (SQLException)new SQLException(message).initCause(t); } - + try { xaDataSourceInstance = (XADataSource) xaDataSourceClass.newInstance(); } catch (Exception t) { @@ -166,9 +166,9 @@ public class BasicManagedDataSource exte PoolingDataSource pds = new ManagedDataSource(connectionPool, transactionRegistry); pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed()); pds.setLogWriter(logWriter); - dataSource = pds; + dataSource = pds; } - + /** * Creates the PoolableConnectionFactory and attaches it to the connection pool. * @@ -195,6 +195,7 @@ public class BasicManagedDataSource exte connectionFactory.setPoolStatements(poolPreparedStatements); connectionFactory.setMaxOpenPrepatedStatements( maxOpenPreparedStatements); + connectionFactory.setMaxConnLifetimeMillis(getMaxConnLifetimeMillis()); validateConnectionFactory(connectionFactory); } catch (RuntimeException e) { throw e;