c48474c696fc4662ff2212823fddb4ea15b1de1c
[tomcat7.0] /
1 /*
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
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
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.
16  */
17  
18  
19 package org.apache.tomcat.jdbc.pool.interceptor;
20
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;
31
32
33 import org.apache.juli.logging.Log;
34 import org.apache.juli.logging.LogFactory;
35 import org.apache.tomcat.jdbc.pool.interceptor.AbstractCreateStatementInterceptor;
36
37 /**
38  * Implementation of <b>JdbcInterceptor</b> that proxies resultSets and statements.
39  * @author Guillermo Fernandes
40  */
41 public class StatementDecoratorInterceptor extends AbstractCreateStatementInterceptor {
42
43     private static final Log logger = LogFactory.getLog(StatementDecoratorInterceptor.class);
44
45     private static final String[] EXECUTE_QUERY_TYPES = { "executeQuery" };
46
47     /**
48      * the constructors that are used to create statement proxies
49      */
50     protected static final Constructor<?>[] constructors = new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
51
52     /**
53      * the constructor to create the resultSet proxies
54      */
55     protected static Constructor<?> resultSetConstructor = null;
56
57     @Override
58     public void closeInvoked() {
59         // nothing to do
60     }
61
62     /**
63      * Creates a constructor for a proxy class, if one doesn't already exist
64      * 
65      * @param idx
66      *            - the index of the constructor
67      * @param clazz
68      *            - the interface that the proxy will implement
69      * @return - returns a constructor used to create new instances
70      * @throws NoSuchMethodException
71      */
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 });
77         }
78         return constructors[idx];
79     }
80
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 });
86         }
87         return resultSetConstructor;
88     }
89
90     /**
91      * Creates a statement interceptor to monitor query response times
92      */
93     @Override
94     public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
95         try {
96             String name = method.getName();
97             Constructor<?> constructor = null;
98             String sql = null;
99             if (compare(CREATE_STATEMENT, name)) {
100                 // createStatement
101                 constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
102             } else if (compare(PREPARE_STATEMENT, name)) {
103                 // prepareStatement
104                 constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
105                 sql = (String)args[0];
106             } else if (compare(PREPARE_CALL, name)) {
107                 // prepareCall
108                 constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
109                 sql = (String)args[0];
110             } else {
111                 // do nothing, might be a future unsupported method
112                 // so we better bail out and let the system continue
113                 return statement;
114             }
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);
118         }
119         return statement;
120     }
121
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.setConstructor(constructor);
131         return result;
132     }
133
134     protected boolean isExecuteQuery(String methodName) {
135         return EXECUTE_QUERY_TYPES[0].equals(methodName);
136     }
137
138     protected boolean isExecuteQuery(Method method) {
139         return isExecuteQuery(method.getName());
140     }
141
142     /**
143      * Class to measure query execute time
144      * 
145      * @author fhanik
146      * 
147      */
148     protected class StatementProxy<T extends java.sql.Statement> implements InvocationHandler {
149         
150         protected boolean closed = false;
151         protected T delegate;
152         private Object actualProxy;
153         private Object connection;
154         private String sql;
155         private Constructor constructor;
156
157         public StatementProxy(T delegate, String sql) {
158             this.delegate = delegate;
159             this.sql = sql;
160         }
161         public T getDelegate() {
162             return this.delegate;
163         }
164         
165         public String getSql() {
166             return sql;
167         }
168
169         public void setConnection(Object proxy) {
170             this.connection = proxy;            
171         }
172         public Object getConnection() {
173             return this.connection;
174         }
175
176         public void setActualProxy(Object proxy){
177             this.actualProxy = proxy;
178         }
179         public Object getActualProxy() {
180             return this.actualProxy;
181         }
182         
183         
184         public Constructor getConstructor() {
185             return constructor;
186         }
187         public void setConstructor(Constructor constructor) {
188             this.constructor = constructor;
189         }
190         public void closeInvoked() {
191             if (getDelegate()!=null) {
192                 try {
193                     getDelegate().close();
194                 }catch (SQLException ignore) {
195                 }
196             }
197             closed = true;
198             delegate = null;
199         }
200         
201         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
202             if (compare(TOSTRING_VAL,method)) {
203                 return toString();
204             }
205             // was close invoked?
206             boolean close = compare(CLOSE_VAL, method);
207             // allow close to be called multiple times
208             if (close && closed)
209                 return null;
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
214             if (closed)
215                 throw new SQLException("Statement closed.");
216             if (compare(GETCONNECTION_VAL,method)){
217                 return connection;
218             }
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;
223             try {
224                 // perform close cleanup
225                 if (close) {
226                     closeInvoked();
227                 } else {
228                     // execute the query
229                     result = method.invoke(delegate, args);
230                 }
231             } catch (Throwable t) {
232                 if (t instanceof InvocationTargetException) {
233                     InvocationTargetException it = (InvocationTargetException) t;
234                     throw it.getCause() != null ? it.getCause() : it;
235                 } else {
236                     throw t;
237                 }
238             }
239             if (process){
240                 Constructor<?> cons = getResultSetConstructor();
241                 result = cons.newInstance(new Object[]{new ResultSetProxy(actualProxy, result)});
242             }
243             return result;
244         }
245         
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());
256             buf.append("]");
257             return buf.toString();
258         }
259     }
260
261     protected class ResultSetProxy implements InvocationHandler {
262
263         private Object st;
264         private Object delegate;
265
266         public ResultSetProxy(Object st, Object delegate) {
267             this.st = st;
268             this.delegate = delegate;
269         }
270
271         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
272             if (method.getName().equals("getStatement")) {
273                 return this.st;
274             } else {
275                 return method.invoke(this.delegate, args);
276             }
277         }
278     }
279 }