[SDNC-5] Rebase sdnc-core
[sdnc/core.git] / dblib / common / src / main / java / org / apache / tomcat / jdbc / pool / interceptor / AbstractQueryReport.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 package org.apache.tomcat.jdbc.pool.interceptor;
39
40 import java.lang.reflect.Constructor;
41 import java.lang.reflect.InvocationHandler;
42 import java.lang.reflect.InvocationTargetException;
43 import java.lang.reflect.Method;
44 import java.lang.reflect.Proxy;
45 import java.sql.CallableStatement;
46 import java.sql.PreparedStatement;
47 import java.sql.SQLException;
48 import java.sql.Statement;
49
50 import org.apache.juli.logging.Log;
51 import org.apache.juli.logging.LogFactory;
52 import org.apache.tomcat.jdbc.pool.JdbcInterceptor;
53 /**
54  * Abstract class that wraps statements and intercepts query executions.
55  *
56  */
57 public abstract class AbstractQueryReport extends AbstractCreateStatementInterceptor {
58     //logger
59     private static final Log log = LogFactory.getLog(AbstractQueryReport.class);
60
61     /**
62      * The threshold in milliseconds. If the query is faster than this, we don't measure it
63      */
64     protected long threshold = 1000; //don't report queries less than this
65
66     /**
67      * the constructors that are used to create statement proxies
68      */
69     protected static final Constructor<?>[] constructors =
70         new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
71
72
73     public AbstractQueryReport() {
74         super();
75     }
76
77     /**
78      * Invoked when prepareStatement has been called and completed.
79      * @param sql - the string used to prepare the statement with
80      * @param time - the time it took to invoke prepare
81      */
82     protected abstract void prepareStatement(String sql, long time);
83
84     /**
85      * Invoked when prepareCall has been called and completed.
86      * @param query - the string used to prepare the statement with
87      * @param time - the time it took to invoke prepare
88      */
89     protected abstract void prepareCall(String query, long time);
90
91     /**
92      * Invoked when a query execution, a call to execute/executeQuery or executeBatch failed.
93      * @param query the query that was executed and failed
94      * @param args the arguments to the execution
95      * @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
96      * @param start the time the query execution started
97      * @param t the exception that happened
98      * @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
99      */
100     protected String reportFailedQuery(String query, Object[] args, final String name, long start, Throwable t) {
101         //extract the query string
102         String sql = (query==null && args!=null &&  args.length>0)?(String)args[0]:query;
103         //if we do batch execution, then we name the query 'batch'
104         if (sql==null && compare(EXECUTE_BATCH,name)) {
105             sql = "batch";
106         }
107         return sql;
108     }
109
110     /**
111      * Invoked when a query execution, a call to execute/executeQuery or executeBatch succeeded and was within the timing threshold
112      * @param query the query that was executed and failed
113      * @param args the arguments to the execution
114      * @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
115      * @param start the time the query execution started
116      * @param delta the time the execution took
117      * @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
118      */
119     protected String reportQuery(String query, Object[] args, final String name, long start, long delta) {
120         //extract the query string
121         String sql = (query==null && args!=null &&  args.length>0)?(String)args[0]:query;
122         //if we do batch execution, then we name the query 'batch'
123         if (sql==null && compare(EXECUTE_BATCH,name)) {
124             sql = "batch";
125         }
126         return sql;
127     }
128
129     /**
130      * Invoked when a query execution, a call to execute/executeQuery or executeBatch succeeded and was exceeded the timing threshold
131      * @param query the query that was executed and failed
132      * @param args the arguments to the execution
133      * @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
134      * @param start the time the query execution started
135      * @param delta the time the execution took
136      * @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
137      */
138     protected String reportSlowQuery(String query, Object[] args, final String name, long start, long delta) {
139         //extract the query string
140         String sql = (query==null && args!=null &&  args.length>0)?(String)args[0]:query;
141         //if we do batch execution, then we name the query 'batch'
142         if (sql==null && compare(EXECUTE_BATCH,name)) {
143             sql = "batch";
144         }
145         return sql;
146     }
147
148     /**
149      * returns the query measure threshold.
150      * This value is in milliseconds. If the query is faster than this threshold than it wont be accounted for
151      * @return the threshold in milliseconds
152      */
153     public long getThreshold() {
154         return threshold;
155     }
156
157     /**
158      * Sets the query measurement threshold. The value is in milliseconds.
159      * If the query goes faster than this threshold it will not be recorded.
160      * @param threshold set to -1 to record every query. Value is in milliseconds.
161      */
162     public void setThreshold(long threshold) {
163         this.threshold = threshold;
164     }
165
166     /**
167      * Creates a constructor for a proxy class, if one doesn't already exist
168      * @param idx - the index of the constructor
169      * @param clazz - the interface that the proxy will implement
170      * @return - returns a constructor used to create new instances
171      * @throws NoSuchMethodException Constructor not found
172      */
173     protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
174         if (constructors[idx]==null) {
175             Class<?> proxyClass = Proxy.getProxyClass(SlowQueryReport.class.getClassLoader(), new Class[] {clazz});
176             constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
177         }
178         return constructors[idx];
179     }
180
181     /**
182      * Creates a statement interceptor to monitor query response times
183      */
184     @Override
185     public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
186         try {
187             Object result = null;
188             String name = method.getName();
189             String sql = null;
190             Constructor<?> constructor = null;
191             if (compare(CREATE_STATEMENT,name)) {
192                 //createStatement
193                 constructor = getConstructor(CREATE_STATEMENT_IDX,Statement.class);
194             }else if (compare(PREPARE_STATEMENT,name)) {
195                 //prepareStatement
196                 sql = (String)args[0];
197                 constructor = getConstructor(PREPARE_STATEMENT_IDX,PreparedStatement.class);
198                 if (sql!=null) {
199                     prepareStatement(sql, time);
200                 }
201             }else if (compare(PREPARE_CALL,name)) {
202                 //prepareCall
203                 sql = (String)args[0];
204                 constructor = getConstructor(PREPARE_CALL_IDX,CallableStatement.class);
205                 prepareCall(sql,time);
206             }else {
207                 //do nothing, might be a future unsupported method
208                 //so we better bail out and let the system continue
209                 return statement;
210             }
211             result = constructor.newInstance(new Object[] { new StatementProxy(statement,sql) });
212             return result;
213         }catch (Exception x) {
214             log.warn("Unable to create statement proxy for slow query report.",x);
215         }
216         return statement;
217     }
218
219
220     /**
221      * Class to measure query execute time
222      *
223      */
224     protected class StatementProxy implements InvocationHandler {
225         protected boolean closed = false;
226         protected Object delegate;
227         protected final String query;
228         public StatementProxy(Object parent, String query) {
229             this.delegate = parent;
230             this.query = query;
231         }
232
233         @Override
234         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
235             //get the name of the method for comparison
236             final String name = method.getName();
237             //was close invoked?
238             boolean close = compare(JdbcInterceptor.CLOSE_VAL,name);
239             //allow close to be called multiple times
240             if (close && closed) return null;
241             //are we calling isClosed?
242             if (compare(JdbcInterceptor.ISCLOSED_VAL,name)) return Boolean.valueOf(closed);
243             //if we are calling anything else, bail out
244             if (closed) throw new SQLException("Statement closed.");
245             boolean process = false;
246             //check to see if we are about to execute a query
247             process = isExecute( method, process);
248             //if we are executing, get the current time
249             long start = (process)?System.currentTimeMillis():0;
250             Object result =  null;
251             try {
252                 //execute the query
253                 result =  method.invoke(delegate,args);
254             }catch (Throwable t) {
255                 reportFailedQuery(query,args,name,start,t);
256                 if (t instanceof InvocationTargetException
257                         && t.getCause() != null) {
258                     throw t.getCause();
259                 } else {
260                     throw t;
261                 }
262             }
263             //measure the time
264             long delta = (process)?(System.currentTimeMillis()-start):Long.MIN_VALUE;
265             //see if we meet the requirements to measure
266             if (delta>threshold) {
267                 try {
268                     //report the slow query
269                     reportSlowQuery(query, args, name, start, delta);
270                 }catch (Exception t) {
271                     if (log.isWarnEnabled()) log.warn("Unable to process slow query",t);
272                 }
273             } else if (process) {
274                 reportQuery(query, args, name, start, delta);
275             }
276             //perform close cleanup
277             if (close) {
278                 closed=true;
279                 delegate = null;
280             }
281             return result;
282         }
283     }
284
285 }