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