1 /*
  2  * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package java.sql;
 27 
 28 import java.util.ArrayList;
 29 import java.util.Collections;
 30 import java.util.Enumeration;
 31 import java.util.Iterator;
 32 import java.util.List;
 33 import java.util.ServiceLoader;
 34 import java.security.AccessController;
 35 import java.security.PrivilegedAction;
 36 import java.util.concurrent.CopyOnWriteArrayList;
 37 import java.util.stream.Stream;
 38 
 39 import jdk.internal.reflect.CallerSensitive;
 40 import jdk.internal.reflect.CallerSensitiveAdapter;
 41 import jdk.internal.reflect.Reflection;
 42 
 43 
 44 /**
 45  * The basic service for managing a set of JDBC drivers.
 46  * <p>
 47  * <strong>NOTE:</strong> The {@link javax.sql.DataSource} interface, provides
 48  * another way to connect to a data source.
 49  * The use of a {@code DataSource} object is the preferred means of
 50  * connecting to a data source.
 51  * <P>
 52  * As part of its initialization, the {@code DriverManager} class will
 53  * attempt to load available JDBC drivers by using:
 54  * <ul>
 55  * <li>The {@code jdbc.drivers} system property which contains a
 56  * colon separated list of fully qualified class names of JDBC drivers. Each
 57  * driver is loaded using the {@linkplain ClassLoader#getSystemClassLoader
 58  * system class loader}:
 59  * <ul>
 60  * <li>{@code jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.taste.ourDriver}
 61  * </ul>
 62  *
 63  * <li>Service providers of the {@code java.sql.Driver} class, that are loaded
 64  * via the {@linkplain ServiceLoader#load service-provider loading} mechanism.
 65  *</ul>
 66  *
 67  * @implNote
 68  * {@code DriverManager} initialization is done lazily and looks up service
 69  * providers using the thread context class loader.  The drivers loaded and
 70  * available to an application will depend on the thread context class loader of
 71  * the thread that triggers driver initialization by {@code DriverManager}.
 72  *
 73  * <P>When the method {@code getConnection} is called,
 74  * the {@code DriverManager} will attempt to
 75  * locate a suitable driver from amongst those loaded at
 76  * initialization and those loaded explicitly using the same class loader
 77  * as the current application.
 78  *
 79  * @see Driver
 80  * @see Connection
 81  * @since 1.1
 82  */
 83 public class DriverManager {
 84 
 85 
 86     // List of registered JDBC drivers
 87     private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
 88     private static volatile int loginTimeout = 0;
 89     private static volatile java.io.PrintWriter logWriter = null;
 90     private static volatile java.io.PrintStream logStream = null;
 91     // Used in println() to synchronize logWriter
 92     private static final Object logSync = new Object();
 93     // Used in ensureDriversInitialized() to synchronize driversInitialized
 94     private static final Object lockForInitDrivers = new Object();
 95     private static volatile boolean driversInitialized;
 96     private static final String JDBC_DRIVERS_PROPERTY = "jdbc.drivers";
 97 
 98     /* Prevent the DriverManager class from being instantiated. */
 99     private DriverManager(){}
100 
101     /**
102      * The {@code SQLPermission} constant that allows the
103      * setting of the logging stream.
104      * @since 1.3
105      */
106     static final SQLPermission SET_LOG_PERMISSION =
107         new SQLPermission("setLog");
108 
109     /**
110      * The {@code SQLPermission} constant that allows the
111      * un-register a registered JDBC driver.
112      * @since 1.8
113      */
114     static final SQLPermission DEREGISTER_DRIVER_PERMISSION =
115         new SQLPermission("deregisterDriver");
116 
117     //--------------------------JDBC 2.0-----------------------------
118 
119     /**
120      * Retrieves the log writer.
121      *
122      * The {@code getLogWriter} and {@code setLogWriter}
123      * methods should be used instead
124      * of the {@code get/setlogStream} methods, which are deprecated.
125      * @return a {@code java.io.PrintWriter} object
126      * @see #setLogWriter
127      * @since 1.2
128      */
129     public static java.io.PrintWriter getLogWriter() {
130             return logWriter;
131     }
132 
133     /**
134      * Sets the logging/tracing {@code PrintWriter} object
135      * that is used by the {@code DriverManager} and all drivers.
136      *<P>
137      * If a security manager exists, its {@code checkPermission}
138      * method is first called with a {@code SQLPermission("setLog")}
139      * permission to check that the caller is allowed to call {@code setLogWriter}.
140      *
141      * @param out the new logging/tracing {@code PrintStream} object;
142      *      {@code null} to disable logging and tracing
143      * @throws SecurityException if a security manager exists and its
144      * {@code checkPermission} method denies permission to set the log writer.
145      * @see SecurityManager#checkPermission
146      * @see #getLogWriter
147      * @since 1.2
148      */
149     public static void setLogWriter(java.io.PrintWriter out) {
150 
151         @SuppressWarnings("removal")
152         SecurityManager sec = System.getSecurityManager();
153         if (sec != null) {
154             sec.checkPermission(SET_LOG_PERMISSION);
155         }
156             logStream = null;
157             logWriter = out;
158     }
159 
160 
161     //---------------------------------------------------------------
162 
163     /**
164      * Attempts to establish a connection to the given database URL.
165      * The {@code DriverManager} attempts to select an appropriate driver from
166      * the set of registered JDBC drivers.
167      *<p>
168      * <B>Note:</B> If a property is specified as part of the {@code url} and
169      * is also specified in the {@code Properties} object, it is
170      * implementation-defined as to which value will take precedence.
171      * For maximum portability, an application should only specify a
172      * property once.
173      *
174      * @param url a database url of the form
175      * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
176      * @param info a list of arbitrary string tag/value pairs as
177      * connection arguments; normally at least a "user" and
178      * "password" property should be included
179      * @return a Connection to the URL
180      * @throws SQLException if a database access error occurs or the url is
181      * {@code null}
182      * @throws SQLTimeoutException  when the driver has determined that the
183      * timeout value specified by the {@code setLoginTimeout} method
184      * has been exceeded and has at least tried to cancel the
185      * current database connection attempt
186      */
187     @CallerSensitive
188     public static Connection getConnection(String url,
189         java.util.Properties info) throws SQLException {
190 
191         return (getConnection(url, info, Reflection.getCallerClass()));
192     }
193 
194     /**
195      * Attempts to establish a connection to the given database URL.
196      * The {@code DriverManager} attempts to select an appropriate driver from
197      * the set of registered JDBC drivers.
198      *<p>
199      * <B>Note:</B> If the {@code user} or {@code password} property are
200      * also specified as part of the {@code url}, it is
201      * implementation-defined as to which value will take precedence.
202      * For maximum portability, an application should only specify a
203      * property once.
204      *
205      * @param url a database url of the form
206      * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
207      * @param user the database user on whose behalf the connection is being
208      *   made
209      * @param password the user's password
210      * @return a connection to the URL
211      * @throws SQLException if a database access error occurs or the url is
212      * {@code null}
213      * @throws SQLTimeoutException  when the driver has determined that the
214      * timeout value specified by the {@code setLoginTimeout} method
215      * has been exceeded and has at least tried to cancel the
216      * current database connection attempt
217      */
218     @CallerSensitive
219     public static Connection getConnection(String url,
220         String user, String password) throws SQLException {
221         java.util.Properties info = new java.util.Properties();
222 
223         if (user != null) {
224             info.put("user", user);
225         }
226         if (password != null) {
227             info.put("password", password);
228         }
229 
230         return (getConnection(url, info, Reflection.getCallerClass()));
231     }
232 
233     /**
234      * Attempts to establish a connection to the given database URL.
235      * The {@code DriverManager} attempts to select an appropriate driver from
236      * the set of registered JDBC drivers.
237      *
238      * @param url a database url of the form
239      *  <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
240      * @return a connection to the URL
241      * @throws SQLException if a database access error occurs or the url is
242      * {@code null}
243      * @throws SQLTimeoutException  when the driver has determined that the
244      * timeout value specified by the {@code setLoginTimeout} method
245      * has been exceeded and has at least tried to cancel the
246      * current database connection attempt
247      */
248     @CallerSensitive
249     public static Connection getConnection(String url)
250         throws SQLException {
251 
252         java.util.Properties info = new java.util.Properties();
253         return (getConnection(url, info, Reflection.getCallerClass()));
254     }
255 
256     /**
257      * Attempts to locate a driver that understands the given URL.
258      * The {@code DriverManager} attempts to select an appropriate driver from
259      * the set of registered JDBC drivers.
260      *
261      * @param url a database URL of the form
262      *     <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
263      * @return a {@code Driver} object representing a driver
264      * that can connect to the given URL
265      * @throws SQLException if a database access error occurs
266      */
267     @CallerSensitive
268     public static Driver getDriver(String url)
269         throws SQLException {
270 
271         println("DriverManager.getDriver(\"" + url + "\")");
272 
273         ensureDriversInitialized();
274 
275         Class<?> callerClass = Reflection.getCallerClass();
276 
277         // Walk through the loaded registeredDrivers attempting to locate someone
278         // who understands the given URL.
279         for (DriverInfo aDriver : registeredDrivers) {
280             // If the caller does not have permission to load the driver then
281             // skip it.
282             if (isDriverAllowed(aDriver.driver, callerClass)) {
283                 try {
284                     if (aDriver.driver.acceptsURL(url)) {
285                         // Success!
286                         println("getDriver returning " + aDriver.driver.getClass().getName());
287                     return (aDriver.driver);
288                     }
289 
290                 } catch(SQLException sqe) {
291                     // Drop through and try the next driver.
292                 }
293             } else {
294                 println("    skipping: " + aDriver.driver.getClass().getName());
295             }
296 
297         }
298 
299         println("getDriver: no suitable driver");
300         throw new SQLException("No suitable driver", "08001");
301     }
302 
303 
304     /**
305      * Registers the given driver with the {@code DriverManager}.
306      * A newly-loaded driver class should call
307      * the method {@code registerDriver} to make itself
308      * known to the {@code DriverManager}. If the driver is currently
309      * registered, no action is taken.
310      *
311      * @param driver the new JDBC Driver that is to be registered with the
312      *               {@code DriverManager}
313      * @throws SQLException if a database access error occurs
314      * @throws NullPointerException if {@code driver} is null
315      */
316     public static void registerDriver(java.sql.Driver driver)
317         throws SQLException {
318 
319         registerDriver(driver, null);
320     }
321 
322     /**
323      * Registers the given driver with the {@code DriverManager}.
324      * A newly-loaded driver class should call
325      * the method {@code registerDriver} to make itself
326      * known to the {@code DriverManager}. If the driver is currently
327      * registered, no action is taken.
328      *
329      * @param driver the new JDBC Driver that is to be registered with the
330      *               {@code DriverManager}
331      * @param da     the {@code DriverAction} implementation to be used when
332      *               {@code DriverManager#deregisterDriver} is called
333      * @throws SQLException if a database access error occurs
334      * @throws NullPointerException if {@code driver} is null
335      * @since 1.8
336      */
337     public static void registerDriver(java.sql.Driver driver,
338             DriverAction da)
339         throws SQLException {
340 
341         /* Register the driver if it has not already been added to our list */
342         if (driver != null) {
343             registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
344         } else {
345             // This is for compatibility with the original DriverManager
346             throw new NullPointerException();
347         }
348 
349         println("registerDriver: " + driver);
350 
351     }
352 
353     /**
354      * Removes the specified driver from the {@code DriverManager}'s list of
355      * registered drivers.
356      * <p>
357      * If a {@code null} value is specified for the driver to be removed, then no
358      * action is taken.
359      * <p>
360      * If a security manager exists, its {@code checkPermission}
361      * method is first called with a {@code SQLPermission("deregisterDriver")}
362      * permission to check that the caller is allowed to deregister a JDBC Driver.
363      * <p>
364      * If the specified driver is not found in the list of registered drivers,
365      * then no action is taken.  If the driver was found, it will be removed
366      * from the list of registered drivers.
367      * <p>
368      * If a {@code DriverAction} instance was specified when the JDBC driver was
369      * registered, its deregister method will be called
370      * prior to the driver being removed from the list of registered drivers.
371      *
372      * @param driver the JDBC Driver to remove
373      * @throws SQLException if a database access error occurs
374      * @throws SecurityException if a security manager exists and its
375      * {@code checkPermission} method denies permission to deregister a driver.
376      *
377      * @see SecurityManager#checkPermission
378      */
379     @CallerSensitive
380     public static void deregisterDriver(Driver driver) throws SQLException {
381         if (driver == null) {
382             return;
383         }
384 
385         @SuppressWarnings("removal")
386         SecurityManager sec = System.getSecurityManager();
387         if (sec != null) {
388             sec.checkPermission(DEREGISTER_DRIVER_PERMISSION);
389         }
390 
391         println("DriverManager.deregisterDriver: " + driver);
392 
393         DriverInfo aDriver = new DriverInfo(driver, null);
394         synchronized (lockForInitDrivers) {
395             if (registeredDrivers.contains(aDriver)) {
396                 if (isDriverAllowed(driver, Reflection.getCallerClass())) {
397                     DriverInfo di = registeredDrivers.get(registeredDrivers.indexOf(aDriver));
398                      // If a DriverAction was specified, Call it to notify the
399                      // driver that it has been deregistered
400                      if (di.action() != null) {
401                          di.action().deregister();
402                      }
403                      registeredDrivers.remove(aDriver);
404                 } else {
405                     // If the caller does not have permission to load the driver then
406                     // throw a SecurityException.
407                     throw new SecurityException();
408                 }
409             } else {
410                 println("    couldn't find driver to unload");
411             }
412         }
413     }
414 
415     /**
416      * Retrieves an Enumeration with all of the currently loaded JDBC drivers
417      * to which the current caller has access.
418      *
419      * <P><B>Note:</B> The classname of a driver can be found using
420      * {@code d.getClass().getName()}
421      *
422      * @return the list of JDBC Drivers loaded by the caller's class loader
423      * @see #drivers()
424      */
425     @CallerSensitive
426     public static Enumeration<Driver> getDrivers() {
427         ensureDriversInitialized();
428 
429         return Collections.enumeration(getDrivers(Reflection.getCallerClass()));
430     }
431 
432     /**
433      * Retrieves a Stream with all of the currently loaded JDBC drivers
434      * to which the current caller has access.
435      *
436      * @return the stream of JDBC Drivers loaded by the caller's class loader
437      * @since 9
438      */
439     @CallerSensitive
440     public static Stream<Driver> drivers() {
441         ensureDriversInitialized();
442 
443         return getDrivers(Reflection.getCallerClass()).stream();
444     }
445 
446     private static List<Driver> getDrivers(Class<?> callerClass) {
447         List<Driver> result = new ArrayList<>();
448         // Walk through the loaded registeredDrivers.
449         for (DriverInfo aDriver : registeredDrivers) {
450             // If the caller does not have permission to load the driver then
451             // skip it.
452             if (isDriverAllowed(aDriver.driver, callerClass)) {
453                 result.add(aDriver.driver);
454             } else {
455                 println("    skipping: " + aDriver.driver.getClass().getName());
456             }
457         }
458         return result;
459     }
460 
461     /**
462      * Sets the maximum time in seconds that a driver will wait
463      * while attempting to connect to a database once the driver has
464      * been identified.
465      *
466      * @param seconds the login time limit in seconds; zero means there is no limit
467      * @see #getLoginTimeout
468      */
469     public static void setLoginTimeout(int seconds) {
470         loginTimeout = seconds;
471     }
472 
473     /**
474      * Gets the maximum time in seconds that a driver can wait
475      * when attempting to log in to a database.
476      *
477      * @return the driver login time limit in seconds
478      * @see #setLoginTimeout
479      */
480     public static int getLoginTimeout() {
481         return (loginTimeout);
482     }
483 
484     /**
485      * Sets the logging/tracing PrintStream that is used
486      * by the {@code DriverManager}
487      * and all drivers.
488      *<P>
489      * If a security manager exists, its {@code checkPermission}
490      * method is first called with a {@code SQLPermission("setLog")}
491      * permission to check that the caller is allowed to call {@code setLogStream}.
492      *
493      * @param out the new logging/tracing PrintStream; to disable, set to {@code null}
494      * @deprecated Use {@code setLogWriter}
495      * @throws SecurityException if a security manager exists and its
496      * {@code checkPermission} method denies permission to set the log stream.
497      * @see SecurityManager#checkPermission
498      * @see #getLogStream
499      */
500     @Deprecated(since="1.2")
501     public static void setLogStream(java.io.PrintStream out) {
502 
503         @SuppressWarnings("removal")
504         SecurityManager sec = System.getSecurityManager();
505         if (sec != null) {
506             sec.checkPermission(SET_LOG_PERMISSION);
507         }
508 
509         logStream = out;
510         if ( out != null )
511             logWriter = new java.io.PrintWriter(out);
512         else
513             logWriter = null;
514     }
515 
516     /**
517      * Retrieves the logging/tracing PrintStream that is used by the {@code DriverManager}
518      * and all drivers.
519      *
520      * @return the logging/tracing PrintStream; if disabled, is {@code null}
521      * @deprecated  Use {@code getLogWriter}
522      * @see #setLogStream
523      */
524     @Deprecated(since="1.2")
525     public static java.io.PrintStream getLogStream() {
526         return logStream;
527     }
528 
529     /**
530      * Prints a message to the current JDBC log stream.
531      *
532      * @param message a log or tracing message
533      */
534     public static void println(String message) {
535         synchronized (logSync) {
536             if (logWriter != null) {
537                 logWriter.println(message);
538 
539                 // automatic flushing is never enabled, so we must do it ourselves
540                 logWriter.flush();
541             }
542         }
543     }
544 
545     //------------------------------------------------------------------------
546 
547     // Indicates whether the class object that would be created if the code calling
548     // DriverManager is accessible.
549     private static boolean isDriverAllowed(Driver driver, Class<?> caller) {
550         ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
551         return isDriverAllowed(driver, callerCL);
552     }
553 
554     private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
555         boolean result = false;
556         if (driver != null) {
557             Class<?> aClass = null;
558             try {
559                 aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
560             } catch (Exception ex) {
561                 result = false;
562             }
563 
564              result = ( aClass == driver.getClass() ) ? true : false;
565         }
566 
567         return result;
568     }
569 
570     /*
571      * Load the initial JDBC drivers by checking the System property
572      * jdbc.drivers and then use the {@code ServiceLoader} mechanism
573      */
574     @SuppressWarnings("removal")
575     private static void ensureDriversInitialized() {
576         if (driversInitialized) {
577             return;
578         }
579 
580         synchronized (lockForInitDrivers) {
581             if (driversInitialized) {
582                 return;
583             }
584             String drivers;
585             try {
586                 drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
587                     public String run() {
588                         return System.getProperty(JDBC_DRIVERS_PROPERTY);
589                     }
590                 });
591             } catch (Exception ex) {
592                 drivers = null;
593             }
594             // If the driver is packaged as a Service Provider, load it.
595             // Get all the drivers through the classloader
596             // exposed as a java.sql.Driver.class service.
597             // ServiceLoader.load() replaces the sun.misc.Providers()
598 
599             AccessController.doPrivileged(new PrivilegedAction<Void>() {
600                 public Void run() {
601 
602                     ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
603                     Iterator<Driver> driversIterator = loadedDrivers.iterator();
604 
605                     /* Load these drivers, so that they can be instantiated.
606                      * It may be the case that the driver class may not be there
607                      * i.e. there may be a packaged driver with the service class
608                      * as implementation of java.sql.Driver but the actual class
609                      * may be missing. In that case a java.util.ServiceConfigurationError
610                      * will be thrown at runtime by the VM trying to locate
611                      * and load the service.
612                      *
613                      * Adding a try catch block to catch those runtime errors
614                      * if driver not available in classpath but it's
615                      * packaged as service and that service is there in classpath.
616                      */
617                     try {
618                         while (driversIterator.hasNext()) {
619                             driversIterator.next();
620                         }
621                     } catch (Throwable t) {
622                         // Do nothing
623                     }
624                     return null;
625                 }
626             });
627 
628             println("DriverManager.initialize: jdbc.drivers = " + drivers);
629 
630             if (drivers != null && !drivers.isEmpty()) {
631                 String[] driversList = drivers.split(":");
632                 println("number of Drivers:" + driversList.length);
633                 for (String aDriver : driversList) {
634                     try {
635                         println("DriverManager.Initialize: loading " + aDriver);
636                         Class.forName(aDriver, true,
637                                 ClassLoader.getSystemClassLoader());
638                     } catch (Exception ex) {
639                         println("DriverManager.Initialize: load failed: " + ex);
640                     }
641                 }
642             }
643 
644             driversInitialized = true;
645             println("JDBC DriverManager initialized");
646         }
647     }
648 
649 
650     //  Worker method called by the public getConnection() methods.
651     @CallerSensitiveAdapter
652     private static Connection getConnection(
653         String url, java.util.Properties info, Class<?> caller) throws SQLException {
654         /*
655          * When callerCl is null, we should check the application's
656          * (which is invoking this class indirectly)
657          * classloader, so that the JDBC driver class outside rt.jar
658          * can be loaded from here.
659          */
660         ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
661         if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
662             callerCL = Thread.currentThread().getContextClassLoader();
663         }
664 
665         if (url == null) {
666             throw new SQLException("The url cannot be null", "08001");
667         }
668 
669         println("DriverManager.getConnection(\"" + url + "\")");
670 
671         ensureDriversInitialized();
672 
673         // Walk through the loaded registeredDrivers attempting to make a connection.
674         // Remember the first exception that gets raised so we can reraise it.
675         SQLException reason = null;
676 
677         for (DriverInfo aDriver : registeredDrivers) {
678             // If the caller does not have permission to load the driver then
679             // skip it.
680             if (isDriverAllowed(aDriver.driver, callerCL)) {
681                 try {
682                     println("    trying " + aDriver.driver.getClass().getName());
683                     Connection con = aDriver.driver.connect(url, info);
684                     if (con != null) {
685                         // Success!
686                         println("getConnection returning " + aDriver.driver.getClass().getName());
687                         return (con);
688                     }
689                 } catch (SQLException ex) {
690                     if (reason == null) {
691                         reason = ex;
692                     }
693                 }
694 
695             } else {
696                 println("    skipping: " + aDriver.driver.getClass().getName());
697             }
698 
699         }
700 
701         // if we got here nobody could connect.
702         if (reason != null)    {
703             println("getConnection failed: " + reason);
704             throw reason;
705         }
706 
707         println("getConnection: no suitable driver found for "+ url);
708         throw new SQLException("No suitable driver found for "+ url, "08001");
709     }
710 
711 
712 }
713 
714 /*
715  * Wrapper class for registered Drivers in order to not expose Driver.equals()
716  * to avoid the capture of the Driver it being compared to as it might not
717  * normally have access.
718  */
719 class DriverInfo {
720 
721     final Driver driver;
722     DriverAction da;
723     DriverInfo(Driver driver, DriverAction action) {
724         this.driver = driver;
725         da = action;
726     }
727 
728     @Override
729     public boolean equals(Object other) {
730         return (other instanceof DriverInfo)
731                 && this.driver == ((DriverInfo) other).driver;
732     }
733 
734     @Override
735     public int hashCode() {
736         return driver.hashCode();
737     }
738 
739     @Override
740     public String toString() {
741         return ("driver[className="  + driver + "]");
742     }
743 
744     DriverAction action() {
745         return da;
746     }
747 }