Author: psteitz
Date: Wed Jan 28 13:33:18 2015
New Revision: 1655301

URL: http://svn.apache.org/r1655301
Log:
Added fastFailValidation property to PC, BDS.  JIRA: DBCP-427.

Modified:
    commons/proper/dbcp/trunk/src/changes/changes.xml
    
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java
    
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java
    
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java
    
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java
    
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
    commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java
    
commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties
    commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml
    
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java
    
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java
    
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java

Modified: commons/proper/dbcp/trunk/src/changes/changes.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/changes/changes.xml?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- commons/proper/dbcp/trunk/src/changes/changes.xml (original)
+++ commons/proper/dbcp/trunk/src/changes/changes.xml Wed Jan 28 13:33:18 2015
@@ -94,6 +94,11 @@ The <action> type attribute can be add,u
       <action issue="DBCP-423" dev="psteitz" type="update">
         Made Datasources implement AutoCloseable.
       </action>
+      <action issue="DBCP-427" dev="psteitz" type="add" due-to="Vladimir 
Konkov">
+        Added fastFailValidation property to PooloableConnection, configurable 
in
+        BasicDataSource.  When set to true, connections that have previously 
thrown
+        fatal disconnection errors will fail validation immediately (no driver 
calls).
+      </action>  
     </release>
     <release version="2.0.1" date="24 May 2014" description="This is a bug fix 
release.">
       <action dev="markt" type="fix">

Modified: 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSource.java
 Wed Jan 28 13:33:18 2015
@@ -31,8 +31,10 @@ import java.sql.SQLFeatureNotSupportedEx
 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;
@@ -1267,13 +1269,15 @@ public class BasicDataSource implements
     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. 
