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