001    /*
002     * $Id: ProxyDriver.java,v 1.1 2014/04/27 19:35:07 oboehm Exp $
003     *
004     * Copyright (c) 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 27.03.2014 by oliver (ob@oasd.de)
019     */
020    
021    package patterntesting.runtime.monitor.db;
022    
023    import java.sql.*;
024    import java.util.*;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.slf4j.*;
028    
029    /**
030     * This JDBC driver acts like a proxy between PatternTesting and the real JDBC
031     * driver to be able to monitor JDBC access. It was inspired by the JAMonDriver
032     * of the JAMon framework.
033     * <p>
034     * This driver is registered for JDBC URLs beginning with "<em>jdbc:proxy:</em>
035     * ...". This prefix must follow the real driver path. E.g. if you want to use
036     * HSQL as database your URL make look like
037     * "<em>jdbc:proxy:hsqldb:file:/tmp/oli</em>".
038     * </p>
039     *
040     * @author oliver
041     * @since 1.4.1 (27.03.2014)
042     * @version $Revision: 1.1 $
043     */
044    public class ProxyDriver implements Driver {
045    
046        private static final String JDBC_URL_PREFIX = "jdbc:proxy:";
047        private static final Logger log = LoggerFactory.getLogger(ProxyDriver.class);
048        private static final Map<String, String> knownDrivers = new HashMap<String, String>();
049    
050        /** Register class as JDBC driver. */
051        static {
052            register();
053            knownDrivers.put("hsqldb", "org.hsqldb.jdbc.JDBCDriver");
054            knownDrivers.put("sqlserver", "com.microsoft.sqlserver.jdbc.SQLServerDriver");
055            knownDrivers.put("jturbo", "com.newatlanta.jturbo.driver.Driver");
056        }
057    
058        private static String getKnownDriverFor(final String jdbcURL) {
059            String[] parts = StringUtils.split(jdbcURL + ":x", ':');
060            return knownDrivers.get(parts[1].toLowerCase());
061        }
062    
063        /**
064         * Registers the driver as JDBC driver.
065         */
066        public static void register() {
067            Driver driver = new ProxyDriver();
068            try {
069                DriverManager.registerDriver(driver);
070                log.debug("{} successful registered as JDBC driver.", driver);
071            } catch (SQLException ex) {
072                DriverManager.println("Cannot register " + driver + " because of " + ex.getMessage() + ".");
073                log.error("Cannot register {} as JDBC driver.", driver, ex);
074            }
075        }
076    
077        /**
078         * Gets the real JDBC URL of the underlying driver.
079         *
080         * @param jdbcURL the jdbc url, e.g. "jdbc:proxy:hsqldb:mem:testdb"
081         * @return the real driver name
082         */
083        public static String getRealURL(final String jdbcURL) {
084            if (jdbcURL.startsWith(JDBC_URL_PREFIX)) {
085                return "jdbc:" + StringUtils.substring(jdbcURL, JDBC_URL_PREFIX.length());
086            } else {
087                return jdbcURL;
088            }
089        }
090    
091        /**
092         * Gets the real driver name of the underlying driver.
093         *
094         * @param jdbcURL the jdbc url, e.g. "jdbc:proxy:hsqldb:mem:testdb"
095         * @return the real driver name
096         */
097        public static String getRealDriverName(final String jdbcURL) {
098            return getDriverName(getRealURL(jdbcURL));
099        }
100    
101        private static String getDriverName(final String jdbcURL) {
102            String driverName = getKnownDriverFor(jdbcURL);
103            if (driverName == null) {
104                return getDriver(jdbcURL).getClass().getName();
105            }
106            return driverName;
107        }
108    
109        /**
110         * Gets the real driver.
111         *
112         * @param jdbcURL the jdbc url, e.g. "jdbc:proxy:hsqldb:mem:testdb"
113         * @return the real driver
114         */
115        public static Driver getRealDriver(final String jdbcURL) {
116            String realURL = getRealURL(jdbcURL);
117            return getDriver(realURL);
118        }
119    
120        private static Driver getDriver(final String url) {
121            try {
122                return DriverManager.getDriver(url);
123            } catch (SQLException ex) {
124                log.trace("Cannot get driver from DriverManager.", ex);
125                log.debug("Must first load driver for \"{}\" because {}.", url, ex.getMessage());
126                return loadDriverFor(url);
127            }
128        }
129    
130        private static Driver loadDriverFor(final String jdbcURL) {
131            try {
132                String driverName = getKnownDriverFor(jdbcURL);
133                if (driverName != null) {
134                    Class.forName(driverName);
135                    log.debug("Driver {} for URL \"{}\" loaded.", driverName, jdbcURL);
136                }
137                return DriverManager.getDriver(jdbcURL);
138            } catch (SQLException ex) {
139                throw new IllegalArgumentException("unregistered URL: \"" + jdbcURL + '"', ex);
140            } catch (ClassNotFoundException ex) {
141                throw new IllegalArgumentException("cannot load driver for \"" + jdbcURL + '"', ex);
142            }
143        }
144    
145        /**
146         * Retrieves whether the driver thinks that it can open a connection to the
147         * given URL. Accepted URLs are URLs beginning with:
148         * <ul>
149         *  <li><tt>jdbc:proxy:</tt>...</li>
150         *  <li><tt>jdbc:jamon:</tt>... (if JAMon is in the classpath)</li>
151         * </ul>
152         *
153         * @param url the JDBC URL
154         * @return true, if successful
155         * @see Driver#acceptsURL(String)
156         */
157        public boolean acceptsURL(final String url) {
158            String prefix = url.toLowerCase();
159            return StringUtils.startsWith(prefix, JDBC_URL_PREFIX);
160        }
161    
162        /**
163         * Attempts to make a database connection to the given URL. The driver
164         * returns "null" if it realizes it is the wrong kind of driver to
165         * connect to the given URL. This will be common, as when the JDBC driver
166         * manager is asked to connect to a given URL it passes the URL to each
167         * loaded driver in turn.
168         *
169         * @param url the url
170         * @param info the info (e.g. user/password)
171         * @return the connection
172         * @throws SQLException the sQL exception
173         * @see Driver#connect(String, Properties)
174         */
175        public Connection connect(final String url, final Properties info) throws SQLException {
176            log.trace("Connecting to URL \"{}\"...", url);
177            if (!acceptsURL(url)) {
178                log.trace("{} does not accept \"{}\" as URL.", this, url);
179                return null;
180            }
181            String realURL = getRealURL(url);
182            Driver realDriver = getDriver(realURL);
183            Connection connection = realDriver.connect(realURL, info);
184            log.trace("Connected to real URL \"{}\".", realURL);
185            return ProxyConnection.newInstance(connection);
186        }
187    
188        /**
189         * Gets the major version
190         *
191         * @return major version
192         */
193        public int getMajorVersion() {
194            return 1;
195        }
196    
197        /**
198         * Gets the minor version.
199         *
200         * @return the minor version
201         */
202        public int getMinorVersion() {
203            return 4;
204        }
205    
206        /**
207         * Gets the property info.
208         *
209         * @param url the url
210         * @param info the info
211         * @return the property info
212         * @throws SQLException the SQL exception
213         */
214        public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) throws SQLException {
215            String realURL = getRealURL(url);
216            Driver driver = getDriver(realURL);
217            return driver.getPropertyInfo(realURL, info);
218        }
219    
220        /**
221         * Jdbc compliant.
222         *
223         * @return true, if successful
224         */
225        public boolean jdbcCompliant() {
226            return true;
227        }
228    
229        /**
230         * Gets the parent logger. This method is needed for the support of Java 5.
231         *
232         * @return the parent logger
233         * @throws SQLFeatureNotSupportedException the SQL feature not supported exception
234         */
235        public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
236            throw new SQLFeatureNotSupportedException("not yet implemented");
237        }
238    
239        /**
240         * Better toString implementation which supports logging and debugging.
241         *
242         * @return the string
243         * @see java.lang.Object#toString()
244         */
245        @Override
246        public String toString() {
247            return this.getClass().getSimpleName() + " " + this.getMajorVersion() + "."
248                    + this.getMinorVersion() + " for \"" + JDBC_URL_PREFIX + "...\"";
249        }
250    
251    }
252