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