patterntesting.runtime.monitor.db.ProxyConnection Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of patterntesting-rt Show documentation
Show all versions of patterntesting-rt Show documentation
PatternTesting Runtime (patterntesting-rt) is the runtime component for
the PatternTesting framework. It provides the annotations and base classes
for the PatternTesting testing framework (e.g. patterntesting-check,
patterntesting-concurrent or patterntesting-exception) but can be also
used standalone for classpath monitoring or profiling.
It uses AOP and AspectJ to perform this feat.
/*
* $Id: ProxyConnection.java,v 1.2 2014/04/27 19:35:07 oboehm Exp $
*
* Copyright (c) 2012-2014 by Oliver Boehm
*
* 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 orimplied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* (c)reated 29.09.2012 by oliver ([email protected])
*/
package patterntesting.runtime.monitor.db;
import java.lang.reflect.*;
import java.sql.*;
import org.slf4j.*;
import patterntesting.runtime.annotation.IgnoreForSequenceDiagram;
import patterntesting.runtime.monitor.db.internal.*;
import patterntesting.runtime.util.Converter;
/**
* This is a dynamic proxy for a JDBC connection which monitors together with
* the {@link ConnectionMonitor} the different newInstance() and close() call.
*
* Note: Since 1.4.2 this class was moved from package
* "patterntesting.runtime.db" to here.
*
*
* @author oliver ([email protected])
* @since 1.3 (29.09.2012)
* @version $Revision: 1.2 $
*/
@IgnoreForSequenceDiagram
public class ProxyConnection implements InvocationHandler {
private static final Logger log = LoggerFactory.getLogger(ProxyConnection.class);
private final Connection connection;
private final StackTraceElement[] caller;
/**
* Creates a new proxy instance for the given connection.
*
* @param connection the connection
* @return the connection
*/
public static Connection newInstance(final Connection connection) {
ProxyConnection proxyConnection = new ProxyConnection(connection);
ConnectionMonitor.addConnection(proxyConnection);
Class>[] interfaces = new Class[] { Connection.class };
return (Connection) Proxy.newProxyInstance(connection.getClass().getClassLoader(),
interfaces, proxyConnection);
}
/**
* Instantiates a new proxy connection. This constructor is called from
* {@link #newInstance(Connection)} which is of no interest for us. We want
* to store the real caller so we ignore the {@link ProxyConnection} class
* but also the {@link ProxyDriver} class (ProxyDriver also calls this
* constructor indirectly) and other non-interesting classes.
*
* @param connection the connection
*/
protected ProxyConnection(final Connection connection) {
this.connection = connection;
this.caller = getCallerStacktrace(ProxyConnection.class, ProxyDriver.class,
ConnectionMonitor.class, DriverManager.class);
}
/**
* Gets the caller stacktrace. To find the real caller we ignore the first
* 3 elements from the stacktrace because this is e.g. the method
* {@link Thread#getStackTrace()} which is not relevant here.
*
* @param ignoredClasses the ignored classes
* @return the caller stacktrace
*/
private static StackTraceElement[] getCallerStacktrace(final Class>... ignoredClasses) {
StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
for (int i = 3; i < stacktrace.length; i++) {
if (!matches(stacktrace[i].getClassName(), ignoredClasses)) {
StackTraceElement[] stacktraceCaller = new StackTraceElement[stacktrace.length - i];
System.arraycopy(stacktrace, i, stacktraceCaller, 0, stacktrace.length - i);
return stacktraceCaller;
}
}
throw new IllegalStateException("no caller found for " + Converter.toString(ignoredClasses));
}
private static boolean matches(final String classname, final Class>...classes) {
for (int i = 0; i < classes.length; i++) {
if (classname.equals(classes[i].getName())) {
return true;
}
}
return false;
}
/**
* Invokes the orginal {@link Connection} method and puts a wrapper around
* {@link Statement} and {@link PreparedStatement} to support monitoring.
*
* TODO: CallableStatement is not yet wrapped.
*
*
* @param proxy the proxy
* @param method the method
* @param args the args
* @return the object
* @throws Throwable the throwable
* @see java.lang.reflect.InvocationHandler#invoke(Object, Method, Object[])
*/
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
assert method != null;
String methodName = method.getName();
try {
if (methodName.equals("close")) {
ConnectionMonitor.removeConnection(this);
} else if (methodName.equals("createStatement")) {
Statement stmt = (Statement) method.invoke(connection, args);
return new StasiStatement(stmt);
} else if (methodName.equals("prepareStatement")) {
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, args);
return new StasiPreparedStatement(stmt, args);
} else if (methodName.equals("toString")) {
return this.toString();
}
return method.invoke(connection, args);
} catch (InvocationTargetException ite) {
log.debug("Cannot invoke {} ({})!", method, ite);
throw ite.getTargetException();
}
}
/**
* Gets the wrapped connection.
*
* @return the connection
*/
public Connection getConnection() {
return this.connection;
}
/**
* Gets the stacktrace of the caller.
*
* @return the caller stacktrace
*/
public StackTraceElement[] getCaller() {
return this.caller;
}
/**
* To string.
*
* @return the string
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.getClass().getSimpleName() + " for " + this.caller[0];
}
}