001 /*
002 * $Id: ProxyConnection.java,v 1.2 2014/04/27 19:35:07 oboehm Exp $
003 *
004 * Copyright (c) 2012-2014 by Oliver Boehm
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 *
018 * (c)reated 29.09.2012 by oliver (ob@oasd.de)
019 */
020
021 package patterntesting.runtime.monitor.db;
022
023 import java.lang.reflect.*;
024 import java.sql.*;
025
026 import org.slf4j.*;
027
028 import patterntesting.runtime.annotation.IgnoreForSequenceDiagram;
029 import patterntesting.runtime.monitor.db.internal.*;
030 import patterntesting.runtime.util.Converter;
031
032 /**
033 * This is a dynamic proxy for a JDBC connection which monitors together with
034 * the {@link ConnectionMonitor} the different newInstance() and close() call.
035 * <p>
036 * Note: Since 1.4.2 this class was moved from package
037 * "patterntesting.runtime.db" to here.
038 * </p>
039 *
040 * @author oliver (ob@aosd.de)
041 * @since 1.3 (29.09.2012)
042 * @version $Revision: 1.2 $
043 */
044 @IgnoreForSequenceDiagram
045 public class ProxyConnection implements InvocationHandler {
046
047 private static final Logger log = LoggerFactory.getLogger(ProxyConnection.class);
048 private final Connection connection;
049 private final StackTraceElement[] caller;
050
051 /**
052 * Creates a new proxy instance for the given connection.
053 *
054 * @param connection the connection
055 * @return the connection
056 */
057 public static Connection newInstance(final Connection connection) {
058 ProxyConnection proxyConnection = new ProxyConnection(connection);
059 ConnectionMonitor.addConnection(proxyConnection);
060 Class<?>[] interfaces = new Class[] { Connection.class };
061 return (Connection) Proxy.newProxyInstance(connection.getClass().getClassLoader(),
062 interfaces, proxyConnection);
063 }
064
065 /**
066 * Instantiates a new proxy connection. This constructor is called from
067 * {@link #newInstance(Connection)} which is of no interest for us. We want
068 * to store the real caller so we ignore the {@link ProxyConnection} class
069 * but also the {@link ProxyDriver} class (ProxyDriver also calls this
070 * constructor indirectly) and other non-interesting classes.
071 *
072 * @param connection the connection
073 */
074 protected ProxyConnection(final Connection connection) {
075 this.connection = connection;
076 this.caller = getCallerStacktrace(ProxyConnection.class, ProxyDriver.class,
077 ConnectionMonitor.class, DriverManager.class);
078 }
079
080 /**
081 * Gets the caller stacktrace. To find the real caller we ignore the first
082 * 3 elements from the stacktrace because this is e.g. the method
083 * {@link Thread#getStackTrace()} which is not relevant here.
084 *
085 * @param ignoredClasses the ignored classes
086 * @return the caller stacktrace
087 */
088 private static StackTraceElement[] getCallerStacktrace(final Class<?>... ignoredClasses) {
089 StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
090 for (int i = 3; i < stacktrace.length; i++) {
091 if (!matches(stacktrace[i].getClassName(), ignoredClasses)) {
092 StackTraceElement[] stacktraceCaller = new StackTraceElement[stacktrace.length - i];
093 System.arraycopy(stacktrace, i, stacktraceCaller, 0, stacktrace.length - i);
094 return stacktraceCaller;
095 }
096 }
097 throw new IllegalStateException("no caller found for " + Converter.toString(ignoredClasses));
098 }
099
100 private static boolean matches(final String classname, final Class<?>...classes) {
101 for (int i = 0; i < classes.length; i++) {
102 if (classname.equals(classes[i].getName())) {
103 return true;
104 }
105 }
106 return false;
107 }
108
109 /**
110 * Invokes the orginal {@link Connection} method and puts a wrapper around
111 * {@link Statement} and {@link PreparedStatement} to support monitoring.
112 * <p>
113 * TODO: CallableStatement is not yet wrapped.
114 * </p>
115 *
116 * @param proxy the proxy
117 * @param method the method
118 * @param args the args
119 * @return the object
120 * @throws Throwable the throwable
121 * @see java.lang.reflect.InvocationHandler#invoke(Object, Method, Object[])
122 */
123 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
124 assert method != null;
125 String methodName = method.getName();
126 try {
127 if (methodName.equals("close")) {
128 ConnectionMonitor.removeConnection(this);
129 } else if (methodName.equals("createStatement")) {
130 Statement stmt = (Statement) method.invoke(connection, args);
131 return new StasiStatement(stmt);
132 } else if (methodName.equals("prepareStatement")) {
133 PreparedStatement stmt = (PreparedStatement) method.invoke(connection, args);
134 return new StasiPreparedStatement(stmt, args);
135 } else if (methodName.equals("toString")) {
136 return this.toString();
137 }
138 return method.invoke(connection, args);
139 } catch (InvocationTargetException ite) {
140 log.debug("Cannot invoke {} ({})!", method, ite);
141 throw ite.getTargetException();
142 }
143 }
144
145 /**
146 * Gets the wrapped connection.
147 *
148 * @return the connection
149 */
150 public Connection getConnection() {
151 return this.connection;
152 }
153
154 /**
155 * Gets the stacktrace of the caller.
156 *
157 * @return the caller stacktrace
158 */
159 public StackTraceElement[] getCaller() {
160 return this.caller;
161 }
162
163 /**
164 * To string.
165 *
166 * @return the string
167 * @see java.lang.Object#toString()
168 */
169 @Override
170 public String toString() {
171 return this.getClass().getSimpleName() + " for " + this.caller[0];
172 }
173
174 }
175