blob: 922205e4408d4ac47ed653ebcbcc27e7051eab69 [file] [log] [blame]
/*
* 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.ResultSet;
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.interceptor.AbstractCreateStatementInterceptor;
/**
* Implementation of <b>JdbcInterceptor</b> that proxies resultSets and statements.
* @author Guillermo Fernandes
*/
public class StatementDecoratorInterceptor extends AbstractCreateStatementInterceptor {
private static final Log logger = LogFactory.getLog(StatementDecoratorInterceptor.class);
private static final String[] EXECUTE_QUERY_TYPES = { "executeQuery" };
/**
* the constructors that are used to create statement proxies
*/
protected static final Constructor<?>[] constructors = new Constructor[AbstractCreateStatementInterceptor.STATEMENT_TYPE_COUNT];
/**
* the constructor to create the resultSet proxies
*/
protected static Constructor<?> resultSetConstructor = null;
@Override
public void closeInvoked() {
// nothing to do
}
/**
* 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
*/
protected Constructor<?> getConstructor(int idx, Class<?> clazz) throws NoSuchMethodException {
if (constructors[idx] == null) {
Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
new Class[] { clazz });
constructors[idx] = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
}
return constructors[idx];
}
protected Constructor<?> getResultSetConstructor() throws NoSuchMethodException {
if (resultSetConstructor == null) {
Class<?> proxyClass = Proxy.getProxyClass(StatementDecoratorInterceptor.class.getClassLoader(),
new Class[] { ResultSet.class });
resultSetConstructor = proxyClass.getConstructor(new Class[] { InvocationHandler.class });
}
return resultSetConstructor;
}
/**
* Creates a statement interceptor to monitor query response times
*/
@Override
public Object createStatement(Object proxy, Method method, Object[] args, Object statement, long time) {
try {
String name = method.getName();
Constructor<?> constructor = null;
String sql = null;
if (compare(CREATE_STATEMENT, name)) {
// createStatement
constructor = getConstructor(CREATE_STATEMENT_IDX, Statement.class);
} else if (compare(PREPARE_STATEMENT, name)) {
// prepareStatement
constructor = getConstructor(PREPARE_STATEMENT_IDX, PreparedStatement.class);
sql = (String)args[0];
} else if (compare(PREPARE_CALL, name)) {
// prepareCall
constructor = getConstructor(PREPARE_CALL_IDX, CallableStatement.class);
sql = (String)args[0];
} else {
// do nothing, might be a future unsupported method
// so we better bail out and let the system continue
return statement;
}
return createDecorator(proxy, method, args, statement, constructor, sql);
} catch (Exception x) {
logger.warn("Unable to create statement proxy for slow query report.", x);
}
return statement;
}
protected Object createDecorator(Object proxy, Method method, Object[] args,
Object statement, Constructor<?> constructor, String sql)
throws InstantiationException, IllegalAccessException, InvocationTargetException {
Object result = null;
StatementProxy statementProxy = new StatementProxy<Statement>((Statement)statement,sql);
result = constructor.newInstance(new Object[] { statementProxy });
statementProxy.setActualProxy(result);
statementProxy.setConnection(proxy);
statementProxy.setConnection(constructor);
return result;
}
protected boolean isExecuteQuery(String methodName) {
return EXECUTE_QUERY_TYPES[0].equals(methodName);
}
protected boolean isExecuteQuery(Method method) {
return isExecuteQuery(method.getName());
}
/**
* Class to measure query execute time
*
* @author fhanik
*
*/
protected class StatementProxy<T extends java.sql.Statement> implements InvocationHandler {
protected boolean closed = false;
protected T delegate;
private Object actualProxy;
private Object connection;
private String sql;
private Constructor constructor;
public StatementProxy(T delegate, String sql) {
this.delegate = delegate;
this.sql = sql;
}
public T getDelegate() {
return this.delegate;
}
public String getSql() {
return sql;
}
public void setConnection(Object proxy) {
this.connection = proxy;
}
public Object getConnection() {
return this.connection;
}
public void setActualProxy(Object proxy){
this.actualProxy = proxy;
}
public Object getActualProxy() {
return this.actualProxy;
}
public Constructor getConstructor() {
return constructor;
}
public void setConstructor(Constructor constructor) {
this.constructor = constructor;
}
public void closeInvoked() {
if (getDelegate()!=null) {
try {
getDelegate().close();
}catch (SQLException ignore) {
}
}
closed = true;
delegate = null;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (compare(TOSTRING_VAL,method)) {
return toString();
}
// was close invoked?
boolean close = compare(CLOSE_VAL, method);
// allow close to be called multiple times
if (close && closed)
return null;
// are we calling isClosed?
if (compare(ISCLOSED_VAL, method))
return Boolean.valueOf(closed);
// if we are calling anything else, bail out
if (closed)
throw new SQLException("Statement closed.");
if (compare(GETCONNECTION_VAL,method)){
return connection;
}
boolean process = isExecuteQuery(method);
// check to see if we are about to execute a query
// if we are executing, get the current time
Object result = null;
try {
// perform close cleanup
if (close) {
closeInvoked();
} else {
// execute the query
result = method.invoke(delegate, args);
}
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
InvocationTargetException it = (InvocationTargetException) t;
throw it.getCause() != null ? it.getCause() : it;
} else {
throw t;
}
}
if (process){
Constructor<?> cons = getResultSetConstructor();
result = cons.newInstance(new Object[]{new ResultSetProxy(actualProxy, result)});
}
return result;
}
public String toString() {
StringBuffer buf = new StringBuffer(StatementProxy.class.getName());
buf.append("[Proxy=");
buf.append(System.identityHashCode(this));
buf.append("; Sql=");
buf.append(getSql());
buf.append("; Delegate=");
buf.append(getDelegate());
buf.append("; Connection=");
buf.append(getConnection());
buf.append("]");
return buf.toString();
}
}
protected class ResultSetProxy implements InvocationHandler {
private Object st;
private Object delegate;
public ResultSetProxy(Object st, Object delegate) {
this.st = st;
this.delegate = delegate;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("getStatement")) {
return this.st;
} else {
return method.invoke(this.delegate, args);
}
}
}
}