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
26package java.sql;
27
28import java.util.ArrayList;
29import java.util.Collections;
30import java.util.Enumeration;
31import java.util.Iterator;
32import java.util.List;
33import java.util.ServiceLoader;
34import java.security.AccessController;
35import java.security.PrivilegedAction;
36import java.util.concurrent.CopyOnWriteArrayList;
37import java.util.stream.Stream;
38
39import jdk.internal.reflect.CallerSensitive;
40import 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 */
83public 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 */
714class 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}
743