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