/* * This software is released under a licence similar to the Apache Software Licence. * See org.logicalcobwebs.proxool.package.html for details. * The latest version is available at http://proxool.sourceforge.net */ package org.logicalcobwebs.proxool; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Statement; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.logicalcobwebs.cglib.proxy.MethodInterceptor; import org.logicalcobwebs.cglib.proxy.MethodProxy; import org.logicalcobwebs.proxool.proxy.InvokerFacade; import org.quickbundle.config.RmConfig; import org.quickbundle.project.listener.RmRequestMonitor; /** * Delegates to Statement for all calls. But also, for all execute methods, it * checks the SQLException and compares it to the fatalSqlException list in the * ConnectionPoolDefinition. If it detects a fatal exception it will destroy the * Connection so that it isn't used again. * @version $Revision: 1.32 $, $Date: 2006/03/03 09:58:26 $ * @author billhorsman * @author $Author: billhorsman $ (current maintainer) */ class ProxyStatement extends AbstractProxyStatement implements MethodInterceptor { private static final Log LOG = LogFactory.getLog(ProxyStatement.class); private static final String EXECUTE_FRAGMENT = "execute"; private static final String EXECUTE_BATCH_METHOD = "executeBatch"; private static final String ADD_BATCH_METHOD = "addBatch"; private static final String EQUALS_METHOD = "equals"; private static final String CLOSE_METHOD = "close"; private static final String GET_CONNECTION_METHOD = "getConnection"; private static final String FINALIZE_METHOD = "finalize"; private static final String SET_NULL_METHOD = "setNull"; private static final String SET_PREFIX = "set"; //qb-rm protected int sqlLogCount = 0; public ProxyStatement(Statement statement, ConnectionPool connectionPool, ProxyConnectionIF proxyConnection, String sqlStatement) { super(statement, connectionPool, proxyConnection, sqlStatement); } public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return invoke(proxy, method, args); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; long startTime = System.currentTimeMillis(); final int argCount = args != null ? args.length : 0; Method concreteMethod = InvokerFacade.getConcreteMethod(getStatement().getClass(), method); // This gets called /before/ the method has run if (concreteMethod.getName().equals(ADD_BATCH_METHOD)) { // If we have just added a batch call then we need to update the sql log if (argCount > 0 && args[0] instanceof String) { setSqlStatementIfNull((String) args[0]); } //qb-rm if(sqlLogCount < RmConfig.getSingleton().getMaxLogSqlBatchSize()) { appendToSqlLog(); } sqlLogCount ++; long[] sqlCount = RmRequestMonitor.tlSqlCount.get(); if(sqlCount != null) { sqlCount[2] ++; } } else if (concreteMethod.getName().equals(EXECUTE_BATCH_METHOD)) { // executing a batch should do a trace //qb-rm if(sqlLogCount > RmConfig.getSingleton().getMaxLogSqlBatchSize()) { getSqlLog().append(";[batchSize:" + sqlLogCount + "]"); } long[] sqlCount = RmRequestMonitor.tlSqlCount.get(); if(sqlCount != null) { sqlCount[1] ++; } startExecute(); } else if (concreteMethod.getName().startsWith(EXECUTE_FRAGMENT)) { // executing should update the log and do a trace if (argCount > 0 && args[0] instanceof String) { setSqlStatementIfNull((String) args[0]); } long[] sqlCount = RmRequestMonitor.tlSqlCount.get(); if(sqlCount != null) { sqlCount[0] ++; } appendToSqlLog(); startExecute(); } // We need to remember an exceptions that get thrown so that we can optionally // pass them to the onExecute() call below Exception exception = null; try { if (concreteMethod.getName().equals(EQUALS_METHOD) && argCount == 1) { result = (equals(args[0])) ? Boolean.TRUE : Boolean.FALSE; } else if (concreteMethod.getName().equals(CLOSE_METHOD) && argCount == 0) { close(); } else if (concreteMethod.getName().equals(GET_CONNECTION_METHOD) && argCount == 0) { result = getConnection(); } else if (concreteMethod.getName().equals(FINALIZE_METHOD) && argCount == 0) { finalize(); } else { try { result = concreteMethod.invoke(getStatement(), args); } catch (IllegalAccessException e) { // This is probably because we are trying to access a non-public concrete class. But don't worry, // we can always use the proxy supplied method. This will only fail if we try to use an injectable // method on a method in a class that isn't public and for a method that isn't declared in an interface - // but if that is the case then that method is inaccessible by any means (even by bypassing Proxool and // using the vendor's driver directly). LOG.debug("Ignoring IllegalAccessException whilst invoking the " + concreteMethod + " concrete method and trying the " + method + " method directly."); // By overriding the method cached in the InvokerFacade we ensure that we only log this message once, and // we speed up subsequent usages by not calling the method that fails first. InvokerFacade.overrideConcreteMethod(getStatement().getClass(), method, method); result = method.invoke(getStatement(), args); } } // We only dump sql calls if we are in verbose mode and debug is enabled if (isTrace()) { try { // What sort of method is it if (concreteMethod.getName().equals(SET_NULL_METHOD) && argCount > 0 && args[0] instanceof Integer) { int index = ((Integer) args[0]).intValue(); putParameter(index, null); } else if (concreteMethod.getName().startsWith(SET_PREFIX) && argCount > 1 && args[0] instanceof Integer) { int index = ((Integer) args[0]).intValue(); putParameter(index, args[1]); } } catch (Exception e) { // We don't want an error during dump screwing up the transaction LOG.error("Ignoring error during dump", e); } } } catch (InvocationTargetException e) { if (e.getTargetException() instanceof Exception) { exception = (Exception) e.getTargetException(); } else { exception = e; } if (testException(e.getTargetException())) { // This is really a fatal one FatalSqlExceptionHelper.throwFatalSQLException(getConnectionPool().getDefinition().getFatalSqlExceptionWrapper(), e.getTargetException()); } throw e.getTargetException(); } catch (Exception e) { exception = e; if (testException(e)) { // This is really a fatal one FatalSqlExceptionHelper.throwFatalSQLException(getConnectionPool().getDefinition().getFatalSqlExceptionWrapper(), e); } throw e; } finally { // This gets called /after/ the method has run if (concreteMethod.getName().equals(EXECUTE_BATCH_METHOD) || concreteMethod.getName().startsWith(EXECUTE_FRAGMENT)) { trace(startTime, exception); } } return result; } } /* Revision history: $Log: ProxyStatement.java,v $ Revision 1.32 2006/03/03 09:58:26 billhorsman Fix for statement.getConnection(). See bug 1149834. Revision 1.31 2006/01/18 14:40:02 billhorsman Unbundled Jakarta's Commons Logging. Revision 1.30 2006/01/16 23:09:28 billhorsman Call concrete finalize() method Revision 1.29 2005/10/07 08:15:00 billhorsman Update sqlCalls /before/ we execute so that we can see what slow calls are doing before they finish. Revision 1.28 2004/07/13 21:06:18 billhorsman Fix problem using injectable interfaces on methods that are declared in non-public classes. Revision 1.27 2004/06/17 21:56:53 billhorsman Use MethodMapper for concrete methods. Revision 1.26 2004/06/02 20:48:14 billhorsman Dropped obsolete InvocationHandler reference. Revision 1.25 2003/12/12 19:29:47 billhorsman Now uses Cglib 2.0 Revision 1.24 2003/10/19 09:50:33 billhorsman Drill down into InvocationTargetException during execution debug. Revision 1.23 2003/10/18 20:44:48 billhorsman Better SQL logging (embed parameter values within SQL call) and works properly with batched statements now. Revision 1.22 2003/09/30 18:39:08 billhorsman New test-before-use, test-after-use and fatal-sql-exception-wrapper-class properties. Revision 1.21 2003/09/29 17:48:49 billhorsman New fatal-sql-exception-wrapper-class allows you to define what exception is used as a wrapper. This means that you can make it a RuntimeException if you need to. Revision 1.20 2003/09/10 22:21:04 chr32 Removing > jdk 1.2 dependencies. Revision 1.19 2003/09/05 17:01:00 billhorsman Trap and throw FatalSQLExceptions. Revision 1.18 2003/03/03 11:11:58 billhorsman fixed licence Revision 1.17 2003/02/13 17:06:42 billhorsman allow for sqlStatement in execute() method Revision 1.16 2003/02/06 17:41:04 billhorsman now uses imported logging Revision 1.15 2003/01/31 16:53:19 billhorsman checkstyle Revision 1.14 2003/01/28 11:47:08 billhorsman new isTrace() and made close() public Revision 1.13 2003/01/27 18:26:40 billhorsman refactoring of ProxyConnection and ProxyStatement to make it easier to write JDK 1.2 patch Revision 1.12 2002/12/19 00:08:36 billhorsman automatic closure of statements when a connection is closed Revision 1.11 2002/12/16 10:57:48 billhorsman add getDelegateStatement to allow access to the delegate JDBC driver's Statement Revision 1.10 2002/12/03 12:24:00 billhorsman fixed fatal sql exception Revision 1.9 2002/11/13 18:22:04 billhorsman fix for trace output Revision 1.8 2002/11/13 12:32:38 billhorsman now correctly logs trace messages even with verbose off Revision 1.7 2002/11/09 15:57:33 billhorsman finished off execute logging and listening Revision 1.6 2002/10/29 23:20:55 billhorsman logs execute time when debug is enabled and verbose is true Revision 1.5 2002/10/28 19:28:25 billhorsman checkstyle Revision 1.4 2002/10/28 08:20:23 billhorsman draft sql dump stuff Revision 1.3 2002/10/25 15:59:32 billhorsman made non-public where possible Revision 1.2 2002/10/17 15:29:18 billhorsman fixes so that equals() works Revision 1.1.1.1 2002/09/13 08:13:30 billhorsman new Revision 1.8 2002/08/24 19:57:15 billhorsman checkstyle changes Revision 1.7 2002/08/24 19:42:26 billhorsman new proxy stuff to work with JDK 1.4 Revision 1.6 2002/07/10 16:14:47 billhorsman widespread layout changes and move constants into ProxoolConstants Revision 1.5 2002/07/02 11:19:08 billhorsman layout code and imports Revision 1.4 2002/06/28 11:19:47 billhorsman improved doc */