[SDNC-5] Rebase sdnc-core
[sdnc/core.git] / dblib / common / src / main / java / org / apache / tomcat / jdbc / pool / interceptor / StatementDecoratorInterceptor.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * openecomp
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 /*
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
28  *
29  *      http://www.apache.org/licenses/LICENSE-2.0
30  *
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.
36  */
37
38
39 package org.apache.tomcat.jdbc.pool.interceptor;
40
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;
51
52 import org.apache.juli.logging.Log;
53 import org.apache.juli.logging.LogFactory;
54
55 /**
56  * Implementation of <b>JdbcInterceptor</b> that proxies resultSets and statements.
57  * @author Guillermo Fernandes
58  */
59 public class StatementDecoratorInterceptor extends AbstractCreateStatementInterceptor {
60
61     private static final Log logger = LogFactory.getLog(StatementDecoratorInterceptor.class);
62
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";
66
67     protected static final String[] RESULTSET_TYPES = {EXECUTE_QUERY, GET_GENERATED_KEYS, GET_RESULTSET};
68
69     /**
70      * the constructors that are used to create statement proxies
71      */
72     protected static final Constructor<?>[] constructors = new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
73
74     /**
75      * the constructor to create the resultSet proxies
76      */
77     protected static Constructor<?> resultSetConstructor = null;
78
79     @Override
80     public void closeInvoked() {
81         // nothing to do
82     }
83
84     /**
85      * Creates a constructor for a proxy class, if one doesn't already exist
86      *
87      * @param idx
88      *            - the index of the constructor
89      * @param clazz
90      *            - the interface that the proxy will implement
91      * @return - returns a constructor used to create new instances
92      * @throws NoSuchMethodException Constructor not found
93      */
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 });
99         }
100         return constructors[idx];
101     }
102
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 });
108         }
109         return resultSetConstructor;
110     }
111
112     /**
113      * Creates a statement interceptor to monitor query response times
114      */
115     @Override
116     public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
117         try {
118             String name = method.getName();
119             Constructor<?> constructor = null;
120             String sql = null;
121             if (compare(CREATE_STATEMENT, name)) {
122                 // createStatement
123                 constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
124             } else if (compare(PREPARE_STATEMENT, name)) {
125                 // prepareStatement
126                 constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
127                 sql = (String)args[0];
128             } else if (compare(PREPARE_CALL, name)) {
129                 // prepareCall
130                 constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
131                 sql = (String)args[0];
132             } else {
133                 // do nothing, might be a future unsupported method
134                 // so we better bail out and let the system continue
135                 return statement;
136             }
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;
143                 }
144                 if (cause instanceof VirtualMachineError) {
145                     throw (VirtualMachineError) cause;
146                 }
147             }
148             logger.warn("Unable to create statement proxy for slow query report.", x);
149         }
150         return statement;
151     }
152
153     /**
154      * Creates a proxy for a Statement.
155      *
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
160      *                          the proxy
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
164      *
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
169      */
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);
180         return result;
181     }
182
183     protected boolean isExecuteQuery(String methodName) {
184         return EXECUTE_QUERY.equals(methodName);
185     }
186
187     protected boolean isExecuteQuery(Method method) {
188         return isExecuteQuery(method.getName());
189     }
190
191     protected boolean isResultSet(Method method, boolean process) {
192         return process(RESULTSET_TYPES, method, process);
193     }
194
195     /**
196      * Class to measure query execute time.
197      */
198     protected class StatementProxy<T extends java.sql.Statement> implements InvocationHandler {
199
200         protected boolean closed = false;
201         protected T delegate;
202         private Object actualProxy;
203         private Object connection;
204         private String sql;
205         private Constructor<?> constructor;
206
207         public StatementProxy(T delegate, String sql) {
208             this.delegate = delegate;
209             this.sql = sql;
210         }
211         public T getDelegate() {
212             return this.delegate;
213         }
214
215         public String getSql() {
216             return sql;
217         }
218
219         public void setConnection(Object proxy) {
220             this.connection = proxy;
221         }
222         public Object getConnection() {
223             return this.connection;
224         }
225
226         public void setActualProxy(Object proxy){
227             this.actualProxy = proxy;
228         }
229         public Object getActualProxy() {
230             return this.actualProxy;
231         }
232
233
234         public Constructor<?> getConstructor() {
235             return constructor;
236         }
237         public void setConstructor(Constructor<?> constructor) {
238             this.constructor = constructor;
239         }
240         public void closeInvoked() {
241             if (getDelegate()!=null) {
242                 try {
243                     getDelegate().close();
244                 }catch (SQLException ignore) {
245                 }
246             }
247             closed = true;
248             delegate = null;
249         }
250
251         @Override
252         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
253             if (compare(TOSTRING_VAL,method)) {
254                 return toString();
255             }
256             // was close invoked?
257             boolean close = compare(CLOSE_VAL, method);
258             // allow close to be called multiple times
259             if (close && closed)
260                 return null;
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
265             if (closed)
266                 throw new SQLException("Statement closed.");
267             if (compare(GETCONNECTION_VAL,method)){
268                 return connection;
269             }
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;
275             try {
276                 // perform close cleanup
277                 if (close) {
278                     closeInvoked();
279                 } else {
280                     // execute the query
281                     result = method.invoke(delegate, args);
282                 }
283             } catch (Throwable t) {
284                 if (t instanceof InvocationTargetException
285                         && t.getCause() != null) {
286                     throw t.getCause();
287                 } else {
288                     throw t;
289                 }
290             }
291             if (process && result != null) {
292                 Constructor<?> cons = getResultSetConstructor();
293                 result = cons.newInstance(new Object[]{new ResultSetProxy(actualProxy, result)});
294             }
295             return result;
296         }
297
298         @Override
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());
309             buf.append("]");
310             return buf.toString();
311         }
312     }
313
314     protected class ResultSetProxy implements InvocationHandler {
315
316         private Object st;
317         private Object delegate;
318
319         public ResultSetProxy(Object st, Object delegate) {
320             this.st = st;
321             this.delegate = delegate;
322         }
323
324         @Override
325         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
326             if (method.getName().equals("getStatement")) {
327                 return this.st;
328             } else {
329                 try {
330                     return method.invoke(this.delegate, args);
331                 } catch (Throwable t) {
332                     if (t instanceof InvocationTargetException
333                             && t.getCause() != null) {
334                         throw t.getCause();
335                     } else {
336                         throw t;
337                     }
338                 }
339             }
340         }
341     }
342 }