+     * pool closes connections due to maximum lifetime exceeded.
+     *
+     * @since 2.1
      */
     @Override
     public boolean isLogExpiredConnections() {
@@ -1292,7 +1296,7 @@ public class BasicDataSource implements
     public void setMaxConnLifetimeMillis(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
@@ -1349,7 +1353,6 @@ public class BasicDataSource implements
         this.enableAutoCommitOnReturn = enableAutoCommitOnReturn;
     }
 
-
     private boolean rollbackOnReturn = true;
 
     /**
@@ -1370,6 +1373,97 @@ public class BasicDataSource implements
         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() {
+        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.
+     * @since 2.1
+     */
+    @Override
+    public String[] getDisconnectionSqlCodesAsArray() {
+        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 #isFastFailValidation()} 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 #isFastFailValidation()} 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(Collection<String> 
disconnectionSqlCodes) {
+        if (disconnectionSqlCodes != null && disconnectionSqlCodes.size() > 0) 
{
+            HashSet<String> newVal = null;
+            for (String s : disconnectionSqlCodes) {
+            if (s != null && s.trim().length() > 0) {
+                    if (newVal == null) {
+                        newVal = new HashSet<String>();
+                    }
+                    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 isFastFailValidation() {
+        return fastFailValidation;
+    }
+
+    /**
+     * @see #isFastFailValidation()
+     * @param fastFailValidation true means connections created by this 
factory will
+     * fast fail validation
+     * @since 2.1
+     */
+    public void setFastFailValidation(boolean fastFailValidation) {
+        this.fastFailValidation = fastFailValidation;
+    }
 
     // ----------------------------------------------------- Instance Variables
 

Modified: 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java
 Wed Jan 28 13:33:18 2015
@@ -98,6 +98,12 @@ public class BasicDataSourceFactory impl
     private static final String PROP_ROLLBACK_ON_RETURN = "rollbackOnReturn";
     private static final String PROP_ENABLE_AUTOCOMMIT_ON_RETURN = 
"enableAutoCommitOnReturn";
     private static final String PROP_DEFAULT_QUERYTIMEOUT = 
"defaultQueryTimeout";
+    private static final String PROP_FASTFAIL_VALIDATION = 
"fastFailValidation";
+
+    /**
+     * Value string must be of the form [STATE_CODE;]*
+     */
+    private static final String PROP_DISCONNECTION_SQL_CODES = 
"disconnectionSqlCodes";
 
     private static final String[] ALL_PROPERTIES = {
         PROP_DEFAULTAUTOCOMMIT,
@@ -139,7 +145,9 @@ public class BasicDataSourceFactory impl
         PROP_LOGEXPIREDCONNECTIONS,
         PROP_ROLLBACK_ON_RETURN,
         PROP_ENABLE_AUTOCOMMIT_ON_RETURN,
-        PROP_DEFAULT_QUERYTIMEOUT
+        PROP_DEFAULT_QUERYTIMEOUT,
+        PROP_FASTFAIL_VALIDATION,
+        PROP_DISCONNECTION_SQL_CODES
     };
 
     // -------------------------------------------------- ObjectFactory Methods
@@ -389,15 +397,7 @@ public class BasicDataSourceFactory impl
 
         value = properties.getProperty(PROP_CONNECTIONINITSQLS);
         if (value != null) {
-            StringTokenizer tokenizer = new StringTokenizer(value, ";");
-            // Have to jump through these hoops as StringTokenizer implements
-            // Enumeration<Object> rather than Enumeration<String>
-            Collection<String> tokens =
-                    new ArrayList<>(tokenizer.countTokens());
-            while (tokenizer.hasMoreTokens()) {
-                tokens.add(tokenizer.nextToken());
-            }
-            dataSource.setConnectionInitSqls(tokens);
+            dataSource.setConnectionInitSqls(parseList(value, ';'));
         }
 
         value = properties.getProperty(PROP_CONNECTIONPROPERTIES);
@@ -414,7 +414,7 @@ public class BasicDataSourceFactory impl
         if (value != null) {
             dataSource.setMaxConnLifetimeMillis(Long.parseLong(value));
         }
-        
+
         value = properties.getProperty(PROP_LOGEXPIREDCONNECTIONS);
         if (value != null) {
             
dataSource.setLogExpiredConnections(Boolean.valueOf(value).booleanValue());
@@ -440,6 +440,15 @@ public class BasicDataSourceFactory impl
             dataSource.setDefaultQueryTimeout(Integer.valueOf(value));
         }
 
+        value = properties.getProperty(PROP_FASTFAIL_VALIDATION);
+        if (value != null) {
+            
dataSource.setFastFailValidation(Boolean.valueOf(value).booleanValue());
+        }
+
+        value = properties.getProperty(PROP_DISCONNECTION_SQL_CODES);
+        if (value != null) {
+            dataSource.setDisconnectionSqlCodes(parseList(value, ','));
+        }
 
         // DBCP-215
         // Trick to make sure that initialSize connections are created
@@ -465,4 +474,20 @@ public class BasicDataSourceFactory impl
       }
       return p;
     }
+
+    /**
+     * Parse list of property values from a delimited string
+     * @param value delimited list of values
+     * @param delimiter character used to separate values in the list
+     * @return String Collection of values
+     */
+    private static Collection<String> parseList(String value, char delimiter) {
+        StringTokenizer tokenizer = new StringTokenizer(value, 
Character.toString(delimiter));
+        Collection<String> tokens =
+                new ArrayList<String>(tokenizer.countTokens());
+        while (tokenizer.hasMoreTokens()) {
+            tokens.add(tokenizer.nextToken());
+        }
+        return tokens;
+    }
 }

Modified: 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/BasicDataSourceMXBean.java
 Wed Jan 28 13:33:18 2015
@@ -215,10 +215,11 @@ public interface BasicDataSourceMXBean {
      * @return {@link BasicDataSource#getMaxConnLifetimeMillis()}
      */
     long getMaxConnLifetimeMillis();
-    
+
     /**
      * See {@link BasicDataSource#isLogExpiredConnections()}
      * @return {@link BasicDataSource#isLogExpiredConnections()}
+     * @since 2.1
      */
     boolean isLogExpiredConnections();
 
@@ -251,4 +252,18 @@ public interface BasicDataSourceMXBean {
      * @return {@link BasicDataSource#isClosed()}
      */
     boolean isClosed();
+
+    /**
+     * See {@link BasicDataSource#isFastFailValidation()}
+     * @return {@link BasicDataSource#isFastFailValidation()}
+     * @since 2.1
+     */
+    boolean isFastFailValidation();
+
+    /**
+     * See {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}
+     * @return {@link BasicDataSource#getDisconnectionSqlCodesAsArray()}
+     * @since 2.1
+     */
+    String[] getDisconnectionSqlCodesAsArray();
 }

Modified: 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnection.java
 Wed Jan 28 13:33:18 2015
@@ -21,6 +21,7 @@ import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.Collection;
 
 import javax.management.InstanceAlreadyExistsException;
 import javax.management.InstanceNotFoundException;
@@ -45,7 +46,7 @@ import org.apache.commons.pool2.ObjectPo
 public class PoolableConnection extends DelegatingConnection<Connection>
         implements PoolableConnectionMXBean {
 
-    private static MBeanServer MBEAN_SERVER = null; 
+    private static MBeanServer MBEAN_SERVER = null;
 
     static {
         try {
@@ -56,7 +57,7 @@ public class PoolableConnection extends
     }
 
     /** The pool to which I should return. */
-    private ObjectPool<PoolableConnection> _pool = null;
+    private final ObjectPool<PoolableConnection> _pool;
 
     private final ObjectName _jmxName;
 
@@ -66,15 +67,38 @@ public class PoolableConnection extends
     private String lastValidationSql = null;
 
     /**
+     *  Indicate that unrecoverable SQLException was thrown when using this 
connection.
+     *  Such a connection should be considered broken and not pass validation 
in the future.
+     */
+    private boolean _fatalSqlExceptionThrown = false;
+
+    /**
+     * SQL_STATE codes considered to signal fatal conditions. Overrides the
+     * defaults in {@link Utils#DISCONNECTION_SQL_CODES} (plus anything 
starting
+     * with {@link Utils#DISCONNECTION_SQL_CODE_PREFIX}).
+     */
+    private final Collection<String> _disconnectionSqlCodes;
+
+    /** Whether or not to fast fail validation after fatal connection errors */
+    private final boolean _fastFailValidation;
+
+    /**
      *
      * @param conn my underlying connection
      * @param pool the pool to which I should return when closed
+     * @param jmxName JMX name
+     * @param disconnectSqlCodes SQL_STATE codes considered fatal 
disconnection errors
+     * @param fastFailValidation true means fatal disconnection errors cause 
subsequent
+     *        validations to fail immediately (no attempt to run query or 
isValid)
      */
     public PoolableConnection(Connection conn,
-            ObjectPool<PoolableConnection> pool, ObjectName jmxName) {
+            ObjectPool<PoolableConnection> pool, ObjectName jmxName, 
Collection<String> disconnectSqlCodes,
+            boolean fastFailValidation) {
         super(conn);
         _pool = pool;
         _jmxName = jmxName;
+        _disconnectionSqlCodes = disconnectSqlCodes;
+        _fastFailValidation = fastFailValidation;
 
         if (jmxName != null) {
             try {
@@ -86,6 +110,17 @@ public class PoolableConnection extends
         }
     }
 
+    /**
+    *
+    * @param conn my underlying connection
+    * @param pool the pool to which I should return when closed
+    * @param jmxName JMX name
+    */
+   public PoolableConnection(Connection conn,
+           ObjectPool<PoolableConnection> pool, ObjectName jmxName) {
+       this(conn, pool, jmxName, null, false);
+   }
+
 
     @Override
     protected void passivate() throws SQLException {
@@ -218,8 +253,29 @@ public class PoolableConnection extends
         return toString();
     }
 
-
+    /**
+     * Validates the connection, using the following algorithm:
+     * <ol>
+     *   <li>If {@code fastFailValidation} (constructor argument) is {@code 
true} and
+     *       this connection has previously thrown a fatal disconnection 
exception,
+     *       a {@code SQLException} is thrown. </li>
+     *   <li>If {@code sql} is null, the driver's
+     *       #{@link Connection#isValid(int) isvalid(timeout)} is called.
+     *       If it returns {@code false}, {@code SQLException} is thrown;
+     *       otherwise, this method returns successfully.</li>
+     *   <li>If {@code sql} is not null, it is executed as a query and if the 
resulting
+     *       {@code ResultSet} contains at least one row, this method returns
+     *       successfully.  If not, {@code SQLException} is thrown.</li>
+     * </ol>
+     * @param sql validation query
+     * @param timeout validation timeout
+     * @throws SQLException if validation fails or an SQLException occurs 
during validation
+     */
     public void validate(String sql, int timeout) throws SQLException {
+        if (_fastFailValidation && _fatalSqlExceptionThrown) {
+            throw new 
SQLException(Utils.getMessage("poolableConnection.validate.fastFail"));
+        }
+
         if (sql == null || sql.length() == 0) {
             if (timeout < 0) {
                 timeout = 0;
@@ -250,5 +306,38 @@ public class PoolableConnection extends
             throw sqle;
         }
     }
+
+    /**
+     * Checks the SQLState of the input exception and any nested SQLExceptions 
it wraps.
+     * <p>
+     * If {@link #getDisconnectSqlCodes() disconnectSQLCodes} has been set, 
sql states
+     * are compared to those in the configured list of fatal exception codes.  
If this
+     * property is not set, codes are compared against the default codes in
+     * #{@link Utils.DISCONNECTION_SQL_CODES} and in this case anything 
starting with
+     * #{link Utils.DISCONNECTION_SQL_CODE_PREFIX} is considered a 
disconnection.</p>
+     *
+     * @param e SQLException to be examined
+     * @return true if the exception signals a disconnection
+     */
+    private boolean isDisconnectionSqlException(SQLException e) {
+        boolean fatalException = false;
+        String sqlState = e.getSQLState();
+        if (sqlState != null) {
+            fatalException = _disconnectionSqlCodes == null ? 
sqlState.startsWith(Utils.DISCONNECTION_SQL_CODE_PREFIX)
+                    || Utils.DISCONNECTION_SQL_CODES.contains(sqlState) : 
_disconnectionSqlCodes.contains(sqlState);
+            if (!fatalException) {
+                if (e.getNextException() != null) {
+                    fatalException = 
isDisconnectionSqlException(e.getNextException());
+                }
+            }
+        }
+        return fatalException;
+    }
+
+    @Override
+    protected void handleException(SQLException e) throws SQLException {
+        _fatalSqlExceptionThrown |= isDisconnectionSqlException(e);
+        super.handleException(e);
+    }
 }
 

Modified: 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/PoolableConnectionFactory.java
 Wed Jan 28 13:33:18 2015
@@ -190,7 +190,6 @@ public class PoolableConnectionFactory
         this.rollbackOnReturn = rollbackOnReturn;
     }
 
-
     public Integer getDefaultQueryTimeout() {
         return defaultQueryTimeout;
     }
@@ -199,6 +198,58 @@ public class PoolableConnectionFactory
         this.defaultQueryTimeout = defaultQueryTimeout;
     }
 
+    /**
+     * 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 #isFastFailValidation()} is
+     * {@code true}, whenever connections created by this factory 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 #isFastFailValidation()} is {@code false} setting this 
property has no
+     * effect.</p>
+     *
+     * @return SQL_STATE codes overriding defaults
+     * @since 2.1
+     */
+    public Collection<String> getDisconnectionSqlCodes() {
+        return _disconnectionSqlCodes;
+    }
+
+    /**
+     * @see #getDisconnectionSqlCodes()
+     * @param disconnectionSqlCodes
+     * @since 2.1
+     */
+    public void setDisconnectionSqlCodes(Collection<String> 
disconnectionSqlCodes) {
+        _disconnectionSqlCodes = disconnectionSqlCodes;
+    }
+
+    /**
+     * 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 factory will fast fail 
validation.
+     * @see #setDisconnectionSqlCodes(Collection)
+     * @since 2.1
+     */
+    public boolean isFastFailValidation() {
+        return _fastFailValidation;
+    }
+
+    /**
+     * @see #isFastFailValidation()
+     * @param fastFailValidation true means connections created by this 
factory will
+     * fast fail validation
+     * @since 2.1
+     */
+    public void setFastFailValidation(boolean fastFailValidation) {
+        _fastFailValidation = fastFailValidation;
+    }
 
     @Override
     public PooledObject<PoolableConnection> makeObject() throws Exception {
@@ -251,7 +302,8 @@ public class PoolableConnectionFactory
                     Constants.JMX_CONNECTION_BASE_EXT + connIndex);
         }
 
-        PoolableConnection pc = new PoolableConnection(conn,_pool, 
connJmxName);
+        PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName,
+                                      _disconnectionSqlCodes, 
_fastFailValidation);
 
         return new DefaultPooledObject<>(pc);
     }
@@ -396,6 +448,8 @@ public class PoolableConnectionFactory
     private volatile String _validationQuery = null;
     private volatile int _validationQueryTimeout = -1;
     private Collection<String> _connectionInitSqls = null;
+    private Collection<String> _disconnectionSqlCodes = null;
+    private boolean _fastFailValidation = false;
     private volatile ObjectPool<PoolableConnection> _pool = null;
     private Boolean _defaultReadOnly = null;
     private Boolean _defaultAutoCommit = null;

Modified: 
commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java 
(original)
+++ commons/proper/dbcp/trunk/src/main/java/org/apache/commons/dbcp2/Utils.java 
Wed Jan 28 13:33:18 2015
@@ -22,7 +22,9 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.Statement;
 import java.text.MessageFormat;
+import java.util.HashSet;
 import java.util.ResourceBundle;
+import java.util.Set;
 
 /**
  * Utility methods.
@@ -37,6 +39,31 @@ public final class Utils {
     public static final boolean IS_SECURITY_ENABLED =
             System.getSecurityManager() != null;
 
+    /** Any SQL_STATE starting with this value is considered a fatal 
disconnect */
+    public static final String DISCONNECTION_SQL_CODE_PREFIX = "08";
+    
+    /**
+     * SQL codes of fatal connection errors.
+     * <ul>
+     *  <li>57P01 (ADMIN SHUTDOWN)</li>
+     *  <li>57P02 (CRASH SHUTDOWN)</li>
+     *  <li>57P03 (CANNOT CONNECT NOW)</li>
+     *  <li>01002 (SQL92 disconnect error)</li>
+     *  <li>JZ0C0 (Sybase disconnect error)</li>
+     *  <li>JZ0C1 (Sybase disconnect error)</li>
+     * </ul>
+     */
+    public static final Set<String> DISCONNECTION_SQL_CODES;
+
+    static {
+        DISCONNECTION_SQL_CODES = new HashSet<String>();
+        DISCONNECTION_SQL_CODES.add("57P01"); // ADMIN SHUTDOWN
+        DISCONNECTION_SQL_CODES.add("57P02"); // CRASH SHUTDOWN
+        DISCONNECTION_SQL_CODES.add("57P03"); // CANNOT CONNECT NOW
+        DISCONNECTION_SQL_CODES.add("01002"); // SQL92 disconnect error
+        DISCONNECTION_SQL_CODES.add("JZ0C0"); // Sybase disconnect error
+        DISCONNECTION_SQL_CODES.add("JZ0C1"); // Sybase disconnect error
+    }
 
     private Utils() {
         // not instantiable

Modified: 
commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties
 (original)
+++ 
commons/proper/dbcp/trunk/src/main/resources/org/apache/commons/dbcp2/LocalStrings.properties
 Wed Jan 28 13:33:18 2015
@@ -15,9 +15,11 @@
 
 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
+poolableConnectionFactory.validateObject.fail=Failed to validate a poolable 
connection.
 
-swallowedExceptionLogger.onSwallowedException=An internal object pool 
swallowed an Exception
+poolableConnection.validate.fastFail=Fatal SQLException was thrown previously 
on this connection.
+
+swallowedExceptionLogger.onSwallowedException=An internal object pool 
swallowed an Exception.
 
 poolingDataSource.factoryConfig=PoolableConnectionFactory not linked to pool. 
Calling setPool() to fix the configuration.
 

Modified: commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml (original)
+++ commons/proper/dbcp/trunk/src/site/xdoc/configuration.xml Wed Jan 28 
13:33:18 2015
@@ -363,8 +363,8 @@ value less than the maximum number of cu
 </source>
 <p>
 <img src="images/icon_info_sml.gif"/>
-Default is false, it is a potential dangerous operation and misbehaving 
programs can do harmfull things. (closing the underlying or continue using it 
when the guarded connection is already closed)
-Be carefull and only use when you need direct access to driver specific 
extentions.
+Default is false, it is a potential dangerous operation and misbehaving 
programs can do harmful things. (closing the underlying or continue using it 
when the guarded connection is already closed)
+Be careful and only use when you need direct access to driver specific 
extensions.
 </p>
 <p>
 <img src="images/icon_warning_sml.gif"/>
@@ -420,6 +420,39 @@ default (300 sec). Traversing a resultse
 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>
+<table>
+<hr><th>Parameter</th><th>Default</th><th>Description</th></hr>
+<tr>
+   <td>fastFailValidation</td>
+   <td>false</td>
+   <td>
+      When this property is true, validation fails fast for connections that 
have
+      thrown "fatal" SQLExceptions. Requests to validate disconnected 
connections
+      fail immediately, with no call to the driver's isValid method or attempt 
to
+      execute a validation query.<br/>
+      The SQL_STATE codes considered to signal fatal errors are by default the 
following:
+      <ul>
+        <li>57P01 (ADMIN SHUTDOWN)</li>
+        <li>57P02 (CRASH SHUTDOWN)</li>
+        <li>57P03 (CANNOT CONNECT NOW)</li>
+        <li>01002 (SQL92 disconnect error)</li>
+        <li>JZ0C0 (Sybase disconnect error)</li>
+        <li>JZ0C1 (Sybase disconnect error)</li>
+        <li>Any SQL_STATE code that starts with "08"</li>
+      </ul>
+      To override this default set of disconnection codes, set the
+      <code>disconnectionSqlCodes</code> property.
+   </td>
+</tr>
+<tr>
+   <td>disconnectionSqlCodes</td>
+   <td>null</td>
+   <td>Comma-delimited list of SQL_STATE codes considered to signal fatal 
disconnection
+       errors. Setting this property has no effect unless
+      <code>fastFailValidation</code> is set to <code>true.</code>
+   </td>
+</tr>
+</table>
 
 </section>
 

Modified: 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestBasicDataSourceFactory.java
 Wed Jan 28 13:33:18 2015
@@ -19,6 +19,7 @@ package org.apache.commons.dbcp2;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import java.sql.Connection;
 import java.util.Properties;
@@ -74,6 +75,8 @@ public class TestBasicDataSourceFactory
         properties.setProperty("poolPreparedStatements", "true");
         properties.setProperty("maxOpenPreparedStatements", "10");
         properties.setProperty("lifo", "true");
+        properties.setProperty("fastFailValidation", "true");
+        properties.setProperty("disconnectionSqlCodes", "XXX,YYY");
 
         BasicDataSource ds = 
BasicDataSourceFactory.createDataSource(properties);
 
@@ -111,5 +114,8 @@ public class TestBasicDataSourceFactory
         assertEquals(true, ds.isPoolPreparedStatements());
         assertEquals(10, ds.getMaxOpenPreparedStatements());
         assertEquals(true, ds.getLifo());
+        assertEquals(true, ds.isFastFailValidation());
+        assertTrue(ds.getDisconnectionSqlCodes().contains("XXX"));
+        assertTrue(ds.getDisconnectionSqlCodes().contains("YYY"));
     }
 }

Modified: 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TestPoolableConnection.java
 Wed Jan 28 13:33:18 2015
@@ -18,12 +18,14 @@ package org.apache.commons.dbcp2;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 import java.sql.Connection;
 import java.sql.SQLException;
+import java.util.ArrayList;
 
-import org.apache.commons.pool2.ObjectPool;
 import org.apache.commons.pool2.impl.GenericObjectPool;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -34,7 +36,7 @@ import org.junit.Test;
  */
 public class TestPoolableConnection {
 
-    private ObjectPool<PoolableConnection> pool = null;
+    private GenericObjectPool<PoolableConnection> pool = null;
 
     @Before
     public void setUp() throws Exception {
@@ -45,9 +47,15 @@ public class TestPoolableConnection {
         factory.setDefaultAutoCommit(Boolean.TRUE);
         factory.setDefaultReadOnly(Boolean.TRUE);
 
+
         pool = new GenericObjectPool<>(factory);
         factory.setPool(pool);
     }
+    
+    @After
+    public void tearDown() {
+        pool.close();
+    }
 
     @Test
     public void testConnectionPool() throws Exception {
@@ -108,4 +116,82 @@ public class TestPoolableConnection {
         Assert.assertEquals(0, pool.getNumActive());
         Assert.assertEquals(1, pool.getNumIdle());
     }
+
+    @Test
+    public void testFastFailValidation() throws Exception {
+        pool.setTestOnReturn(true);
+        PoolableConnectionFactory factory = (PoolableConnectionFactory) 
pool.getFactory();
+        factory.setFastFailValidation(true);
+        PoolableConnection conn = pool.borrowObject();
+        TesterConnection nativeConnection = (TesterConnection) 
conn.getInnermostDelegate();
+        
+        // Set up non-fatal exception
+        nativeConnection.setFailure(new SQLException("Not fatal error.", 
"Invalid syntax."));
+        try {
+            conn.createStatement();
+            fail("Should throw SQL exception.");
+        } catch (SQLException ignored) {
+            // cleanup failure
+            nativeConnection.setFailure(null);
+        }
+
+        // validate should not fail - error was not fatal and condition was 
cleaned up
+        conn.validate("SELECT 1", 1000);
+
+        // now set up fatal failure
+        nativeConnection.setFailure(new SQLException("Fatal connection 
error.", "01002"));
+
+        try {
+            conn.createStatement();
+            fail("Should throw SQL exception.");
+        } catch (SQLException ignored) {
+            // cleanup failure
+            nativeConnection.setFailure(null);
+        }
+
+        // validate should now fail because of previous fatal error, despite 
cleanup
+        try {
+            conn.validate("SELECT 1", 1000);
+            fail("Should throw SQL exception on validation.");
+        } catch (SQLException notValid){
+            // expected - fatal error && fastFailValidation
+        }
+
+        // verify that bad connection does not get returned to the pool
+        conn.close();  // testOnReturn triggers validate, which should fail
+        assertEquals("The pool should have no active connections",
+                0, pool.getNumActive());
+        assertEquals("The pool should have no idle connections",
+                0, pool.getNumIdle());
+    }
+    
+    @Test
+    public void testFastFailValidationCustomCodes() throws Exception {
+        pool.setTestOnReturn(true);
+        PoolableConnectionFactory factory = (PoolableConnectionFactory) 
pool.getFactory();
+        factory.setFastFailValidation(true);
+        ArrayList<String> disconnectionSqlCodes = new ArrayList<String>();
+        disconnectionSqlCodes.add("XXX");
+        factory.setDisconnectionSqlCodes(disconnectionSqlCodes);
+        PoolableConnection conn = pool.borrowObject();
+        TesterConnection nativeConnection = (TesterConnection) 
conn.getInnermostDelegate();
+        
+        // Set up fatal exception
+        nativeConnection.setFailure(new SQLException("Fatal connection 
error.", "XXX"));
+        
+        try {
+            conn.createStatement();
+            fail("Should throw SQL exception.");
+        } catch (SQLException ignored) {
+            // cleanup failure
+            nativeConnection.setFailure(null);
+        }
+        
+        // verify that bad connection does not get returned to the pool
+        conn.close();  // testOnReturn triggers validate, which should fail
+        assertEquals("The pool should have no active connections",
+                0, pool.getNumActive());
+        assertEquals("The pool should have no idle connections",
+                0, pool.getNumIdle());
+    }
 }

Modified: 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java
URL: 
http://svn.apache.org/viewvc/commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java?rev=1655301&r1=1655300&r2=1655301&view=diff
==============================================================================
--- 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java
 (original)
+++ 
commons/proper/dbcp/trunk/src/test/java/org/apache/commons/dbcp2/TesterConnection.java
 Wed Jan 28 13:33:18 2015
@@ -234,7 +234,11 @@ public class TesterConnection implements
 
     protected void checkFailure() throws SQLException {
         if (failure != null) {
-            throw new SQLException("TesterConnection failure", failure);
+            if(failure instanceof SQLException) {
+                throw (SQLException)failure;
+            } else {
+                throw new SQLException("TesterConnection failure", failure);
+            }
         }
     }
 


Reply via email to