001: /*
002: * Craftsman Spy.
003: * Copyright (C) 2005 Sébastien LECACHEUR
004: *
005: * This program is free software; you can redistribute it and/or modify
006: * it under the terms of the GNU General Public License as published by
007: * the Free Software Foundation; either version 2 of the License, or
008: * (at your option) any later version.
009: *
010: * This program is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU General Public License for more details.
014: *
015: * You should have received a copy of the GNU General Public License
016: * along with this program; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
018: */
019: package craftsman.spy;
020:
021: import java.sql.Connection;
022: import java.sql.Driver;
023: import java.sql.DriverManager;
024: import java.sql.DriverPropertyInfo;
025: import java.sql.SQLException;
026: import java.util.Properties;
027:
028: /**
029: * This classe is the implementation of the JDBC driver in order to
030: * log any JDBC action.
031: *
032: * The system property <code>spy.driver</code> MUST contains the real
033: * JDBC driver full class name in order to use the Spy driver.
034: *
035: * Another way is to use a specific JDBC url string like :
036: * <code>jdbc:spy:<i>driver_full_class_name</i>:url</code>.
037: *
038: * The two solutions can be used as the following examples :
039: * <pre>
040: * System.setProperty("spy.driver","vendor.database.Driver");// or with the -Dspy.driver=vendor.database.Driver JVM option
041: * Class.forName("craftsman.spy.SpyDriver");
042: * Connection c = DriverManager.getConnection("jdbc:database:mydata");
043: * </pre>
044: *
045: * <pre>
046: * Class.forName("craftsman.spy.SpyDriver");
047: * Connection c = DriverManager.getConnection("jdbc:spy:vendor.database.Driver:database:mydata");
048: * </pre>
049: * @author Sébastien LECACHEUR
050: */
051: public class SpyDriver implements Driver {
052: /**
053: * The real JDBC driver instance.
054: */
055: private Driver real = null;
056:
057: /**
058: * The property name of the real JDBC driver full class name.
059: */
060: private final static String DRIVER_CLASS_NAME_PROPERTY = "spy.driver";
061:
062: /**
063: * The property name of the SQL query to get the JDBC connection ID.
064: *
065: * This query can be :
066: * <ul>
067: * <li><code>select @@spid</code> for Sybase</li>
068: * <li><code>select @@spid</code> for MS SQL-Server</li>
069: * <li><code>select @@session.identity</code> for MySQL</li>
070: * <li><code>select @@spidExec</code> for Oracle</li>
071: * </ul>
072: */
073: private final static String CONNECTION_ID_SQL_PROPERTY = "spy.connection.id";
074:
075: /**
076: * The prefix of accepted JDBC url by the Spy JDBC driver when the real driver
077: * is no selected with the <code>spy.driver</code> system property.
078: */
079: private final static String URL_PREFIX = "jdbc:spy:";
080:
081: static {
082: try {
083: new SpyDriver();
084: } catch (Exception e) {
085: e.printStackTrace();
086: }
087: }
088:
089: /**
090: * Constructs a new Spy JDBC driver.
091: * @throws ClassNotFoundException When the real JDBC driver class was not found.
092: * @throws InstantiationException When the real JDBC driver cannot be instancied.
093: * @throws IllegalAccessException When cannot create a new instance of the real
094: * JDBC driver.
095: * @throws SQLException When cannot register the Spy JDBC driver or deregistrer
096: * the real JDBC driver.
097: */
098: public SpyDriver() throws ClassNotFoundException,
099: InstantiationException, IllegalAccessException,
100: SQLException {
101: init();
102: }
103:
104: /**
105: * Initializes the Spy JDBC driver.
106: * @throws ClassNotFoundException When the real JDBC driver class was not found.
107: * @throws InstantiationException When the real JDBC driver cannot be instancied.
108: * @throws IllegalAccessException When cannot create a new instance of the real
109: * JDBC driver.
110: * @throws SQLException When cannot register the Spy JDBC driver or deregistrer
111: * the real JDBC driver.
112: */
113: private void init() throws ClassNotFoundException,
114: InstantiationException, IllegalAccessException,
115: SQLException {
116: DriverManager.registerDriver(this );
117: registerRealDriver(System
118: .getProperty(DRIVER_CLASS_NAME_PROPERTY));
119: }
120:
121: /**
122: * Register the real JDBC driver in the Spy JDBC driver. Create a new instance
123: * of the real JDBC driver, store it and deregister this one from the driver
124: * manager.
125: * @param className String The full name of the real JDBC driver.
126: * @throws ClassNotFoundException When the real JDBC driver class was not found.
127: * @throws InstantiationException When the real JDBC driver cannot be instancied.
128: * @throws IllegalAccessException When cannot create a new instance of the real
129: * JDBC driver.
130: * @throws SQLException When cannot deregistrer
131: * the real JDBC driver.
132: */
133: private void registerRealDriver(String className)
134: throws InstantiationException, IllegalAccessException,
135: ClassNotFoundException, SQLException {
136: if (className != null) {
137: real = (Driver) Class.forName(className).newInstance();
138: DriverManager.deregisterDriver(real);
139: }
140: }
141:
142: /**
143: * Extracts from a JDBC url the real driver full class name.
144: * @param url String The JDBC url from which extract the
145: * driver full class name.
146: * @return String The full class name otherwise <code>null</code>.
147: */
148: private String getRealDriverClassName(String url) {
149: String result = null;
150: int index = url.indexOf(':', URL_PREFIX.length() + 1);
151:
152: if (index != -1) {
153: result = url.substring(URL_PREFIX.length(), index);
154: }
155:
156: return result;
157: }
158:
159: /**
160: * Returns the real JDBC url from the given url. Extracts the
161: * real url from a "spy url".
162: * @param url The JDBC url from which get the real url.
163: * @return String The real url otherwise the same.
164: */
165: private String getRealUrl(String url) {
166: return url != null ? url.startsWith(URL_PREFIX) ? ("jdbc" + url
167: .substring(url.indexOf(':', URL_PREFIX.length() + 1)))
168: : url : url;
169: }
170:
171: /**
172: * Retrieves the real driver's major version number. Initially
173: * this should be <code>1</code>.
174: * @return int This driver's major version number.
175: * @see Driver#getMajorVersion()
176: */
177: public int getMajorVersion() {
178: return real != null ? real.getMajorVersion() : 1;
179: }
180:
181: /**
182: * Gets the real driver's minor version number. Initially this
183: * should be <code>0</code>.
184: * @return int This driver's minor version number.
185: * @see Driver#getMinorVersion()
186: */
187: public int getMinorVersion() {
188: return real != null ? real.getMinorVersion() : 0;
189: }
190:
191: /**
192: * Reports whether thz real driver is a genuine JDBC Compliant driver.
193: * @return boolean <code>true</code> if this driver is JDBC Compliant;
194: * <code>false</code> otherwise.
195: * @see Driver#jdbcCompliant()
196: */
197: public boolean jdbcCompliant() {
198: return real.jdbcCompliant();
199: }
200:
201: /**
202: * Retrieves whether the real driver thinks that it can open a
203: * connection to the given URL.
204: * @param url String The URL of the database
205: * @return boolean <code>true</code> if this driver understands
206: * the given URL; <code>false</code> otherwise.
207: * @throws SQLException If a database access error occurs.
208: * @see Driver#acceptsURL(java.lang.String)
209: */
210: public boolean acceptsURL(String url) throws SQLException {
211: boolean result = false;
212:
213: if (real == null && url.startsWith(URL_PREFIX)) {
214: try {
215: registerRealDriver(getRealDriverClassName(url));
216: } catch (IllegalAccessException e) {
217: e.printStackTrace();
218: } catch (InstantiationException e) {
219: e.printStackTrace();
220: } catch (ClassNotFoundException e) {
221: e.printStackTrace();
222: }
223: }
224:
225: if (real != null) {
226: result = real.acceptsURL(getRealUrl(url));
227: }
228:
229: return result;
230: }
231:
232: /**
233: * Attempts to make a database connection to the given URL.
234: * @param url String The URL of the database to which to connect.
235: * @param info Properties A list of arbitrary string tag/value
236: * pairs as connection arguments.
237: * @return Connection A Connection object that represents a
238: * connection to the URL.
239: * @throws SQLException If a database access error occurs.
240: * @see Driver#connect(java.lang.String, java.util.Properties)
241: */
242: public Connection connect(String url, Properties info)
243: throws SQLException {
244: Connection con = null;
245:
246: if (real == null && url.startsWith(URL_PREFIX)) {
247: try {
248: registerRealDriver(getRealDriverClassName(url));
249: } catch (IllegalAccessException e) {
250: e.printStackTrace();
251: } catch (InstantiationException e) {
252: e.printStackTrace();
253: } catch (ClassNotFoundException e) {
254: e.printStackTrace();
255: }
256: }
257:
258: if (real != null) {
259: con = new SpyConnection(
260: real.connect(getRealUrl(url), info), System
261: .getProperty(CONNECTION_ID_SQL_PROPERTY));
262: }
263:
264: return con;
265: }
266:
267: /**
268: * Gets information about the possible properties for the real driver.
269: * @param url String The URL of the database to which to connect.
270: * @param info Properties A proposed list of tag/value pairs that will
271: * be sent on connect open.
272: * @return DriverPropertyInfo[] An array of DriverPropertyInfo objects
273: * describing possible properties.
274: * @throws SQLException If a database access error occurs.
275: * @see Driver#getPropertyInfo(java.lang.String, java.util.Properties)
276: */
277: public DriverPropertyInfo[] getPropertyInfo(String url,
278: Properties info) throws SQLException {
279: return real != null ? real.getPropertyInfo(url, info) : null;
280: }
281: }
|