[SDNC-5] Rebase sdnc-core
[sdnc/core.git] / dblib / common / src / main / java / org / apache / tomcat / jdbc / pool / interceptor / AbstractQueryReport.java
diff --git a/dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractQueryReport.java b/dblib/common/src/main/java/org/apache/tomcat/jdbc/pool/interceptor/AbstractQueryReport.java
new file mode 100644 (file)
index 0000000..374969d
--- /dev/null
@@ -0,0 +1,285 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * openecomp
+ * ================================================================================
+ * Copyright (C) 2016 - 2017 AT&T
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.tomcat.jdbc.pool.interceptor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jdbc.pool.JdbcInterceptor;
+/**
+ * Abstract class that wraps statements and intercepts query executions.
+ *
+ */
+public abstract class AbstractQueryReport extends AbstractCreateStatementInterceptor {
+    //logger
+    private static final Log log = LogFactory.getLog(AbstractQueryReport.class);
+
+    /**
+     * The threshold in milliseconds. If the query is faster than this, we don't measure it
+     */
+    protected long threshold = 1000; //don't report queries less than this
+
+    /**
+     * the constructors that are used to create statement proxies
+     */
+    protected static final Constructor<?>[] constructors =
+        new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
+
+
+    public AbstractQueryReport() {
+        super();
+    }
+
+    /**
+     * Invoked when prepareStatement has been called and completed.
+     * @param sql - the string used to prepare the statement with
+     * @param time - the time it took to invoke prepare
+     */
+    protected abstract void prepareStatement(String sql, long time);
+
+    /**
+     * Invoked when prepareCall has been called and completed.
+     * @param query - the string used to prepare the statement with
+     * @param time - the time it took to invoke prepare
+     */
+    protected abstract void prepareCall(String query, long time);
+
+    /**
+     * Invoked when a query execution, a call to execute/executeQuery or executeBatch failed.
+     * @param query the query that was executed and failed
+     * @param args the arguments to the execution
+     * @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
+     * @param start the time the query execution started
+     * @param t the exception that happened
+     * @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
+     */
+    protected String reportFailedQuery(String query, Object[] args, final String name, long start, Throwable t) {
+        //extract the query string
+        String sql = (query==null && args!=null &&  args.length>0)?(String)args[0]:query;
+        //if we do batch execution, then we name the query 'batch'
+        if (sql==null && compare(EXECUTE_BATCH,name)) {
+            sql = "batch";
+        }
+        return sql;
+    }
+
+    /**
+     * Invoked when a query execution, a call to execute/executeQuery or executeBatch succeeded and was within the timing threshold
+     * @param query the query that was executed and failed
+     * @param args the arguments to the execution
+     * @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
+     * @param start the time the query execution started
+     * @param delta the time the execution took
+     * @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
+     */
+    protected String reportQuery(String query, Object[] args, final String name, long start, long delta) {
+        //extract the query string
+        String sql = (query==null && args!=null &&  args.length>0)?(String)args[0]:query;
+        //if we do batch execution, then we name the query 'batch'
+        if (sql==null && compare(EXECUTE_BATCH,name)) {
+            sql = "batch";
+        }
+        return sql;
+    }
+
+    /**
+     * Invoked when a query execution, a call to execute/executeQuery or executeBatch succeeded and was exceeded the timing threshold
+     * @param query the query that was executed and failed
+     * @param args the arguments to the execution
+     * @param name the name of the method used to execute {@link AbstractCreateStatementInterceptor#isExecute(Method, boolean)}
+     * @param start the time the query execution started
+     * @param delta the time the execution took
+     * @return - the SQL that was executed or the string &quot;batch&quot; if it was a batch execution
+     */
+    protected String reportSlowQuery(String query, Object[] args, final String name, long start, long delta) {
+        //extract the query string
+        String sql = (query==null && args!=null &&  args.length>0)?(String)args[0]:query;
+        //if we do batch execution, then we name the query 'batch'
+        if (sql==null && compare(EXECUTE_BATCH,name)) {
+            sql = "batch";
+        }
+        return sql;
+    }
+
+    /**
+     * returns the query measure threshold.
+     * This value is in milliseconds. If the query is faster than this threshold than it wont be accounted for
+     * @return the threshold in milliseconds
+     */
+    public long getThreshold() {
+        return threshold;
+    }
+
+    /**
+     * Sets the query measurement threshold. The value is in milliseconds.
+     * If the query goes faster than this threshold it will not be recorded.
+     * @param threshold set to -1 to record every query. Value is in milliseconds.
+     */
+    public void setThreshold(long threshold) {
+        this.threshold = threshold;
+    }
+
+    /**
+     * Creates a constructor for a proxy class, if one doesn't already exist
+     * @param idx - the index of the constructor
+     * @param clazz - the interface that the proxy will implement
+     * @return - returns a constructor used to create new instances
+     * @throws NoSuchMethodException Constructor not found
+     */
+    protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
+        if (constructors[idx]==null) {
+            Class<?> proxyClass = Proxy.getProxyClass(SlowQueryReport.class.getClassLoader(), new Class[] {clazz});
+            constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
+        }
+        return constructors[idx];
+    }
+
+    /**
+     * Creates a statement interceptor to monitor query response times
+     */
+    @Override
+    public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
+        try {
+            Object result = null;
+            String name = method.getName();
+            String sql = null;
+            Constructor<?> constructor = null;
+            if (compare(CREATE_STATEMENT,name)) {
+                //createStatement
+                constructor = getConstructor(CREATE_STATEMENT_IDX,Statement.class);
+            }else if (compare(PREPARE_STATEMENT,name)) {
+                //prepareStatement
+                sql = (String)args[0];
+                constructor = getConstructor(PREPARE_STATEMENT_IDX,PreparedStatement.class);
+                if (sql!=null) {
+                    prepareStatement(sql, time);
+                }
+            }else if (compare(PREPARE_CALL,name)) {
+                //prepareCall
+                sql = (String)args[0];
+                constructor = getConstructor(PREPARE_CALL_IDX,CallableStatement.class);
+                prepareCall(sql,time);
+            }else {
+                //do nothing, might be a future unsupported method
+                //so we better bail out and let the system continue
+                return statement;
+            }
+            result = constructor.newInstance(new Object[] { new StatementProxy(statement,sql) });
+            return result;
+        }catch (Exception x) {
+            log.warn("Unable to create statement proxy for slow query report.",x);
+        }
+        return statement;
+    }
+
+
+    /**
+     * Class to measure query execute time
+     *
+     */
+    protected class StatementProxy implements InvocationHandler {
+        protected boolean closed = false;
+        protected Object delegate;
+        protected final String query;
+        public StatementProxy(Object parent, String query) {
+            this.delegate = parent;
+            this.query = query;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+            //get the name of the method for comparison
+            final String name = method.getName();
+            //was close invoked?
+            boolean close = compare(JdbcInterceptor.CLOSE_VAL,name);
+            //allow close to be called multiple times
+            if (close && closed) return null;
+            //are we calling isClosed?
+            if (compare(JdbcInterceptor.ISCLOSED_VAL,name)) return Boolean.valueOf(closed);
+            //if we are calling anything else, bail out
+            if (closed) throw new SQLException("Statement closed.");
+            boolean process = false;
+            //check to see if we are about to execute a query
+            process = isExecute( method, process);
+            //if we are executing, get the current time
+            long start = (process)?System.currentTimeMillis():0;
+            Object result =  null;
+            try {
+                //execute the query
+                result =  method.invoke(delegate,args);
+            }catch (Throwable t) {
+                reportFailedQuery(query,args,name,start,t);
+                if (t instanceof InvocationTargetException
+                        && t.getCause() != null) {
+                    throw t.getCause();
+                } else {
+                    throw t;
+                }
+            }
+            //measure the time
+            long delta = (process)?(System.currentTimeMillis()-start):Long.MIN_VALUE;
+            //see if we meet the requirements to measure
+            if (delta>threshold) {
+                try {
+                    //report the slow query
+                    reportSlowQuery(query, args, name, start, delta);
+                }catch (Exception t) {
+                    if (log.isWarnEnabled()) log.warn("Unable to process slow query",t);
+                }
+            } else if (process) {
+                reportQuery(query, args, name, start, delta);
+            }
+            //perform close cleanup
+            if (close) {
+                closed=true;
+                delegate = null;
+            }
+            return result;
+        }
+    }
+
+}