commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bay...@apache.org
Subject svn commit: r1156964 - in /commons/proper/dbutils/trunk: pom.xml src/java/org/apache/commons/dbutils/AsyncQueryRunner.java src/test/org/apache/commons/dbutils/AsyncQueryRunnerTest.java
Date Fri, 12 Aug 2011 04:45:26 GMT
Author: bayard
Date: Fri Aug 12 04:45:25 2011
New Revision: 1156964

URL: http://svn.apache.org/viewvc?rev=1156964&view=rev
Log:
Adding asynchronous query runner patch from William Speirs. Also moves DbUtils to Java6 dependent. DBUTILS-78

Added:
    commons/proper/dbutils/trunk/src/java/org/apache/commons/dbutils/AsyncQueryRunner.java   (with props)
    commons/proper/dbutils/trunk/src/test/org/apache/commons/dbutils/AsyncQueryRunnerTest.java   (with props)
Modified:
    commons/proper/dbutils/trunk/pom.xml

Modified: commons/proper/dbutils/trunk/pom.xml
URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/pom.xml?rev=1156964&r1=1156963&r2=1156964&view=diff
==============================================================================
--- commons/proper/dbutils/trunk/pom.xml [utf-8] (original)
+++ commons/proper/dbutils/trunk/pom.xml [utf-8] Fri Aug 12 04:45:25 2011
@@ -173,14 +173,26 @@
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>3.8.2</version>
+      <version>4.8.2</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+    	<groupId>org.mockito</groupId>
+    	<artifactId>mockito-core</artifactId>
+    	<version>1.8.5</version>
+    	<scope>test</scope>
+    </dependency>
+    <dependency>
+    	<groupId>org.hamcrest</groupId>
+    	<artifactId>hamcrest-all</artifactId>
+    	<version>1.1</version>
+    	<scope>test</scope>
+    </dependency>
   </dependencies>
 
   <properties>
-    <maven.compile.source>1.5</maven.compile.source>
-    <maven.compile.target>1.5</maven.compile.target>
+    <maven.compile.source>1.6</maven.compile.source>
+    <maven.compile.target>1.6</maven.compile.target>
     <commons.componentid>dbutils</commons.componentid>
     <commons.release.version>1.3</commons.release.version>
     <commons.rc.version>RC4</commons.rc.version>
@@ -205,8 +217,8 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
           <configuration>
-            <source>1.5</source>
-            <target>1.5</target>
+            <source>1.6</source>
+            <target>1.6</target>
         </configuration>
       </plugin>
       <plugin>
@@ -268,7 +280,7 @@
         <artifactId>maven-pmd-plugin</artifactId>
         <version>2.3</version>
         <configuration>
-          <targetJdk>1.5</targetJdk>
+          <targetJdk>1.6</targetJdk>
         </configuration>
       </plugin>
       <plugin>

