2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2016 - 2017 AT&T
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
22 * Licensed to the Apache Software Foundation (ASF) under one or more
23 * contributor license agreements. See the NOTICE file distributed with
24 * this work for additional information regarding copyright ownership.
25 * The ASF licenses this file to You under the Apache License, Version 2.0
26 * (the "License"); you may not use this file except in compliance with
27 * the License. You may obtain a copy of the License at
29 * http://www.apache.org/licenses/LICENSE-2.0
31 * Unless required by applicable law or agreed to in writing, software
32 * distributed under the License is distributed on an "AS IS" BASIS,
33 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
34 * See the License for the specific language governing permissions and
35 * limitations under the License.
39 package org.apache.tomcat.jdbc.pool.interceptor;
41 import java.lang.reflect.Constructor;
42 import java.lang.reflect.InvocationHandler;
43 import java.lang.reflect.InvocationTargetException;
44 import java.lang.reflect.Method;
45 import java.lang.reflect.Proxy;
46 import java.sql.CallableStatement;
47 import java.sql.PreparedStatement;
48 import java.sql.ResultSet;
49 import java.sql.SQLException;
50 import java.sql.Statement;
52 import org.apache.juli.logging.Log;
53 import org.apache.juli.logging.LogFactory;
56 * Implementation of <b>JdbcInterceptor</b> that proxies resultSets and statements.
57 * @author Guillermo Fernandes
59 public class StatementDecoratorInterceptor extends AbstractCreateStatementInterceptor {
61 private static final Log logger = LogFactory.getLog(StatementDecoratorInterceptor.class);
63 protected static final String EXECUTE_QUERY = "executeQuery";
64 protected static final String GET_GENERATED_KEYS = "getGeneratedKeys";
65 protected static final String GET_RESULTSET = "getResultSet";
67 protected static final String[] RESULTSET_TYPES = {EXECUTE_QUERY, GET_GENERATED_KEYS, GET_RESULTSET};
70 * the constructors that are used to create statement proxies
72 protected static final Constructor<?>[] constructors = new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
75 * the constructor to create the resultSet proxies
77 protected static Constructor<?> resultSetConstructor = null;
80 public void closeInvoked() {
85 * Creates a constructor for a proxy class, if one doesn't already exist
88 * - the index of the constructor
90 * - the interface that the proxy will implement
91 * @return - returns a constructor used to create new instances
92 * @throws NoSuchMethodException Constructor not found
94 protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
95 if (constructors[idx] == null) {
96 Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
97 new Class[] { clazz });
98 constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
100 return constructors[idx];
103 protected Constructor<?> getResultSetConstructor() throws NoSuchMethodException {
104 if (resultSetConstructor == null) {
105 Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
106 new Class[] { ResultSet.class });
107 resultSetConstructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
109 return resultSetConstructor;
113 * Creates a statement interceptor to monitor query response times
116 public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
118 String name = method.getName();
119 Constructor<?> constructor = null;
121 if (compare(CREATE_STATEMENT, name)) {
123 constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
124 } else if (compare(PREPARE_STATEMENT, name)) {
126 constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
127 sql = (String)args[0];
128 } else if (compare(PREPARE_CALL, name)) {
130 constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
131 sql = (String)args[0];
133 // do nothing, might be a future unsupported method
134 // so we better bail out and let the system continue
137 return createDecorator(proxy, method, args, statement, constructor, sql);
138 } catch (Exception x) {
139 if (x instanceof InvocationTargetException) {
140 Throwable cause = x.getCause();
141 if (cause instanceof ThreadDeath) {
142 throw (ThreadDeath) cause;
144 if (cause instanceof VirtualMachineError) {
145 throw (VirtualMachineError) cause;
148 logger.warn("Unable to create statement proxy for slow query report.", x);
154 * Creates a proxy for a Statement.
156 * @param proxy The proxy object on which the method that triggered
157 * the creation of the statement was called.
158 * @param method The method that was called on the proxy
159 * @param args The arguments passed as part of the method call to
161 * @param statement The statement object that is to be proxied
162 * @param constructor The constructor for the desired proxy
163 * @param sql The sql of of the statement
165 * @return A new proxy for the Statement
166 * @throws InstantiationException Couldn't instantiate object
167 * @throws IllegalAccessException Inaccessible constructor
168 * @throws InvocationTargetException Exception thrown from constructor
170 protected Object createDecorator(Object proxy, Method method, Object[] args,
171 Object statement, Constructor<?> constructor, String sql)
172 throws InstantiationException, IllegalAccessException, InvocationTargetException {
173 Object result = null;
174 StatementProxy<Statement> statementProxy =
175 new StatementProxy<>((Statement)statement,sql);
176 result = constructor.newInstance(new Object[] { statementProxy });
177 statementProxy.setActualProxy(result);
178 statementProxy.setConnection(proxy);
179 statementProxy.setConstructor(constructor);
183 protected boolean isExecuteQuery(String methodName) {
184 return EXECUTE_QUERY.equals(methodName);
187 protected boolean isExecuteQuery(Method method) {
188 return isExecuteQuery(method.getName());
191 protected boolean isResultSet(Method method, boolean process) {
192 return process(RESULTSET_TYPES, method, process);
196 * Class to measure query execute time.
198 protected class StatementProxy<T extends java.sql.Statement> implements InvocationHandler {
200 protected boolean closed = false;
201 protected T delegate;
202 private Object actualProxy;
203 private Object connection;
205 private Constructor<?> constructor;
207 public StatementProxy(T delegate, String sql) {
208 this.delegate = delegate;
211 public T getDelegate() {
212 return this.delegate;
215 public String getSql() {
219 public void setConnection(Object proxy) {
220 this.connection = proxy;
222 public Object getConnection() {
223 return this.connection;
226 public void setActualProxy(Object proxy){
227 this.actualProxy = proxy;
229 public Object getActualProxy() {
230 return this.actualProxy;
234 public Constructor<?> getConstructor() {
237 public void setConstructor(Constructor<?> constructor) {
238 this.constructor = constructor;
240 public void closeInvoked() {
241 if (getDelegate()!=null) {
243 getDelegate().close();
244 }catch (SQLException ignore) {
252 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
253 if (compare(TOSTRING_VAL,method)) {
256 // was close invoked?
257 boolean close = compare(CLOSE_VAL, method);
258 // allow close to be called multiple times
261 // are we calling isClosed?
262 if (compare(ISCLOSED_VAL, method))
263 return Boolean.valueOf(closed);
264 // if we are calling anything else, bail out
266 throw new SQLException("Statement closed.");
267 if (compare(GETCONNECTION_VAL,method)){
270 boolean process = false;
271 process = isResultSet(method, process);
272 // check to see if we are about to execute a query
273 // if we are executing, get the current time
274 Object result = null;
276 // perform close cleanup
281 result = method.invoke(delegate, args);
283 } catch (Throwable t) {
284 if (t instanceof InvocationTargetException
285 && t.getCause() != null) {
291 if (process && result != null) {
292 Constructor<?> cons = getResultSetConstructor();
293 result = cons.newInstance(new Object[]{new ResultSetProxy(actualProxy, result)});
299 public String toString() {
300 StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
301 buf.append("[Proxy=");
302 buf.append(System.identityHashCode(this));
303 buf.append("; Sql=");
304 buf.append(getSql());
305 buf.append("; Delegate=");
306 buf.append(getDelegate());
307 buf.append("; Connection=");
308 buf.append(getConnection());
310 return buf.toString();
314 protected class ResultSetProxy implements InvocationHandler {
317 private Object delegate;
319 public ResultSetProxy(Object st, Object delegate) {
321 this.delegate = delegate;
325 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
326 if (method.getName().equals("getStatement")) {
330 return method.invoke(this.delegate, args);
331 } catch (Throwable t) {
332 if (t instanceof InvocationTargetException
333 && t.getCause() != null) {