1 | /* |
2 | Copyright (C) 2002-2004 MySQL AB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of version 2 of the GNU General Public License as |
6 | published by the Free Software Foundation. |
7 | |
8 | There are special exceptions to the terms and conditions of the GPL |
9 | as it is applied to this software. View the full text of the |
10 | exception in file EXCEPTIONS-CONNECTOR-J in the directory of this |
11 | software distribution. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program; if not, write to the Free Software |
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | |
22 | |
23 | |
24 | */ |
25 | package com.mysql.jdbc; |
26 | |
27 | import java.io.IOException; |
28 | import java.io.InputStream; |
29 | import java.io.UnsupportedEncodingException; |
30 | import java.net.URLDecoder; |
31 | import java.sql.DriverPropertyInfo; |
32 | import java.sql.SQLException; |
33 | |
34 | import java.util.Iterator; |
35 | import java.util.List; |
36 | import java.util.Properties; |
37 | import java.util.StringTokenizer; |
38 | |
39 | /** |
40 | * The Java SQL framework allows for multiple database drivers. Each driver |
41 | * should supply a class that implements the Driver interface |
42 | * |
43 | * <p> |
44 | * The DriverManager will try to load as many drivers as it can find and then |
45 | * for any given connection request, it will ask each driver in turn to try to |
46 | * connect to the target URL. |
47 | * </p> |
48 | * |
49 | * <p> |
50 | * It is strongly recommended that each Driver class should be small and |
51 | * standalone so that the Driver class can be loaded and queried without |
52 | * bringing in vast quantities of supporting code. |
53 | * </p> |
54 | * |
55 | * <p> |
56 | * When a Driver class is loaded, it should create an instance of itself and |
57 | * register it with the DriverManager. This means that a user can load and |
58 | * register a driver by doing Class.forName("foo.bah.Driver") |
59 | * </p> |
60 | * |
61 | * @author Mark Matthews |
62 | * @version $Id: NonRegisteringDriver.java,v 1.1.2.1 2005/05/13 18:58:38 |
63 | * mmatthews Exp $ |
64 | * |
65 | * @see org.gjt.mm.mysql.Connection |
66 | * @see java.sql.Driver |
67 | */ |
68 | public class NonRegisteringDriver implements java.sql.Driver { |
69 | /** |
70 | * Key used to retreive the database value from the properties instance |
71 | * passed to the driver. |
72 | */ |
73 | public static final String DBNAME_PROPERTY_KEY = "DBNAME"; |
74 | |
75 | /** Should the driver generate debugging output? */ |
76 | public static final boolean DEBUG = false; |
77 | |
78 | /** Index for hostname coming out of parseHostPortPair(). */ |
79 | public final static int HOST_NAME_INDEX = 0; |
80 | |
81 | /** |
82 | * Key used to retreive the hostname value from the properties instance |
83 | * passed to the driver. |
84 | */ |
85 | public static final String HOST_PROPERTY_KEY = "HOST"; |
86 | |
87 | /** |
88 | * Key used to retreive the password value from the properties instance |
89 | * passed to the driver. |
90 | */ |
91 | public static final String PASSWORD_PROPERTY_KEY = "password"; |
92 | |
93 | /** Index for port # coming out of parseHostPortPair(). */ |
94 | public final static int PORT_NUMBER_INDEX = 1; |
95 | |
96 | /** |
97 | * Key used to retreive the port number value from the properties instance |
98 | * passed to the driver. |
99 | */ |
100 | public static final String PORT_PROPERTY_KEY = "PORT"; |
101 | |
102 | public static final String PROPERTIES_TRANSFORM_KEY = "propertiesTransform"; |
103 | |
104 | /** Should the driver generate method-call traces? */ |
105 | public static final boolean TRACE = false; |
106 | |
107 | public static final String USE_CONFIG_PROPERTY_KEY = "useConfigs"; |
108 | |
109 | /** |
110 | * Key used to retreive the username value from the properties instance |
111 | * passed to the driver. |
112 | */ |
113 | public static final String USER_PROPERTY_KEY = "user"; |
114 | |
115 | /** |
116 | * Gets the drivers major version number |
117 | * |
118 | * @return the drivers major version number |
119 | */ |
120 | static int getMajorVersionInternal() { |
121 | return safeIntParse("5"); //$NON-NLS-1$ |
122 | } |
123 | |
124 | /** |
125 | * Get the drivers minor version number |
126 | * |
127 | * @return the drivers minor version number |
128 | */ |
129 | static int getMinorVersionInternal() { |
130 | return safeIntParse("0"); //$NON-NLS-1$ |
131 | } |
132 | |
133 | /** |
134 | * Parses hostPortPair in the form of [host][:port] into an array, with the |
135 | * element of index HOST_NAME_INDEX being the host (or null if not |
136 | * specified), and the element of index PORT_NUMBER_INDEX being the port (or |
137 | * null if not specified). |
138 | * |
139 | * @param hostPortPair |
140 | * host and port in form of of [host][:port] |
141 | * |
142 | * @return array containing host and port as Strings |
143 | * |
144 | * @throws SQLException |
145 | * if a parse error occurs |
146 | */ |
147 | protected static String[] parseHostPortPair(String hostPortPair) |
148 | throws SQLException { |
149 | int portIndex = hostPortPair.indexOf(":"); //$NON-NLS-1$ |
150 | |
151 | String[] splitValues = new String[2]; |
152 | |
153 | String hostname = null; |
154 | |
155 | if (portIndex != -1) { |
156 | if ((portIndex + 1) < hostPortPair.length()) { |
157 | String portAsString = hostPortPair.substring(portIndex + 1); |
158 | hostname = hostPortPair.substring(0, portIndex); |
159 | |
160 | splitValues[HOST_NAME_INDEX] = hostname; |
161 | |
162 | splitValues[PORT_NUMBER_INDEX] = portAsString; |
163 | } else { |
164 | throw SQLError.createSQLException(Messages |
165 | .getString("NonRegisteringDriver.37"), //$NON-NLS-1$ |
166 | SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); |
167 | } |
168 | } else { |
169 | splitValues[HOST_NAME_INDEX] = hostPortPair; |
170 | splitValues[PORT_NUMBER_INDEX] = null; |
171 | } |
172 | |
173 | return splitValues; |
174 | } |
175 | |
176 | private static int safeIntParse(String intAsString) { |
177 | try { |
178 | return Integer.parseInt(intAsString); |
179 | } catch (NumberFormatException nfe) { |
180 | return 0; |
181 | } |
182 | } |
183 | |
184 | /** |
185 | * Construct a new driver and register it with DriverManager |
186 | * |
187 | * @throws SQLException |
188 | * if a database error occurs. |
189 | */ |
190 | public NonRegisteringDriver() throws SQLException { |
191 | // Required for Class.forName().newInstance() |
192 | } |
193 | |
194 | /** |
195 | * Typically, drivers will return true if they understand the subprotocol |
196 | * specified in the URL and false if they don't. This driver's protocols |
197 | * start with jdbc:mysql: |
198 | * |
199 | * @param url |
200 | * the URL of the driver |
201 | * |
202 | * @return true if this driver accepts the given URL |
203 | * |
204 | * @exception SQLException |
205 | * if a database-access error occurs |
206 | * |
207 | * @see java.sql.Driver#acceptsURL |
208 | */ |
209 | public boolean acceptsURL(String url) throws SQLException { |
210 | return (parseURL(url, null) != null); |
211 | } |
212 | |
213 | // |
214 | // return the database name property |
215 | // |
216 | |
217 | /** |
218 | * Try to make a database connection to the given URL. The driver should |
219 | * return "null" if it realizes it is the wrong kind of driver to connect to |
220 | * the given URL. This will be common, as when the JDBC driverManager is |
221 | * asked to connect to a given URL, it passes the URL to each loaded driver |
222 | * in turn. |
223 | * |
224 | * <p> |
225 | * The driver should raise an SQLException if it is the right driver to |
226 | * connect to the given URL, but has trouble connecting to the database. |
227 | * </p> |
228 | * |
229 | * <p> |
230 | * The java.util.Properties argument can be used to pass arbitrary string |
231 | * tag/value pairs as connection arguments. |
232 | * </p> |
233 | * |
234 | * <p> |
235 | * My protocol takes the form: |
236 | * |
237 | * <PRE> |
238 | * |
239 | * jdbc:mysql://host:port/database |
240 | * |
241 | * </PRE> |
242 | * |
243 | * </p> |
244 | * |
245 | * @param url |
246 | * the URL of the database to connect to |
247 | * @param info |
248 | * a list of arbitrary tag/value pairs as connection arguments |
249 | * |
250 | * @return a connection to the URL or null if it isnt us |
251 | * |
252 | * @exception SQLException |
253 | * if a database access error occurs |
254 | * |
255 | * @see java.sql.Driver#connect |
256 | */ |
257 | public java.sql.Connection connect(String url, Properties info) |
258 | throws SQLException { |
259 | Properties props = null; |
260 | |
261 | if ((props = parseURL(url, info)) == null) { |
262 | return null; |
263 | } |
264 | |
265 | try { |
266 | Connection newConn = new com.mysql.jdbc.Connection(host(props), |
267 | port(props), props, database(props), url); |
268 | |
269 | return newConn; |
270 | } catch (SQLException sqlEx) { |
271 | // Don't wrap SQLExceptions, throw |
272 | // them un-changed. |
273 | throw sqlEx; |
274 | } catch (Exception ex) { |
275 | throw SQLError.createSQLException(Messages |
276 | .getString("NonRegisteringDriver.17") //$NON-NLS-1$ |
277 | + ex.toString() |
278 | + Messages.getString("NonRegisteringDriver.18"), //$NON-NLS-1$ |
279 | SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE); |
280 | } |
281 | } |
282 | |
283 | /** |
284 | * Returns the database property from <code>props</code> |
285 | * |
286 | * @param props |
287 | * the Properties to look for the database property. |
288 | * |
289 | * @return the database name. |
290 | */ |
291 | public String database(Properties props) { |
292 | return props.getProperty(DBNAME_PROPERTY_KEY); //$NON-NLS-1$ |
293 | } |
294 | |
295 | /** |
296 | * Gets the drivers major version number |
297 | * |
298 | * @return the drivers major version number |
299 | */ |
300 | public int getMajorVersion() { |
301 | return getMajorVersionInternal(); |
302 | } |
303 | |
304 | /** |
305 | * Get the drivers minor version number |
306 | * |
307 | * @return the drivers minor version number |
308 | */ |
309 | public int getMinorVersion() { |
310 | return getMinorVersionInternal(); |
311 | } |
312 | |
313 | /** |
314 | * The getPropertyInfo method is intended to allow a generic GUI tool to |
315 | * discover what properties it should prompt a human for in order to get |
316 | * enough information to connect to a database. |
317 | * |
318 | * <p> |
319 | * Note that depending on the values the human has supplied so far, |
320 | * additional values may become necessary, so it may be necessary to iterate |
321 | * through several calls to getPropertyInfo |
322 | * </p> |
323 | * |
324 | * @param url |
325 | * the Url of the database to connect to |
326 | * @param info |
327 | * a proposed list of tag/value pairs that will be sent on |
328 | * connect open. |
329 | * |
330 | * @return An array of DriverPropertyInfo objects describing possible |
331 | * properties. This array may be an empty array if no properties are |
332 | * required |
333 | * |
334 | * @exception SQLException |
335 | * if a database-access error occurs |
336 | * |
337 | * @see java.sql.Driver#getPropertyInfo |
338 | */ |
339 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) |
340 | throws SQLException { |
341 | if (info == null) { |
342 | info = new Properties(); |
343 | } |
344 | |
345 | if ((url != null) && url.startsWith("jdbc:mysql://")) { //$NON-NLS-1$ |
346 | info = parseURL(url, info); |
347 | } |
348 | |
349 | DriverPropertyInfo hostProp = new DriverPropertyInfo(HOST_PROPERTY_KEY, //$NON-NLS-1$ |
350 | info.getProperty(HOST_PROPERTY_KEY)); //$NON-NLS-1$ |
351 | hostProp.required = true; |
352 | hostProp.description = Messages.getString("NonRegisteringDriver.3"); //$NON-NLS-1$ |
353 | |
354 | DriverPropertyInfo portProp = new DriverPropertyInfo(PORT_PROPERTY_KEY, //$NON-NLS-1$ |
355 | info.getProperty(PORT_PROPERTY_KEY, "3306")); //$NON-NLS-1$ //$NON-NLS-2$ |
356 | portProp.required = false; |
357 | portProp.description = Messages.getString("NonRegisteringDriver.7"); //$NON-NLS-1$ |
358 | |
359 | DriverPropertyInfo dbProp = new DriverPropertyInfo(DBNAME_PROPERTY_KEY, //$NON-NLS-1$ |
360 | info.getProperty(DBNAME_PROPERTY_KEY)); //$NON-NLS-1$ |
361 | dbProp.required = false; |
362 | dbProp.description = "Database name"; //$NON-NLS-1$ |
363 | |
364 | DriverPropertyInfo userProp = new DriverPropertyInfo(USER_PROPERTY_KEY, //$NON-NLS-1$ |
365 | info.getProperty(USER_PROPERTY_KEY)); //$NON-NLS-1$ |
366 | userProp.required = true; |
367 | userProp.description = Messages.getString("NonRegisteringDriver.13"); //$NON-NLS-1$ |
368 | |
369 | DriverPropertyInfo passwordProp = new DriverPropertyInfo( |
370 | PASSWORD_PROPERTY_KEY, //$NON-NLS-1$ |
371 | info.getProperty(PASSWORD_PROPERTY_KEY)); //$NON-NLS-1$ |
372 | passwordProp.required = true; |
373 | passwordProp.description = Messages |
374 | .getString("NonRegisteringDriver.16"); //$NON-NLS-1$ |
375 | |
376 | DriverPropertyInfo[] dpi = ConnectionProperties |
377 | .exposeAsDriverPropertyInfo(info, 5); |
378 | |
379 | dpi[0] = hostProp; |
380 | dpi[1] = portProp; |
381 | dpi[2] = dbProp; |
382 | dpi[3] = userProp; |
383 | dpi[4] = passwordProp; |
384 | |
385 | return dpi; |
386 | } |
387 | |
388 | // |
389 | // return the value of any property this driver knows about |
390 | // |
391 | |
392 | /** |
393 | * Returns the hostname property |
394 | * |
395 | * @param props |
396 | * the java.util.Properties instance to retrieve the hostname |
397 | * from. |
398 | * |
399 | * @return the hostname |
400 | */ |
401 | public String host(Properties props) { |
402 | return props.getProperty(HOST_PROPERTY_KEY, "localhost"); //$NON-NLS-1$ //$NON-NLS-2$ |
403 | } |
404 | |
405 | /** |
406 | * Report whether the driver is a genuine JDBC compliant driver. A driver |
407 | * may only report "true" here if it passes the JDBC compliance tests, |
408 | * otherwise it is required to return false. JDBC compliance requires full |
409 | * support for the JDBC API and full support for SQL 92 Entry Level. |
410 | * |
411 | * <p> |
412 | * MySQL is not SQL92 compliant |
413 | * </p> |
414 | * |
415 | * @return is this driver JDBC compliant? |
416 | */ |
417 | public boolean jdbcCompliant() { |
418 | return false; |
419 | } |
420 | |
421 | /** |
422 | * |
423 | * |
424 | * @param url |
425 | * ... |
426 | * @param defaults |
427 | * ... |
428 | * |
429 | * @return ... |
430 | * |
431 | * @throws java.sql.SQLException |
432 | * ... |
433 | */ |
434 | public Properties parseURL(String url, Properties defaults) |
435 | throws java.sql.SQLException { |
436 | Properties urlProps = (defaults != null) ? new Properties(defaults) |
437 | : new Properties(); |
438 | |
439 | if (url == null) { |
440 | return null; |
441 | } |
442 | |
443 | if (!StringUtils.startsWithIgnoreCase(url, "jdbc:mysql://") && |
444 | !StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:mxj://")) { //$NON-NLS-1$ |
445 | |
446 | return null; |
447 | } |
448 | |
449 | int beginningOfSlashes = 13; |
450 | |
451 | if (StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:mxj://")) { |
452 | beginningOfSlashes = 17; |
453 | |
454 | urlProps.setProperty("socketFactory", |
455 | "com.mysql.management.driverlaunched.ServerLauncherSocketFactory"); |
456 | } |
457 | |
458 | /* |
459 | * Parse parameters after the ? in the URL and remove them from the |
460 | * original URL. |
461 | */ |
462 | int index = url.indexOf("?"); //$NON-NLS-1$ |
463 | |
464 | if (index != -1) { |
465 | String paramString = url.substring(index + 1, url.length()); |
466 | url = url.substring(0, index); |
467 | |
468 | StringTokenizer queryParams = new StringTokenizer(paramString, "&"); //$NON-NLS-1$ |
469 | |
470 | while (queryParams.hasMoreTokens()) { |
471 | String parameterValuePair = queryParams.nextToken(); |
472 | |
473 | int indexOfEquals = StringUtils.indexOfIgnoreCase(0, |
474 | parameterValuePair, "="); |
475 | |
476 | String parameter = null; |
477 | String value = null; |
478 | |
479 | if (indexOfEquals != -1) { |
480 | parameter = parameterValuePair.substring(0, indexOfEquals); |
481 | |
482 | if (indexOfEquals + 1 < parameterValuePair.length()) { |
483 | value = parameterValuePair.substring(indexOfEquals + 1); |
484 | } |
485 | } |
486 | |
487 | if ((value != null && value.length() > 0) |
488 | && (parameter != null && parameter.length() > 0)) { |
489 | try { |
490 | urlProps.put(parameter, URLDecoder.decode(value, |
491 | "UTF-8")); |
492 | } catch (UnsupportedEncodingException badEncoding) { |
493 | // punt |
494 | urlProps.put(parameter, URLDecoder.decode(value)); |
495 | } catch (NoSuchMethodError nsme) { |
496 | // punt again |
497 | urlProps.put(parameter, URLDecoder.decode(value)); |
498 | } |
499 | } |
500 | } |
501 | } |
502 | |
503 | url = url.substring(beginningOfSlashes); |
504 | |
505 | String hostStuff = null; |
506 | |
507 | int slashIndex = url.indexOf("/"); //$NON-NLS-1$ |
508 | |
509 | if (slashIndex != -1) { |
510 | hostStuff = url.substring(0, slashIndex); |
511 | |
512 | if ((slashIndex + 1) < url.length()) { |
513 | urlProps.put(DBNAME_PROPERTY_KEY, //$NON-NLS-1$ |
514 | url.substring((slashIndex + 1), url.length())); |
515 | } |
516 | } else { |
517 | return null; |
518 | } |
519 | |
520 | if ((hostStuff != null) && (hostStuff.length() > 0)) { |
521 | urlProps.put(HOST_PROPERTY_KEY, hostStuff); //$NON-NLS-1$ |
522 | } |
523 | |
524 | String propertiesTransformClassName = urlProps |
525 | .getProperty(PROPERTIES_TRANSFORM_KEY); |
526 | |
527 | if (propertiesTransformClassName != null) { |
528 | try { |
529 | ConnectionPropertiesTransform propTransformer = (ConnectionPropertiesTransform) Class |
530 | .forName(propertiesTransformClassName).newInstance(); |
531 | |
532 | urlProps = propTransformer.transformProperties(urlProps); |
533 | } catch (InstantiationException e) { |
534 | throw SQLError.createSQLException( |
535 | "Unable to create properties transform instance '" |
536 | + propertiesTransformClassName |
537 | + "' due to underlying exception: " |
538 | + e.toString(), |
539 | SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); |
540 | } catch (IllegalAccessException e) { |
541 | throw SQLError.createSQLException( |
542 | "Unable to create properties transform instance '" |
543 | + propertiesTransformClassName |
544 | + "' due to underlying exception: " |
545 | + e.toString(), |
546 | SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); |
547 | } catch (ClassNotFoundException e) { |
548 | throw SQLError.createSQLException( |
549 | "Unable to create properties transform instance '" |
550 | + propertiesTransformClassName |
551 | + "' due to underlying exception: " |
552 | + e.toString(), |
553 | SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); |
554 | } |
555 | } |
556 | |
557 | // If we use a config, it actually should get overridden by anything in |
558 | // the URL or passed-in properties |
559 | |
560 | String configNames = null; |
561 | |
562 | if (defaults != null) { |
563 | configNames = defaults.getProperty(USE_CONFIG_PROPERTY_KEY); |
564 | } |
565 | |
566 | if (configNames == null) { |
567 | configNames = urlProps.getProperty(USE_CONFIG_PROPERTY_KEY); |
568 | } |
569 | |
570 | if (configNames != null) { |
571 | List splitNames = StringUtils.split(configNames, ",", true); |
572 | |
573 | Properties configProps = new Properties(); |
574 | |
575 | Iterator namesIter = splitNames.iterator(); |
576 | |
577 | while (namesIter.hasNext()) { |
578 | String configName = (String) namesIter.next(); |
579 | |
580 | try { |
581 | InputStream configAsStream = getClass() |
582 | .getResourceAsStream( |
583 | "configs/" + configName + ".properties"); |
584 | |
585 | if (configAsStream == null) { |
586 | throw SQLError.createSQLException( |
587 | "Can't find configuration template named '" |
588 | + configName + "'", |
589 | SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); |
590 | } |
591 | configProps.load(configAsStream); |
592 | } catch (IOException ioEx) { |
593 | throw SQLError.createSQLException( |
594 | "Unable to load configuration template '" |
595 | + configName |
596 | + "' due to underlying IOException: " |
597 | + ioEx, |
598 | SQLError.SQL_STATE_INVALID_CONNECTION_ATTRIBUTE); |
599 | } |
600 | } |
601 | |
602 | Iterator propsIter = urlProps.keySet().iterator(); |
603 | |
604 | while (propsIter.hasNext()) { |
605 | String key = propsIter.next().toString(); |
606 | String property = urlProps.getProperty(key); |
607 | configProps.setProperty(key, property); |
608 | } |
609 | |
610 | urlProps = configProps; |
611 | } |
612 | |
613 | // Properties passed in should override ones in URL |
614 | |
615 | if (defaults != null) { |
616 | Iterator propsIter = defaults.keySet().iterator(); |
617 | |
618 | while (propsIter.hasNext()) { |
619 | String key = propsIter.next().toString(); |
620 | String property = defaults.getProperty(key); |
621 | urlProps.setProperty(key, property); |
622 | } |
623 | } |
624 | |
625 | return urlProps; |
626 | } |
627 | |
628 | /** |
629 | * Returns the port number property |
630 | * |
631 | * @param props |
632 | * the properties to get the port number from |
633 | * |
634 | * @return the port number |
635 | */ |
636 | public int port(Properties props) { |
637 | return Integer.parseInt(props.getProperty(PORT_PROPERTY_KEY, "3306")); //$NON-NLS-1$ //$NON-NLS-2$ |
638 | } |
639 | |
640 | /** |
641 | * Returns the given property from <code>props</code> |
642 | * |
643 | * @param name |
644 | * the property name |
645 | * @param props |
646 | * the property instance to look in |
647 | * |
648 | * @return the property value, or null if not found. |
649 | */ |
650 | public String property(String name, Properties props) { |
651 | return props.getProperty(name); |
652 | } |
653 | } |