Added: commons/proper/dbutils/trunk/src/java/org/apache/commons/dbutils/AsyncQueryRunner.java
URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/src/java/org/apache/commons/dbutils/AsyncQueryRunner.java?rev=1156964&view=auto
==============================================================================
--- commons/proper/dbutils/trunk/src/java/org/apache/commons/dbutils/AsyncQueryRunner.java (added)
+++ commons/proper/dbutils/trunk/src/java/org/apache/commons/dbutils/AsyncQueryRunner.java Fri Aug 12 04:45:25 2011
@@ -0,0 +1,786 @@
+/*
+ * 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.commons.dbutils;
+
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.RunnableFuture;
+
+import javax.sql.DataSource;
+
+/**
+ * Executes SQL queries with pluggable strategies for handling 
+ * <code>ResultSet</code>s.  This class is thread safe.
+ * 
+ * @see ResultSetHandler
+ */
+public class AsyncQueryRunner {
+
+    /**
+     * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried it yet)?
+     */
+    private volatile boolean pmdKnownBroken = false;
+    
+    /**
+     * The DataSource to retrieve connections from.
+     */
+    protected final DataSource ds;
+
+    /**
+     * Constructor for QueryRunner.
+     */
+    public AsyncQueryRunner() {
+        this(null, false); 
+    }
+
+    /**
+     * Constructor for QueryRunner, allows workaround for Oracle drivers
+     * @param pmdKnownBroken Oracle drivers don't support {@link ParameterMetaData#getParameterType(int) };
+     * if <code>pmdKnownBroken</code> is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     */
+    public AsyncQueryRunner(boolean pmdKnownBroken) {
+        this(null, pmdKnownBroken); 
+    }
+    
+    /**
+     * Constructor for QueryRunner, allows workaround for Oracle drivers.  Methods that do not take a 
+     * <code>Connection</code> parameter will retrieve connections from this
+     * <code>DataSource</code>.
+     * 
+     * @param ds The <code>DataSource</code> to retrieve connections from.
+     */
+    public AsyncQueryRunner(DataSource ds) {
+        this(ds, false);
+    }
+    
+    /**
+     * Constructor for QueryRunner, allows workaround for Oracle drivers.  Methods that do not take a 
+     * <code>Connection</code> parameter will retrieve connections from this
+     * <code>DataSource</code>.
+     * 
+     * @param ds The <code>DataSource</code> to retrieve connections from.
+     * @param pmdKnownBroken Oracle drivers don't support {@link ParameterMetaData#getParameterType(int) };
+     * if <code>pmdKnownBroken</code> is set to true, we won't even try it; if false, we'll try it,
+     * and if it breaks, we'll remember not to use it again.
+     */
+    public AsyncQueryRunner(DataSource ds, boolean pmdKnownBroken) {
+        this.pmdKnownBroken = pmdKnownBroken;
+        this.ds = ds;
+    }
+    
+    protected class BatchCallableStatement implements Callable<int[]> {
+    	private String sql;
+    	private Object[][] params;
+    	private Connection conn;
+    	private boolean closeConn;
+    	private PreparedStatement ps;
+    	
+    	public BatchCallableStatement(String sql, Object[][] params, Connection conn, boolean closeConn, PreparedStatement ps) {
+    		this.sql = sql;
+    		this.params = params;
+    		this.conn = conn;
+    		this.closeConn = closeConn;
+    		this.ps = ps;
+    	}
+		
+    	public int[] call() throws Exception {
+    		int[] ret = null;
+    		
+    		try {
+    			ret = ps.executeBatch();
+    		} catch(SQLException e) {
+    			rethrow(e, sql, (Object[])params);
+    		} finally {
+    			close(ps);
+    			if(closeConn)
+    				close(conn);
+    		}
+    		
+    		return ret;
+    	}
+    }
+    
+    /**
+     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.
+     * 
+     * @param conn The Connection to use to run the query.  The caller is
+     * responsible for closing this Connection.
+     * @param sql The SQL to execute.
+     * @param params An array of query replacement parameters.  Each row in
+     * this array is one set of batch replacement values. 
+     * @return A RunnableFuture which when completed returns the number of rows updated per statement.
+     * @throws SQLException if a database access error occurs
+     * @since DbUtils 1.1
+     */
+    public RunnableFuture<int[]> batch(Connection conn, String sql, Object[][] params) throws SQLException {
+        return this.batch(conn, false, sql, params);
+    }
+
+    /**
+     * Execute a batch of SQL INSERT, UPDATE, or DELETE queries.  The 
+     * <code>Connection</code> is retrieved from the <code>DataSource</code> 
+     * set in the constructor.  This <code>Connection</code> must be in 
+     * auto-commit mode or the update will not be saved. 
+     * 
+     * @param sql The SQL to execute.
+     * @param params An array of query replacement parameters.  Each row in
+     * this array is one set of batch replacement values. 
+     * @return A RunnableFuture which when completed returns the number of rows updated per statement.
+     * @throws SQLException if a database access error occurs
+     * @since DbUtils 1.1
+     */
+    public RunnableFuture<int[]> batch(String sql, Object[][] params) throws SQLException {
+        Connection conn = this.prepareConnection();
+
+        return this.batch(conn, true, sql, params);
+    }
+    
+    private RunnableFuture<int[]> batch(Connection conn, boolean closeConn, String sql, Object[][] params) throws SQLException {
+    	if(conn == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null connection");
+    	}
+    	
+    	if(sql == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null SQL statement");
+    	}
+    	
+    	if(params == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null parameters. If parameters aren't need, pass an empty array.");
+    	}
+
+        PreparedStatement stmt = null;
+        FutureTask<int[]> ret = null;
+        try {
+        	stmt = this.prepareStatement(conn, sql);
+        	
+            for (int i = 0; i < params.length; i++) {
+                this.fillStatement(stmt, params[i]);
+                stmt.addBatch();
+            }
+            
+            ret = new FutureTask<int[]>(new BatchCallableStatement(sql, params, conn, closeConn, stmt));
+
+        } catch (SQLException e) {
+            close(stmt);
+            close(conn);
+            this.rethrow(e, sql, (Object[])params);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Fill the <code>PreparedStatement</code> replacement parameters with 
+     * the given objects.
+     * @param stmt PreparedStatement to fill
+     * @param params Query replacement parameters; <code>null</code> is a valid
+     * value to pass in.
+     * @throws SQLException if a database access error occurs
+     */
+    public void fillStatement(PreparedStatement stmt, Object... params) throws SQLException {
+
+        // check the parameter count, if we can
+        ParameterMetaData pmd = null;
+        if (!pmdKnownBroken) {
+            pmd = stmt.getParameterMetaData();
+            int stmtCount = pmd.getParameterCount();
+            int paramsCount = params == null ? 0 : params.length;
+            
+            if (stmtCount != paramsCount) {
+                throw new SQLException("Wrong number of parameters: expected "
+                        + stmtCount + ", was given " + paramsCount);
+            }
+        }
+
+        // nothing to do here
+        if (params == null) {
+            return;
+        }
+
+        for (int i = 0; i < params.length; i++) {
+            if (params[i] != null) {
+                stmt.setObject(i + 1, params[i]);
+            } else {
+                // VARCHAR works with many drivers regardless
+                // of the actual column type.  Oddly, NULL and 
+                // OTHER don't work with Oracle's drivers.
+                int sqlType = Types.VARCHAR;
+                if (!pmdKnownBroken) {
+                    try {
+                        sqlType = pmd.getParameterType(i + 1);
+                    } catch (SQLException e) {
+                        pmdKnownBroken = true;
+                    }
+                }
+                stmt.setNull(i + 1, sqlType);
+            }
+        }
+    }
+
+    /**
+     * Fill the <code>PreparedStatement</code> replacement parameters with the
+     * given object's bean property values.
+     * 
+     * @param stmt
+     *            PreparedStatement to fill
+     * @param bean
+     *            a JavaBean object
+     * @param properties
+     *            an ordered array of properties; this gives the order to insert
+     *            values in the statement
+     * @throws SQLException
+     *             if a database access error occurs
+     */
+    public void fillStatementWithBean(PreparedStatement stmt, Object bean,
+            PropertyDescriptor[] properties) throws SQLException {
+        Object[] params = new Object[properties.length];
+        for (int i = 0; i < properties.length; i++) {
+            PropertyDescriptor property = properties[i];
+            Object value = null;
+            Method method = property.getReadMethod();
+            if (method == null) {
+                throw new RuntimeException("No read method for bean property "
+                        + bean.getClass() + " " + property.getName());
+            }
+            try {
+                value = method.invoke(bean, new Object[0]);
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException("Couldn't invoke method: " + method, e);
+            } catch (IllegalArgumentException e) {
+                throw new RuntimeException("Couldn't invoke method with 0 arguments: " + method, e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Couldn't invoke method: " + method, e);
+            } 
+            params[i] = value;
+        }
+        fillStatement(stmt, params);
+    }
+
+    /**
+     * Fill the <code>PreparedStatement</code> replacement parameters with the
+     * given object's bean property values.
+     * 
+     * @param stmt PreparedStatement to fill
+     * @param bean A JavaBean object
+     * @param propertyNames An ordered array of property names (these should match the
+     *                      getters/setters); this gives the order to insert values in the
+     *                      statement
+     * @throws SQLException If a database access error occurs
+     */
+    public void fillStatementWithBean(PreparedStatement stmt, Object bean, String... propertyNames) throws SQLException {
+        PropertyDescriptor[] descriptors;
+        try {
+            descriptors = Introspector.getBeanInfo(bean.getClass())
+                    .getPropertyDescriptors();
+        } catch (IntrospectionException e) {
+            throw new RuntimeException("Couldn't introspect bean " + bean.getClass().toString(), e);
+        }
+        PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
+        for (int i = 0; i < propertyNames.length; i++) {
+            String propertyName = propertyNames[i];
+            if (propertyName == null) {
+                throw new NullPointerException("propertyName can't be null: " + i);
+            }
+            boolean found = false;
+            for (int j = 0; j < descriptors.length; j++) {
+                PropertyDescriptor descriptor = descriptors[j];
+                if (propertyName.equals(descriptor.getName())) {
+                    sorted[i] = descriptor;
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new RuntimeException("Couldn't find bean property: "
+                        + bean.getClass() + " " + propertyName);
+            }
+        }
+        fillStatementWithBean(stmt, bean, sorted);
+    }
+
+    /**
+     * Returns the <code>DataSource</code> this runner is using.  
+     * <code>QueryRunner</code> methods always call this method to get the
+     * <code>DataSource</code> so subclasses can provide specialized
+     * behavior.
+     *
+     * @return DataSource the runner is using
+     */
+    public DataSource getDataSource() {
+        return this.ds;
+    }
+
+    /**
+     * Factory method that creates and initializes a 
+     * <code>PreparedStatement</code> object for the given SQL.  
+     * <code>QueryRunner</code> methods always call this method to prepare 
+     * statements for them.  Subclasses can override this method to provide 
+     * special PreparedStatement configuration if needed.  This implementation
+     * simply calls <code>conn.prepareStatement(sql)</code>.
+     *  
+     * @param conn The <code>Connection</code> used to create the 
+     * <code>PreparedStatement</code>
+     * @param sql The SQL statement to prepare.
+     * @return An initialized <code>PreparedStatement</code>.
+     * @throws SQLException if a database access error occurs
+     */
+    protected PreparedStatement prepareStatement(Connection conn, String sql)
+        throws SQLException {
+            
+        return conn.prepareStatement(sql);
+    }
+    
+    /**
+     * Factory method that creates and initializes a 
+     * <code>Connection</code> object.  <code>QueryRunner</code> methods 
+     * always call this method to retrieve connections from its DataSource.  
+     * Subclasses can override this method to provide 
+     * special <code>Connection</code> configuration if needed.  This 
+     * implementation simply calls <code>ds.getConnection()</code>.
+     * 
+     * @return An initialized <code>Connection</code>.
+     * @throws SQLException if a database access error occurs
+     * @since DbUtils 1.1
+     */
+    protected Connection prepareConnection() throws SQLException {
+        if(this.getDataSource() == null) {
+            throw new SQLException("QueryRunner requires a DataSource to be " +
+                "invoked in this way, or a Connection should be passed in");
+        }
+        return this.getDataSource().getConnection();
+    }
+    
+    protected class QueryCallableStatement<T> implements Callable<T> {
+    	private String sql;
+    	private Object[] params;
+    	private Connection conn;
+    	private boolean closeConn;
+    	private PreparedStatement ps;
+    	private ResultSetHandler<T> rsh;
+    	
+    	public QueryCallableStatement(Connection conn, boolean closeConn, PreparedStatement ps, ResultSetHandler<T> rsh, String sql, Object... params) {
+    		this.sql = sql;
+    		this.params = params;
+    		this.conn = conn;
+    		this.closeConn = closeConn;
+    		this.ps = ps;
+    		this.rsh = rsh;
+    	}
+
+		public T call() throws Exception {
+            ResultSet rs = null;
+            T ret = null;
+            
+            try {
+                rs = wrap(ps.executeQuery());
+                ret = rsh.handle(rs);
+            } catch(SQLException e) {
+            	rethrow(e, sql, params);
+            } finally {
+            	try {
+            		close(rs);
+            	} finally {
+            		close(ps);
+            		if(closeConn)
+            			close(conn);
+            	}
+            }
+            
+            return ret;
+		}
+    	
+    }
+
+    private <T> RunnableFuture<T> query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
+        PreparedStatement stmt = null;
+        FutureTask<T> ret = null;
+
+    	if(conn == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null connection");
+    	}
+    	
+    	if(sql == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null SQL statement");
+    	}
+    	
+    	if(rsh == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null ResultSetHandler");
+    	}
+    	
+        try {
+            stmt = this.prepareStatement(conn, sql);
+            this.fillStatement(stmt, params);
+            
+            ret = new FutureTask<T>(new QueryCallableStatement<T>(conn, closeConn, stmt, rsh, sql, params));
+
+        } catch (SQLException e) {
+            close(stmt);
+            if(closeConn)
+            	close(conn);
+            this.rethrow(e, sql, params);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Execute an SQL SELECT query with replacement parameters.  The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param rsh The handler that converts the results into an object.
+     * @param params The replacement parameters.
+     * @return A RunnableFuture which when completed returns the object returned by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> RunnableFuture<T> query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
+    	return query(conn, false, sql, rsh, params);
+    }
+
+    /**
+     * Execute an SQL SELECT query without any replacement parameters.  The
+     * caller is responsible for closing the connection.
+     * @param <T> The type of object that the handler returns
+     * @param conn The connection to execute the query in.
+     * @param sql The query to execute.
+     * @param rsh The handler that converts the results into an object.
+     * @return A RunnableFuture which when completed returns the object returned by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> RunnableFuture<T> query(Connection conn, String sql, ResultSetHandler<T> rsh) throws SQLException {
+        return this.query(conn, false, sql, rsh, (Object[]) null);
+    }
+
+    /**
+     * Executes the given SELECT SQL query and returns a result object.
+     * The <code>Connection</code> is retrieved from the 
+     * <code>DataSource</code> set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from 
+     * the <code>ResultSet</code>.
+     * @param params Initialize the PreparedStatement's IN parameters with 
+     * this array.
+     * @return A RunnableFuture which when completed returns the object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> RunnableFuture<T> query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
+        Connection conn = this.prepareConnection();
+
+        return this.query(conn, true, sql, rsh, params);
+    }
+
+    /**
+     * Executes the given SELECT SQL without any replacement parameters.
+     * The <code>Connection</code> is retrieved from the
+     * <code>DataSource</code> set in the constructor.
+     * @param <T> The type of object that the handler returns
+     * @param sql The SQL statement to execute.
+     * @param rsh The handler used to create the result object from 
+     * the <code>ResultSet</code>.
+     * 
+     * @return A RunnableFuture which when completed returns the object generated by the handler.
+     * @throws SQLException if a database access error occurs
+     */
+    public <T> RunnableFuture<T> query(String sql, ResultSetHandler<T> rsh) throws SQLException {
+        Connection conn = this.prepareConnection();
+        return this.query(conn, true, sql, rsh, (Object[]) null);
+    }
+
+    /**
+     * Throws a new exception with a more informative error message.
+     * 
+     * @param cause The original exception that will be chained to the new 
+     * exception when it's rethrown. 
+     * 
+     * @param sql The query that was executing when the exception happened.
+     * 
+     * @param params The query replacement parameters; <code>null</code> is a 
+     * valid value to pass in.
+     * 
+     * @throws SQLException if a database access error occurs
+     */
+    protected void rethrow(SQLException cause, String sql, Object... params)
+        throws SQLException {
+
+        String causeMessage = cause.getMessage();
+        if (causeMessage == null) {
+            causeMessage = "";
+        }
+        StringBuffer msg = new StringBuffer(causeMessage);
+
+        msg.append(" Query: ");
+        msg.append(sql);
+        msg.append(" Parameters: ");
+
+        if (params == null) {
+            msg.append("[]");
+        } else {
+            msg.append(Arrays.deepToString(params));
+        }
+
+        SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
+                cause.getErrorCode());
+        e.setNextException(cause);
+
+        throw e;
+    }
+
+    protected class UpdateCallableStatement implements Callable<Integer> {
+    	private String sql;
+    	private Object[] params;
+    	private Connection conn;
+    	private boolean closeConn;
+    	private PreparedStatement ps;
+    	
+    	public UpdateCallableStatement(Connection conn, boolean closeConn, PreparedStatement ps, String sql, Object... params) {
+    		this.sql = sql;
+    		this.params = params;
+    		this.conn = conn;
+    		this.closeConn = closeConn;
+    		this.ps = ps;
+    	}
+
+		public Integer call() throws Exception {
+			int rows = 0;
+			
+			try {
+	            rows = ps.executeUpdate();
+			} catch (SQLException e) {
+				rethrow(e, sql, params);
+			} finally {
+				close(ps);
+				if(closeConn)
+					close(conn);
+			}
+			
+			return rows;
+		}
+    	
+    }
+    
+    private RunnableFuture<Integer> update(Connection conn, boolean closeConn, String sql, Object... params) throws SQLException {
+        PreparedStatement stmt = null;
+        FutureTask<Integer> ret = null;
+
+    	if(conn == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null connection");
+    	}
+    	
+    	if(sql == null) {
+    		if(closeConn)
+    			close(conn);
+    		throw new SQLException("Null SQL statement");
+    	}
+
+        try {
+            stmt = this.prepareStatement(conn, sql);
+            this.fillStatement(stmt, params);
+            
+            ret = new FutureTask<Integer>(new UpdateCallableStatement(conn, closeConn, stmt, sql, params));
+
+        } catch (SQLException e) {
+        	close(stmt);
+        	if(closeConn)
+        		close(conn);
+            this.rethrow(e, sql, params);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query without replacement
+     * parameters.
+     * 
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @return A RunnableFuture which when completed returns the number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public RunnableFuture<Integer> update(Connection conn, String sql) throws SQLException {
+        return this.update(conn, false, sql, (Object[]) null);
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query with a single replacement
+     * parameter.
+     * 
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param param The replacement parameter.
+     * @return A RunnableFuture which when completed returns the number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public RunnableFuture<Integer> update(Connection conn, String sql, Object param) throws SQLException {
+        return this.update(conn, false, sql, new Object[] { param });
+    }
+
+    /**
+     * Execute an SQL INSERT, UPDATE, or DELETE query.
+     * 
+     * @param conn The connection to use to run the query.
+     * @param sql The SQL to execute.
+     * @param params The query replacement parameters.
+     * @return A RunnableFuture which when completed returns the number of rows updated.
+     * @throws SQLException if a database access error occurs
+     */
+    public RunnableFuture<Integer> update(Connection conn, String sql, Object... params) throws SQLException {
+    	return this.update(conn, false, sql, params);
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement without
+     * any replacement parameters. The <code>Connection</code> is retrieved 
+     * from the <code>DataSource</code> set in the constructor.  This 
+     * <code>Connection</code> must be in auto-commit mode or the update will 
+     * not be saved. 
+     * 
+     * @param sql The SQL statement to execute.
+     * @throws SQLException if a database access error occurs
+     * @return A RunnableFuture which when completed returns the number of rows updated.
+     */
+    public RunnableFuture<Integer> update(String sql) throws SQLException {
+        Connection conn = this.prepareConnection();
+        return this.update(conn, true, sql, (Object[]) null);
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement with
+     * a single replacement parameter.  The <code>Connection</code> is 
+     * retrieved from the <code>DataSource</code> set in the constructor.
+     * This <code>Connection</code> must be in auto-commit mode or the 
+     * update will not be saved. 
+     * 
+     * @param sql The SQL statement to execute.
+     * @param param The replacement parameter.
+     * @throws SQLException if a database access error occurs
+     * @return A RunnableFuture which when completed returns the number of rows updated.
+     */
+    public RunnableFuture<Integer> update(String sql, Object param) throws SQLException {
+        Connection conn = this.prepareConnection();
+        return this.update(conn, true, sql, new Object[] { param });
+    }
+
+    /**
+     * Executes the given INSERT, UPDATE, or DELETE SQL statement.  The 
+     * <code>Connection</code> is retrieved from the <code>DataSource</code> 
+     * set in the constructor.  This <code>Connection</code> must be in 
+     * auto-commit mode or the update will not be saved. 
+     * 
+     * @param sql The SQL statement to execute.
+     * @param params Initializes the PreparedStatement's IN (i.e. '?') 
+     * parameters.
+     * @throws SQLException if a database access error occurs
+     * @return A RunnableFuture which when completed returns the number of rows updated.
+     */
+    public RunnableFuture<Integer> update(String sql, Object... params) throws SQLException {
+        Connection conn = this.prepareConnection();
+        return this.update(conn, true, sql, params);
+    }
+    
+    /**
+     * Wrap the <code>ResultSet</code> in a decorator before processing it.
+     * This implementation returns the <code>ResultSet</code> it is given
+     * without any decoration.
+     *
+     * <p>
+     * Often, the implementation of this method can be done in an anonymous 
+     * inner class like this:
+     * </p>
+     * <pre> 
+     * QueryRunner run = new QueryRunner() {
+     *     protected ResultSet wrap(ResultSet rs) {
+     *         return StringTrimmedResultSet.wrap(rs);
+     *     }
+     * };
+     * </pre>
+     * 
+     * @param rs The <code>ResultSet</code> to decorate; never 
+     * <code>null</code>.
+     * @return The <code>ResultSet</code> wrapped in some decorator. 
+     */
+    protected ResultSet wrap(ResultSet rs) {
+        return rs;
+    }
+    
+    /**
+     * Close a <code>Connection</code>.  This implementation avoids closing if 
+     * null and does <strong>not</strong> suppress any exceptions.  Subclasses
+     * can override to provide special handling like logging.
+     * @param conn Connection to close
+     * @throws SQLException if a database access error occurs
+     * @since DbUtils 1.1
+     */
+    protected void close(Connection conn) throws SQLException {
+        DbUtils.close(conn);
+    }
+    
+    /**
+     * Close a <code>Statement</code>.  This implementation avoids closing if 
+     * null and does <strong>not</strong> suppress any exceptions.  Subclasses
+     * can override to provide special handling like logging.
+     * @param stmt Statement to close
+     * @throws SQLException if a database access error occurs
+     * @since DbUtils 1.1
+     */
+    protected void close(Statement stmt) throws SQLException {
+        DbUtils.close(stmt);
+    }
+
+    /**
+     * Close a <code>ResultSet</code>.  This implementation avoids closing if 
+     * null and does <strong>not</strong> suppress any exceptions.  Subclasses
+     * can override to provide special handling like logging.
+     * @param rs ResultSet to close
+     * @throws SQLException if a database access error occurs
+     * @since DbUtils 1.1
+     */
+    protected void close(ResultSet rs) throws SQLException {
+        DbUtils.close(rs);
+    }
+
+}

Propchange: commons/proper/dbutils/trunk/src/java/org/apache/commons/dbutils/AsyncQueryRunner.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: commons/proper/dbutils/trunk/src/test/org/apache/commons/dbutils/AsyncQueryRunnerTest.java
URL: http://svn.apache.org/viewvc/commons/proper/dbutils/trunk/src/test/org/apache/commons/dbutils/AsyncQueryRunnerTest.java?rev=1156964&view=auto
==============================================================================
--- commons/proper/dbutils/trunk/src/test/org/apache/commons/dbutils/AsyncQueryRunnerTest.java (added)
+++ commons/proper/dbutils/trunk/src/test/org/apache/commons/dbutils/AsyncQueryRunnerTest.java Fri Aug 12 04:45:25 2011
@@ -0,0 +1,569 @@
+/*
+ * 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.commons.dbutils;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.RunnableFuture;
+
+import javax.sql.DataSource;
+
+import org.apache.commons.dbutils.handlers.ArrayHandler;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AsyncQueryRunnerTest {
+    AsyncQueryRunner runner;
+	ArrayHandler handler;
+    
+    @Mock DataSource dataSource;
+    @Mock Connection conn;
+    @Mock PreparedStatement stmt;
+    @Mock ParameterMetaData meta;
+    @Mock ResultSet results;
+    
+    static final Method getParameterCount, getParameterType, getParameterMetaData;
+    static {
+        try {
+            getParameterCount = ParameterMetaData.class.getMethod("getParameterCount", new Class[0]);
+            getParameterType = ParameterMetaData.class.getMethod("getParameterType", new Class[]{int.class});
+            getParameterMetaData = PreparedStatement.class.getMethod("getParameterMetaData", new Class[0]);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    
+    @Before
+    public void setUp() throws Exception {
+		MockitoAnnotations.initMocks(this);	// init the mocks
+
+		when(dataSource.getConnection()).thenReturn(conn);
+		when(conn.prepareStatement(any(String.class))).thenReturn(stmt);
+		when(stmt.getParameterMetaData()).thenReturn(meta);
+		when(stmt.getResultSet()).thenReturn(results);
+		when(stmt.executeQuery()).thenReturn(results);
+		when(results.next()).thenReturn(false);
+
+		 handler = new ArrayHandler();
+		 runner = new AsyncQueryRunner(dataSource);
+    }
+    
+    //
+    // Batch test cases
+    //
+    
+    private void callGoodBatch(Connection conn, Object[][] params) throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	RunnableFuture<int[]> future = runner.batch(conn, "select * from blah where ? = ?", params);
+    	verify(stmt, times(2)).addBatch();
+    	verify(stmt, never()).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(1)).executeBatch();
+    	verify(stmt, times(1)).close();	// make sure we closed the statement
+    	verify(conn, times(0)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    }
+    
+    private void callGoodBatch(Object[][] params) throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	RunnableFuture<int[]> future = runner.batch("select * from blah where ? = ?", params);
+    	verify(stmt, times(2)).addBatch();
+    	verify(stmt, never()).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(1)).executeBatch();
+    	verify(stmt, times(1)).close();	// make sure we closed the statement
+    	verify(conn, times(1)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    }
+
+    @Test
+    public void testGoodBatch() throws Exception {
+    	String[][] params = new String[][] { { "unit", "unit" }, { "test", "test" } };
+    	
+    	callGoodBatch(params);
+    }
+    
+    @Test
+    public void testGoodBatchPmdTrue() throws Exception {
+    	runner = new AsyncQueryRunner(dataSource, true);
+    	String[][] params = new String[][] { { "unit", "unit" }, { "test", "test" } };
+    	
+    	callGoodBatch(params);
+    }
+    
+    @Test
+    public void testGoodBatchDefaultConstructor() throws Exception {
+    	runner = new AsyncQueryRunner();
+    	String[][] params = new String[][] { { "unit", "unit" }, { "test", "test" } };
+    	
+    	callGoodBatch(conn, params);
+    }
+
+    @Test
+    public void testNullParamsBatch() throws Exception {
+    	String[][] params = new String[][] { { null, "unit" }, { "test", null } };
+    	
+    	callGoodBatch(params);
+    }
+    
+
+
+    // helper method for calling batch when an exception is expected
+    private void callBatchWithException(String sql, Object[][] params) throws Exception {
+    	RunnableFuture<int[]> future = null;
+    	boolean caught = false;
+    	
+    	try {
+    		future = runner.batch(sql, params);
+        	verify(stmt, times(2)).addBatch();
+        	verify(stmt, never()).close();	// make sure the statement is still open
+
+        	future.run();
+        	verify(stmt, times(1)).executeBatch();
+        	verify(stmt, times(1)).close();	// make sure the statement is closed
+        	verify(conn, times(1)).close();	// make sure the connection is closed
+        	
+        	future.get();
+    	} catch(SQLException e) {
+    		caught = true;
+    	} catch(ExecutionException e) {
+    		caught = true;
+    	}
+    	
+    	if(!caught)
+    		fail("Exception never thrown, but expected");
+    }
+
+    @Test
+    public void testTooFewParamsBatch() throws Exception {
+    	String[][] params = new String[][] { { "unit" }, { "test" } };
+    	
+    	callBatchWithException("select * from blah where ? = ?", params);
+    }
+
+    @Test
+    public void testTooManyParamsBatch() throws Exception {
+    	String[][] params = new String[][] { { "unit", "unit", "unit" }, { "test", "test", "test" } };
+    	
+    	callBatchWithException("select * from blah where ? = ?", params);
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullConnectionBatch() throws Exception {
+    	String[][] params = new String[][] { { "unit", "unit" }, { "test", "test" } };
+    	
+    	when(meta.getParameterCount()).thenReturn(2);
+    	when(dataSource.getConnection()).thenReturn(null);
+    	
+    	runner.batch("select * from blah where ? = ?", params);
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullSqlBatch() throws Exception {
+    	String[][] params = new String[][] { { "unit", "unit" }, { "test", "test" } };
+    	
+    	when(meta.getParameterCount()).thenReturn(2);
+    	
+    	runner.batch(null, params);
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullParamsArgBatch() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	
+    	runner.batch("select * from blah where ? = ?", null);
+    }
+
+    @Test
+    public void testAddBatchException() throws Exception {
+    	String[][] params = new String[][] { { "unit", "unit" }, { "test", "test" } };
+    	
+    	doThrow(new SQLException()).when(stmt).addBatch();
+    	
+    	callBatchWithException("select * from blah where ? = ?", params);
+    }
+
+    @Test
+    public void testExecuteBatchException() throws Exception {
+    	String[][] params = new String[][] { { "unit", "unit" }, { "test", "test" } };
+    	
+    	doThrow(new SQLException()).when(stmt).executeBatch();
+    	
+    	callBatchWithException("select * from blah where ? = ?", params);
+    }
+
+    
+    //
+    // Query test cases
+    //
+    private void callGoodQuery(Connection conn) throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	RunnableFuture<Object[]> future = runner.query(conn, "select * from blah where ? = ?", handler, "unit", "test");
+    	verify(stmt, never()).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(1)).executeQuery();
+    	verify(results, times(1)).close();
+    	verify(stmt, times(1)).close();	// make sure we closed the statement
+    	verify(conn, times(0)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    	
+    	// call the other variation of query
+    	when(meta.getParameterCount()).thenReturn(0);
+    	future = runner.query(conn, "select * from blah", handler);
+    	verify(stmt, times(1)).close();	// make sure the statement has only been closed once
+
+    	future.run();
+    	verify(stmt, times(2)).executeQuery();
+    	verify(results, times(2)).close();
+    	verify(stmt, times(2)).close();	// make sure we closed the statement
+    	verify(conn, times(0)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    	
+    }
+    
+    private void callGoodQuery() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	RunnableFuture<Object[]> future = runner.query("select * from blah where ? = ?", handler, "unit", "test");
+    	verify(stmt, never()).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(1)).executeQuery();
+    	verify(results, times(1)).close();
+    	verify(stmt, times(1)).close();	// make sure we closed the statement
+    	verify(conn, times(1)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+
+    	// call the other variation of query
+    	when(meta.getParameterCount()).thenReturn(0);
+    	future = runner.query("select * from blah", handler);
+    	verify(stmt, times(1)).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(2)).executeQuery();
+    	verify(results, times(2)).close();
+    	verify(stmt, times(2)).close();	// make sure we closed the statement
+    	verify(conn, times(2)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    }
+    
+    @Test
+    public void testGoodQuery() throws Exception {
+    	callGoodQuery();
+    }
+    
+    @Test
+    public void testGoodQueryPmdTrue() throws Exception {
+    	runner = new AsyncQueryRunner(true);
+    	callGoodQuery(conn);
+    }
+    
+    @Test
+    public void testGoodQueryDefaultConstructor() throws Exception {
+    	runner = new AsyncQueryRunner();
+    	callGoodQuery(conn);
+    }
+    
+
+    // helper method for calling batch when an exception is expected
+    private void callQueryWithException(Object... params) throws Exception {
+    	RunnableFuture<Object[]> future = null;
+    	boolean caught = false;
+    	
+    	try {
+        	when(meta.getParameterCount()).thenReturn(2);
+    		future = runner.query("select * from blah where ? = ?", handler, params);
+        	verify(stmt, never()).close();	// make sure the statement is still open
+
+        	future.run();
+        	verify(stmt, times(1)).executeQuery();
+        	verify(results, times(1)).close();
+        	verify(stmt, times(1)).close();	// make sure we closed the statement
+        	verify(conn, times(1)).close();	// make sure we closed the connection
+        	
+        	future.get();
+    	} catch(SQLException e) {
+    		caught = true;
+    	} catch(ExecutionException e) {
+    		caught = true;
+    	}
+    	
+    	if(!caught)
+    		fail("Exception never thrown, but expected");
+    }
+
+    @Test
+    public void testNoParamsQuery() throws Exception {
+    	callQueryWithException();
+    }
+    
+    @Test
+    public void testTooFewParamsQuery() throws Exception {
+    	callQueryWithException("unit");
+    }
+
+    @Test
+    public void testTooManyParamsQuery() throws Exception {
+    	callQueryWithException("unit", "test", "fail");
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullConnectionQuery() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	when(dataSource.getConnection()).thenReturn(null);
+    	
+    	runner.query("select * from blah where ? = ?", handler, "unit", "test");
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullSqlQuery() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	
+    	runner.query(null, handler);
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullHandlerQuery() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	
+    	runner.query("select * from blah where ? = ?", null);
+    }
+
+    @Test
+    public void testExecuteQueryException() throws Exception {
+    	doThrow(new SQLException()).when(stmt).executeQuery();
+    	
+    	callQueryWithException(handler, "unit", "test");
+    }
+
+
+    //
+    // Update test cases
+    //
+    private void callGoodUpdate(Connection conn) throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	RunnableFuture<Integer> future = runner.update(conn, "update blah set ? = ?", "unit", "test");
+    	verify(stmt, never()).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(1)).executeUpdate();
+    	verify(stmt, times(1)).close();	// make sure we closed the statement
+    	verify(conn, times(0)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    	
+    	// call the other variation
+    	when(meta.getParameterCount()).thenReturn(0);
+    	future = runner.update(conn, "update blah set unit = test");
+    	verify(stmt, times(1)).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(2)).executeUpdate();
+    	verify(stmt, times(2)).close();	// make sure we closed the statement
+    	verify(conn, times(0)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+
+    	// call the other variation
+    	when(meta.getParameterCount()).thenReturn(1);
+    	future = runner.update(conn, "update blah set unit = ?", "test");
+    	verify(stmt, times(2)).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(3)).executeUpdate();
+    	verify(stmt, times(3)).close();	// make sure we closed the statement
+    	verify(conn, times(0)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    }
+    
+    private void callGoodUpdate() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	RunnableFuture<Integer> future = runner.update("update blah set ? = ?", "unit", "test");
+    	verify(stmt, never()).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(1)).executeUpdate();
+    	verify(stmt, times(1)).close();	// make sure we closed the statement
+    	verify(conn, times(1)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+
+    	// call the other variation
+    	when(meta.getParameterCount()).thenReturn(0);
+    	future = runner.update("update blah set unit = test");
+    	verify(stmt, times(1)).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(2)).executeUpdate();
+    	verify(stmt, times(2)).close();	// make sure we closed the statement
+    	verify(conn, times(2)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+
+    	// call the other variation
+    	when(meta.getParameterCount()).thenReturn(1);
+    	future = runner.update("update blah set unit = ?", "test");
+    	verify(stmt, times(2)).close();	// make sure the statement is still open
+
+    	future.run();
+    	verify(stmt, times(3)).executeUpdate();
+    	verify(stmt, times(3)).close();	// make sure we closed the statement
+    	verify(conn, times(3)).close();	// make sure we closed the connection
+
+    	future.get(); // make sure an exception wasn't thrown
+    }
+    
+    @Test
+    public void testGoodUpdate() throws Exception {
+    	callGoodUpdate();
+    }
+    
+    @Test
+    public void testGoodUpdatePmdTrue() throws Exception {
+    	runner = new AsyncQueryRunner(true);
+    	callGoodUpdate(conn);
+    }
+
+    @Test
+    public void testGoodUpdateDefaultConstructor() throws Exception {
+    	runner = new AsyncQueryRunner();
+    	callGoodUpdate(conn);
+    }
+
+    // helper method for calling batch when an exception is expected
+    private void callUpdateWithException(Object... params) throws Exception {
+    	RunnableFuture<Integer> future = null;
+    	boolean caught = false;
+    	
+    	try {
+    		when(meta.getParameterCount()).thenReturn(2);
+    		future = runner.update("select * from blah where ? = ?", params);
+        	verify(stmt, never()).close();	// make sure the statement is still open
+
+        	future.run();
+        	verify(stmt, times(1)).executeUpdate();
+        	verify(stmt, times(1)).close();	// make sure we closed the statement
+        	verify(conn, times(1)).close();	// make sure we closed the connection
+
+        	future.get();
+    	} catch(SQLException e) {
+    		caught = true;
+    	} catch(ExecutionException e) {
+    		caught = true;
+    	}
+    	
+    	if(!caught)
+    		fail("Exception never thrown, but expected");
+    }
+
+    @Test
+    public void testNoParamsUpdate() throws Exception {
+    	callUpdateWithException();
+    }
+
+    @Test
+    public void testTooFewParamsUpdate() throws Exception {
+    	callUpdateWithException("unit");
+    }
+
+    @Test
+    public void testTooManyParamsUpdate() throws Exception {
+    	callUpdateWithException("unit", "test", "fail");
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullConnectionUpdate() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	when(dataSource.getConnection()).thenReturn(null);
+    	
+    	runner.update("select * from blah where ? = ?", "unit", "test");
+    }
+
+    @Test(expected=SQLException.class)
+    public void testNullSqlUpdate() throws Exception {
+    	when(meta.getParameterCount()).thenReturn(2);
+    	
+    	runner.update(null);
+    }
+
+    @Test
+    public void testExecuteUpdateException() throws Exception {
+    	doThrow(new SQLException()).when(stmt).executeUpdate();
+    	
+    	callUpdateWithException("unit", "test");
+    }
+    
+    //
+    // Random tests
+    //
+    class MyBean {
+    	private int a;
+    	private double b;
+    	private String c;
+    	
+		public int getA() {	return a; }
+		public void setA(int a) { this.a = a; }
+		public double getB() { return b; }
+		public void setB(double b) { this.b = b; }
+		public String getC() { return c; }
+		public void setC(String c) { this.c = c; }
+    }
+    
+    @Test
+    public void testFillStatementWithBean() throws Exception {
+    	MyBean bean = new MyBean();
+    	when(meta.getParameterCount()).thenReturn(3);
+    	runner.fillStatementWithBean(stmt, bean, new String[] { "a", "b", "c" });
+    }
+    
+    @Test(expected=NullPointerException.class)
+    public void testFillStatementWithBeanNullNames() throws Exception {
+    	MyBean bean = new MyBean();
+    	when(meta.getParameterCount()).thenReturn(3);
+    	runner.fillStatementWithBean(stmt, bean, new String[] { "a", "b", null });
+    }
+    
+    @Test(expected=SQLException.class)
+    public void testBadPrepareConnection() throws Exception {
+    	runner = new AsyncQueryRunner();
+    	runner.update("update blah set unit = test");
+    }
+}

Propchange: commons/proper/dbutils/trunk/src/test/org/apache/commons/dbutils/AsyncQueryRunnerTest.java
------------------------------------------------------------------------------
    svn:eol-style = native



Mime
View raw message