diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java index b1510617e7..69ed482256 100644 --- modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/ConnectionPool.java @@ -328,7 +328,10 @@ public class ConnectionPool { next = next.getNext(); } } - + // setup statement proxy + if (getPoolProperties().getUseStatementFacade()) { + handler = new StatementFacade(handler); + } try { getProxyConstructor(con.getXAConnection() != null); //create the proxy diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java index 120a6f6bdc..d0a41dfdff 100644 --- modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceFactory.java @@ -125,6 +125,8 @@ public class DataSourceFactory implements ObjectFactory { protected static final String PROP_IGNOREEXCEPTIONONPRELOAD = "ignoreExceptionOnPreLoad"; + protected static final String PROP_USESTATEMENTFACADE = "useStatementFacade"; + public static final int UNKNOWN_TRANSACTIONISOLATION = -1; public static final String OBJECT_NAME = "object_name"; @@ -180,7 +182,8 @@ public class DataSourceFactory implements ObjectFactory { PROP_USEDISPOSABLECONNECTIONFACADE, PROP_LOGVALIDATIONERRORS, PROP_PROPAGATEINTERRUPTSTATE, - PROP_IGNOREEXCEPTIONONPRELOAD + PROP_IGNOREEXCEPTIONONPRELOAD, + PROP_USESTATEMENTFACADE }; // -------------------------------------------------- ObjectFactory Methods @@ -528,7 +531,10 @@ public class DataSourceFactory implements ObjectFactory { if (value != null) { poolProperties.setIgnoreExceptionOnPreLoad(Boolean.parseBoolean(value)); } - + value = properties.getProperty(PROP_USESTATEMENTFACADE); + if (value != null) { + poolProperties.setUseStatementFacade(Boolean.parseBoolean(value)); + } return poolProperties; } diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java index b5d0c0a0ad..cea6370a92 100644 --- modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/DataSourceProxy.java @@ -1418,6 +1418,22 @@ public class DataSourceProxy implements PoolConfiguration { getPoolProperties().setIgnoreExceptionOnPreLoad(ignoreExceptionOnPreLoad); } + /** + * {@inheritDoc} + */ + @Override + public boolean getUseStatementFacade() { + return getPoolProperties().getUseStatementFacade(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setUseStatementFacade(boolean useStatementFacade) { + getPoolProperties().setUseStatementFacade(useStatementFacade); + } + public void purge() { try { createPool().purge(); diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java index ae489a038e..b0f7f4f54b 100644 --- modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolConfiguration.java @@ -889,4 +889,18 @@ public interface PoolConfiguration { */ public boolean isIgnoreExceptionOnPreLoad(); + /** + * Set this to true if you wish to wrap statements in order to enable equals() and hashCode() + * methods to be called on the closed statements if any statement proxy is set. + * @param useStatementFacade set to true to wrap statements + */ + public void setUseStatementFacade(boolean useStatementFacade); + + /** + * Returns true if this connection pool is configured to wrap statements in order + * to enable equals() and hashCode() methods to be called on the closed statements if any + * statement proxy is set. + * @return true if the statements are wrapped + */ + public boolean getUseStatementFacade(); } diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java index 2a2131f008..070d1d0a4c 100644 --- modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/PoolProperties.java @@ -28,6 +28,7 @@ import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; + import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -94,7 +95,7 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl private volatile boolean logValidationErrors = false; private volatile boolean propagateInterruptState = false; private volatile boolean ignoreExceptionOnPreLoad = false; - + private volatile boolean useStatementFacade = true; /** * {@inheritDoc} @@ -1290,6 +1291,22 @@ public class PoolProperties implements PoolConfiguration, Cloneable, Serializabl this.ignoreExceptionOnPreLoad = ignoreExceptionOnPreLoad; } + /** + * {@inheritDoc} + */ + @Override + public boolean getUseStatementFacade() { + return useStatementFacade; + } + + /** + * {@inheritDoc} + */ + @Override + public void setUseStatementFacade(boolean useStatementFacade) { + this.useStatementFacade = useStatementFacade; + } + @Override protected Object clone() throws CloneNotSupportedException { // TODO Auto-generated method stub diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/StatementFacade.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/StatementFacade.java new file mode 100644 index 0000000000..f3c8e59d28 --- /dev/null +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/StatementFacade.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package org.apache.tomcat.jdbc.pool; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.Statement; + +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor; + +public class StatementFacade extends AbstractCreateStatementInterceptor { + + private static final Log logger = LogFactory.getLog(StatementFacade.class); + + /** + * the constructors that are used to create statement proxies + */ + protected static final Constructor[] constructors + = new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT]; + + protected StatementFacade(JdbcInterceptor interceptor) { + setUseEquals(interceptor.isUseEquals()); + setNext(interceptor); + } + + @Override + public void closeInvoked() { + // nothing to do + } + + /** + * Creates a statement interceptor to monitor query response times + */ + @Override + public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) { + try { + String name = method.getName(); + Constructor constructor = null; + String sql = null; + if (compare(CREATE_STATEMENT, name)) { + // createStatement + constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class); + } else if (compare(PREPARE_STATEMENT, name)) { + // prepareStatement + constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class); + sql = (String)args[0]; + } else if (compare(PREPARE_CALL, name)) { + // prepareCall + constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class); + sql = (String)args[0]; + } else { + // do nothing + return statement; + } + return constructor.newInstance(new Object[] { new StatementProxy(statement,sql) }); + } catch (Exception x) { + logger.warn("Unable to create statement proxy.", x); + } + return statement; + } + + /** + * Creates a constructor for a proxy class, if one doesn't already exist + * + * @param idx + * - the index of the constructor + * @param clazz + * - the interface that the proxy will implement + * @return - returns a constructor used to create new instances + * @throws NoSuchMethodException Constructor not found + */ + protected Constructor getConstructor(int idx, Class clazz) throws NoSuchMethodException { + if (constructors[idx] == null) { + Class proxyClass = Proxy.getProxyClass(StatementFacade.class.getClassLoader(), + new Class[] { clazz }); + constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class }); + } + return constructors[idx]; + } + + /** + * Class to measure query execute time. + */ + protected class StatementProxy implements InvocationHandler { + protected boolean closed = false; + protected Object delegate; + protected final String query; + public StatementProxy(Object parent, String query) { + this.delegate = parent; + this.query = query; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (compare(TOSTRING_VAL,method)) { + return toString(); + } + if (compare(EQUALS_VAL, method)) { + return Boolean.valueOf( + this.equals(Proxy.getInvocationHandler(args[0]))); + } + if (compare(HASHCODE_VAL, method)) { + return Integer.valueOf(this.hashCode()); + } + if (compare(CLOSE_VAL, method)) { + if (delegate == null) return null; + } + if (compare(ISCLOSED_VAL, method)) { + if (delegate == null) return true; + } + + Object result = null; + try { + //invoke next + result = method.invoke(delegate,args); + } catch (Throwable t) { + if (t instanceof InvocationTargetException && t.getCause() != null) { + throw t.getCause(); + } else { + throw t; + } + } + //perform close cleanup + if (compare(CLOSE_VAL, method)) { + delegate = null; + } + return result; + } + + @Override + public int hashCode() { + return System.identityHashCode(this); + } + + @Override + public boolean equals(Object obj) { + return this==obj; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(StatementProxy.class.getName()); + buf.append("[Proxy="); + buf.append(hashCode()); + buf.append("; Query="); + buf.append(query); + buf.append("; Delegate="); + buf.append(delegate); + buf.append("]"); + return buf.toString(); + } + } + +} diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java index 8eb0bfefa0..27928d19b3 100644 --- modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/jmx/ConnectionPool.java @@ -916,6 +916,22 @@ public class ConnectionPool extends NotificationBroadcasterSupport implements Co throw new UnsupportedOperationException(); } + /** + * {@inheritDoc} + */ + @Override + public boolean getUseStatementFacade() { + return getPoolProperties().getUseStatementFacade(); + } + + /** + * {@inheritDoc} + */ + @Override + public void setUseStatementFacade(boolean useStatementFacade) { + getPoolProperties().setUseStatementFacade(useStatementFacade); + } + /** * {@inheritDoc} */ diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml index 1694c80ae7..38b009fa03 100644 --- modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml +++ modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/pool/mbeans-descriptors.xml @@ -330,6 +330,12 @@ is="true" writeable="false"/> + + +
+ + + + 60764: Implement equals() and + hashCode() in the StatementFacade in order to + enable these methods to be called on the closed statements if any + statement proxy is set. This behavior can be changed with + useStatementFacade attribute. (kfujino) + + + +