2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
19 package org.apache.tomcat.jdbc.pool.interceptor;
21 import java.lang.reflect.Constructor;
22 import java.lang.reflect.InvocationHandler;
23 import java.lang.reflect.InvocationTargetException;
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Proxy;
26 import java.sql.CallableStatement;
27 import java.sql.PreparedStatement;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.sql.Statement;
33 import org.apache.juli.logging.Log;
34 import org.apache.juli.logging.LogFactory;
35 import org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor;
38 * Implementation of <b>JdbcInterceptor</b> that proxies resultSets and statements.
39 * @author Guillermo Fernandes
41 public class StatementDecoratorInterceptor extends AbstractCreateStatementInterceptor {
43 private static final Log logger = LogFactory.getLog(StatementDecoratorInterceptor.class);
45 private static final String[] EXECUTE_QUERY_TYPES = { "executeQuery" };
48 * the constructors that are used to create statement proxies
50 protected static final Constructor<?>[] constructors = new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
53 * the constructor to create the resultSet proxies
55 protected static Constructor<?> resultSetConstructor = null;
58 public void closeInvoked() {
63 * Creates a constructor for a proxy class, if one doesn't already exist
66 * - the index of the constructor
68 * - the interface that the proxy will implement
69 * @return - returns a constructor used to create new instances
70 * @throws NoSuchMethodException
72 protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
73 if (constructors[idx] == null) {
74 Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
75 new Class[] { clazz });
76 constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
78 return constructors[idx];
81 protected Constructor<?> getResultSetConstructor() throws NoSuchMethodException {
82 if (resultSetConstructor == null) {
83 Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
84 new Class[] { ResultSet.class });
85 resultSetConstructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
87 return resultSetConstructor;
91 * Creates a statement interceptor to monitor query response times
94 public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
96 String name = method.getName();
97 Constructor<?> constructor = null;
99 if (compare(CREATE_STATEMENT, name)) {
101 constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
102 } else if (compare(PREPARE_STATEMENT, name)) {
104 constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
105 sql = (String)args[0];
106 } else if (compare(PREPARE_CALL, name)) {
108 constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
109 sql = (String)args[0];
111 // do nothing, might be a future unsupported method
112 // so we better bail out and let the system continue
115 return createDecorator(proxy, method, args, statement, constructor, sql);
116 } catch (Exception x) {
117 logger.warn("Unable to create statement proxy for slow query report.", x);
122 protected Object createDecorator(Object proxy, Method method, Object[] args,
123 Object statement, Constructor<?> constructor, String sql)
124 throws InstantiationException, IllegalAccessException, InvocationTargetException {
125 Object result = null;
126 StatementProxy statementProxy = new StatementProxy<Statement>((Statement)statement,sql);
127 result = constructor.newInstance(new Object[] { statementProxy });
128 statementProxy.setActualProxy(result);
129 statementProxy.setConnection(proxy);
130 statementProxy.setConnection(constructor);
134 protected boolean isExecuteQuery(String methodName) {
135 return EXECUTE_QUERY_TYPES[0].equals(methodName);
138 protected boolean isExecuteQuery(Method method) {
139 return isExecuteQuery(method.getName());
143 * Class to measure query execute time
148 protected class StatementProxy<T extends java.sql.Statement> implements InvocationHandler {
150 protected boolean closed = false;
151 protected T delegate;
152 private Object actualProxy;
153 private Object connection;
155 private Constructor constructor;
157 public StatementProxy(T delegate, String sql) {
158 this.delegate = delegate;
161 public T getDelegate() {
162 return this.delegate;
165 public String getSql() {
169 public void setConnection(Object proxy) {
170 this.connection = proxy;
172 public Object getConnection() {
173 return this.connection;
176 public void setActualProxy(Object proxy){
177 this.actualProxy = proxy;
179 public Object getActualProxy() {
180 return this.actualProxy;
184 public Constructor getConstructor() {
187 public void setConstructor(Constructor constructor) {
188 this.constructor = constructor;
190 public void closeInvoked() {
191 if (getDelegate()!=null) {
193 getDelegate().close();
194 }catch (SQLException ignore) {
201 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
202 if (compare(TOSTRING_VAL,method)) {
205 // was close invoked?
206 boolean close = compare(CLOSE_VAL, method);
207 // allow close to be called multiple times
210 // are we calling isClosed?
211 if (compare(ISCLOSED_VAL, method))
212 return Boolean.valueOf(closed);
213 // if we are calling anything else, bail out
215 throw new SQLException("Statement closed.");
216 if (compare(GETCONNECTION_VAL,method)){
219 boolean process = isExecuteQuery(method);
220 // check to see if we are about to execute a query
221 // if we are executing, get the current time
222 Object result = null;
224 // perform close cleanup
229 result = method.invoke(delegate, args);
231 } catch (Throwable t) {
232 if (t instanceof InvocationTargetException) {
233 InvocationTargetException it = (InvocationTargetException) t;
234 throw it.getCause() != null ? it.getCause() : it;
240 Constructor<?> cons = getResultSetConstructor();
241 result = cons.newInstance(new Object[]{new ResultSetProxy(actualProxy, result)});
246 public String toString() {
247 StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
248 buf.append("[Proxy=");
249 buf.append(System.identityHashCode(this));
250 buf.append("; Sql=");
251 buf.append(getSql());
252 buf.append("; Delegate=");
253 buf.append(getDelegate());
254 buf.append("; Connection=");
255 buf.append(getConnection());
257 return buf.toString();
261 protected class ResultSetProxy implements InvocationHandler {
264 private Object delegate;
266 public ResultSetProxy(Object st, Object delegate) {
268 this.delegate = delegate;
271 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
272 if (method.getName().equals("getStatement")) {
275 return method.invoke(this.delegate, args);