/*
* Copyright 2013 Sylvain LAURENT
*
* 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.
*/
package ch.sla.jdbcperflogger.driver;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.eclipse.jdt.annotation.Nullable;
import ch.sla.jdbcperflogger.DatabaseType;
import ch.sla.jdbcperflogger.StatementType;
import ch.sla.jdbcperflogger.logger.PerfLogger;
public class LoggingStatementInvocationHandler implements InvocationHandler {
protected static final String CLEAR_BATCH = "clearBatch";
protected static final String ADD_BATCH = "addBatch";
protected static final String EXECUTE_BATCH = "executeBatch";
protected static final String EXECUTE_LARGE_BATCH = "executeLargeBatch";
protected static final String EXECUTE_UPDATE = "executeUpdate";
protected static final String EXECUTE_LARGE_UPDATE = "executeLargeUpdate";
protected static final String EXECUTE = "execute";
protected static final String EXECUTE_QUERY = "executeQuery";
protected static final String GET_RESULT_SET = "getResultSet";
protected UUID connectionId;
protected final DatabaseType databaseType;
protected final Statement wrappedStatement;
private final List<String> batchedNonPreparedStmtExecutions = new ArrayList<String>();
protected @Nullable UUID lastExecutionLogId;
LoggingStatementInvocationHandler(final UUID connectionId, final Statement statement,
final DatabaseType databaseType) {
this.connectionId = connectionId;
wrappedStatement = statement;
this.databaseType = databaseType;
}
@Override
@Nullable
public Object invoke(final @Nullable Object _proxy, final Method method, final @Nullable Object[] args)
throws Throwable {
final Object result;
final String methodName = method.getName();
if (EXECUTE_QUERY.equals(methodName) && args != null) {
return internalExecuteQuery(method, args);
} else if ((EXECUTE.equals(methodName) || EXECUTE_UPDATE.equals(methodName)
|| EXECUTE_LARGE_UPDATE.equals(methodName)) && args != null) {
return internalExecute(method, args);
} else if (EXECUTE_BATCH.equals(methodName) || EXECUTE_LARGE_BATCH.equals(methodName)) {
return internalExecuteBatch(method, args);
} else if (GET_RESULT_SET.equals(methodName)) {
// TODO : handle getResultSet to return a proxy to the resultset like in internalExecuteQuery
assert lastExecutionLogId != null;
return getAndWrapResultSet(method, args, lastExecutionLogId);
} else {
result = Utils.invokeUnwrapException(wrappedStatement, method, args);
if (ADD_BATCH.equals(methodName) && args != null) {
batchedNonPreparedStmtExecutions.add((String) args[0]);
} else if (CLEAR_BATCH.equals(methodName)) {
batchedNonPreparedStmtExecutions.clear();
}
}
return result;
}
protected final ResultSet internalExecuteQuery(final Method method, final Object[] args) throws Throwable {
final UUID logId = UUID.randomUUID();
final long start = System.nanoTime();
PerfLogger.logBeforeStatement(connectionId, logId, (String) args[0], StatementType.NON_PREPARED_QUERY_STMT,
wrappedStatement.getQueryTimeout(), wrappedStatement.getConnection().getAutoCommit());
Throwable exc = null;
try {
return getAndWrapResultSet(method, args, logId);
} catch (final Throwable e) {
exc = e;
throw exc;
} finally {
final long end = System.nanoTime();
PerfLogger.logStatementExecuted(logId, end - start, null, exc);
lastExecutionLogId = logId;
}
}
private ResultSet getAndWrapResultSet(final Method method, final @Nullable Object[] args, final UUID logId)
throws Throwable {
final ResultSet resultSet = (ResultSet) Utils.invokeUnwrapExceptionReturnNonNull(wrappedStatement, method,
args);
return (ResultSet) Proxy.newProxyInstance(LoggingStatementInvocationHandler.class.getClassLoader(),
Utils.extractAllInterfaces(resultSet.getClass()),
new LoggingResultSetInvocationHandler(resultSet, logId));
}
@Nullable
protected final Object internalExecute(final Method method, final Object[] args) throws Throwable {
final UUID logId = UUID.randomUUID();
PerfLogger.logBeforeStatement(connectionId, logId, (String) args[0], StatementType.BASE_NON_PREPARED_STMT,
wrappedStatement.getQueryTimeout(), wrappedStatement.getConnection().getAutoCommit());
Throwable exc = null;
Long updateCount = null;
final long start = System.nanoTime();
try {
final Object result = Utils.invokeUnwrapException(wrappedStatement, method, args);
if (result instanceof Number) {
updateCount = ((Number) result).longValue();
}
return result;
} catch (final Throwable e) {
exc = e;
throw exc;
} finally {
final long end = System.nanoTime();
PerfLogger.logStatementExecuted(logId, end - start, updateCount, exc);
lastExecutionLogId = logId;
}
}
@Nullable
protected Object internalExecuteBatch(final Method method, @Nullable final Object[] args) throws Throwable {
final UUID logId = UUID.randomUUID();
PerfLogger.logNonPreparedBatchedStatements(connectionId, logId, batchedNonPreparedStmtExecutions, databaseType,
wrappedStatement.getQueryTimeout(), wrappedStatement.getConnection().getAutoCommit());
try {
return internalExecuteBatchInternal(method, args, logId);
} finally {
batchedNonPreparedStmtExecutions.clear();
lastExecutionLogId = logId;
}
}
@Nullable
protected Object internalExecuteBatchInternal(final Method method, final @Nullable Object[] args, final UUID logId)
throws Throwable {
Throwable exc = null;
Long updateCount = null;
final long start = System.nanoTime();
try {
final Object result = Utils.invokeUnwrapException(wrappedStatement, method, args);
if (result instanceof int[]) {
final int[] nbRows = (int[]) result;
long totalRows = 0;
for (int i = 0; i < nbRows.length; i++) {
totalRows += nbRows[i];
}
updateCount = Long.valueOf(totalRows);
} else if (result instanceof long[]) {// java8
final long[] nbRows = (long[]) result;
long totalRows = 0;
for (int i = 0; i < nbRows.length; i++) {
totalRows += nbRows[i];
}
updateCount = Long.valueOf(totalRows);
}
return result;
} catch (final Throwable e) {
exc = e;
throw exc;
} finally {
final long end = System.nanoTime();
PerfLogger.logStatementExecuted(logId, end - start, updateCount, exc);
lastExecutionLogId = logId;
}
}
}