content: /*
   * Copyright (C) 2014 The Android Open Source Project
   * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package java.sql;
  
  import java.util.Iterator;
  import java.util.ServiceLoader;
  import java.security.AccessController;
  import java.security.PrivilegedAction;
  import java.util.concurrent.CopyOnWriteArrayList;
  
  import sun.reflect.CallerSensitive;
  import sun.reflect.Reflection;
  
  // Android-changed line 2 of the javadoc to "{@code DataSource}".
  
  /**
   * <P>The basic service for managing a set of JDBC drivers.<br>
   * <B>NOTE:</B> The {@code DataSource} interface, new in the
   * JDBC 2.0 API, provides another way to connect to a data source.
   * The use of a <code>DataSource</code> object is the preferred means of
   * connecting to a data source.
   *
   * <P>As part of its initialization, the <code>DriverManager</code> class will
   * attempt to load the driver classes referenced in the "jdbc.drivers"
   * system property. This allows a user to customize the JDBC Drivers
   * used by their applications. For example in your
   * ~/.hotjava/properties file you might specify:
   * <pre>
   * <CODE>jdbc.drivers=foo.bah.Driver:wombat.sql.Driver:bad.taste.ourDriver</CODE>
   * </pre>
   *<P> The <code>DriverManager</code> methods <code>getConnection</code> and
   * <code>getDrivers</code> have been enhanced to support the Java Standard Edition
   * <a href="../../../technotes/guides/jar/jar.html#Service%20Provider">Service Provider</a> mechanism. JDBC 4.0 Drivers must
   * include the file <code>META-INF/services/java.sql.Driver</code>. This file contains the name of the JDBC drivers
   * implementation of <code>java.sql.Driver</code>.  For example, to load the <code>my.sql.Driver</code> class,
   * the <code>META-INF/services/java.sql.Driver</code> file would contain the entry:
   * <pre>
   * <code>my.sql.Driver</code>
   * </pre>
   *
   * <P>Applications no longer need to explictly load JDBC drivers using <code>Class.forName()</code>. Existing programs
   * which currently load JDBC drivers using <code>Class.forName()</code> will continue to work without
   * modification.
   *
   * <P>When the method <code>getConnection</code> is called,
   * the <code>DriverManager</code> will attempt to
   * locate a suitable driver from amongst those loaded at
   * initialization and those loaded explicitly using the same classloader
   * as the current applet or application.
   *
   * <P>
   * Starting with the Java 2 SDK, Standard Edition, version 1.3, a
   * logging stream can be set only if the proper
   * permission has been granted.  Normally this will be done with
   * the tool PolicyTool, which can be used to grant <code>permission
   * java.sql.SQLPermission "setLog"</code>.
   * @see Driver
   * @see Connection
   */
  public class DriverManager {
  
  
      // List of registered JDBC drivers
      private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<DriverInfo>();
      private static volatile int loginTimeout = 0;
      private static volatile java.io.PrintWriter logWriter = null;
      private static volatile java.io.PrintStream logStream = null;
      // Used in println() to synchronize logWriter
      private final static  Object logSync = new Object();
  
      /* Prevent the DriverManager class from being instantiated. */
      private DriverManager(){}
  
  
      /**
       * Load the initial JDBC drivers by checking the System property
       * jdbc.properties and then use the {@code ServiceLoader} mechanism
       */
      static {
          loadInitialDrivers();
          println("JDBC DriverManager initialized");
      }
  
      /**
       * The <code>SQLPermission</code> constant that allows the
       * setting of the logging stream.
       * @since 1.3
       */
      final static SQLPermission SET_LOG_PERMISSION =
          new SQLPermission("setLog");
  
      //--------------------------JDBC 2.0-----------------------------
  
      /**
       * Retrieves the log writer.
       *
       * The <code>getLogWriter</code> and <code>setLogWriter</code>
       * methods should be used instead
       * of the <code>get/setlogStream</code> methods, which are deprecated.
       * @return a <code>java.io.PrintWriter</code> object
       * @see #setLogWriter
       * @since 1.2
       */
      public static java.io.PrintWriter getLogWriter() {
              return logWriter;
      }
  
      /**
       * Sets the logging/tracing <code>PrintWriter</code> object
       * that is used by the <code>DriverManager</code> and all drivers.
       * <P>
       * There is a minor versioning problem created by the introduction
       * of the method <code>setLogWriter</code>.  The
       * method <code>setLogWriter</code> cannot create a <code>PrintStream</code> object
       * that will be returned by <code>getLogStream</code>---the Java platform does
       * not provide a backward conversion.  As a result, a new application
       * that uses <code>setLogWriter</code> and also uses a JDBC 1.0 driver that uses
       * <code>getLogStream</code> will likely not see debugging information written
       * by that driver.
       *<P>
       * Starting with the Java 2 SDK, Standard Edition, version 1.3 release, this method checks
       * to see that there is an <code>SQLPermission</code> object before setting
       * the logging stream.  If a <code>SecurityManager</code> exists and its
       * <code>checkPermission</code> method denies setting the log writer, this
       * method throws a <code>java.lang.SecurityException</code>.
       *
       * @param out the new logging/tracing <code>PrintStream</code> object;
       *      <code>null</code> to disable logging and tracing
       * @throws SecurityException
       *    if a security manager exists and its
       *    <code>checkPermission</code> method denies
       *    setting the log writer
       *
       * @see SecurityManager#checkPermission
       * @see #getLogWriter
       * @since 1.2
       */
      public static void setLogWriter(java.io.PrintWriter out) {
  
          SecurityManager sec = System.getSecurityManager();
          if (sec != null) {
              sec.checkPermission(SET_LOG_PERMISSION);
          }
              logStream = null;
              logWriter = out;
      }
  
  
      //---------------------------------------------------------------
  
      /**
       * Attempts to establish a connection to the given database URL.
       * The <code>DriverManager</code> attempts to select an appropriate driver from
       * the set of registered JDBC drivers.
       *
       * @param url a database url of the form
       * <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
       * @param info a list of arbitrary string tag/value pairs as
       * connection arguments; normally at least a "user" and
       * "password" property should be included
       * @return a Connection to the URL
       * @exception SQLException if a database access error occurs
       */
      @CallerSensitive
      public static Connection getConnection(String url,
          java.util.Properties info) throws SQLException {
          return (getConnection(url, info, Reflection.getCallerClass()));
      }
  
      /**
       * Attempts to establish a connection to the given database URL.
       * The <code>DriverManager</code> attempts to select an appropriate driver from
       * the set of registered JDBC drivers.
       *
       * @param url a database url of the form
       * <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
       * @param user the database user on whose behalf the connection is being
       *   made
       * @param password the user's password
       * @return a connection to the URL
       * @exception SQLException if a database access error occurs
       */
      @CallerSensitive
      public static Connection getConnection(String url,
          String user, String password) throws SQLException {
          java.util.Properties info = new java.util.Properties();
  
          if (user != null) {
              info.put("user", user);
          }
          if (password != null) {
              info.put("password", password);
          }
  
          return (getConnection(url, info, Reflection.getCallerClass()));
      }
  
      /**
       * Attempts to establish a connection to the given database URL.
       * The <code>DriverManager</code> attempts to select an appropriate driver from
       * the set of registered JDBC drivers.
       *
       * @param url a database url of the form
       *  <code> jdbc:<em>subprotocol</em>:<em>subname</em></code>
       * @return a connection to the URL
       * @exception SQLException if a database access error occurs
       */
      @CallerSensitive
      public static Connection getConnection(String url)
          throws SQLException {
  
          java.util.Properties info = new java.util.Properties();
          return (getConnection(url, info, Reflection.getCallerClass()));
      }
  
      /**
       * Attempts to locate a driver that understands the given URL.
       * The <code>DriverManager</code> attempts to select an appropriate driver from
       * the set of registered JDBC drivers.
       *
       * @param url a database URL of the form
       *     <code>jdbc:<em>subprotocol</em>:<em>subname</em></code>
       * @return a <code>Driver</code> object representing a driver
       * that can connect to the given URL
       * @exception SQLException if a database access error occurs
       */
      @CallerSensitive
      public static Driver getDriver(String url)
          throws SQLException {
  
          println("DriverManager.getDriver(\"" + url + "\")");
  
          Class<?> callerClass = Reflection.getCallerClass();
  
          // Walk through the loaded registeredDrivers attempting to locate someone
          // who understands the given URL.
          for (DriverInfo aDriver : registeredDrivers) {
              // If the caller does not have permission to load the driver then
              // skip it.
              if(isDriverAllowed(aDriver.driver, callerClass)) {
                  try {
                      if(aDriver.driver.acceptsURL(url)) {
                          // Success!
                          println("getDriver returning " + aDriver.driver.getClass().getName());
                      return (aDriver.driver);
                      }
  
                  } catch(SQLException sqe) {
                      // Drop through and try the next driver.
                  }
              } else {
                  println("    skipping: " + aDriver.driver.getClass().getName());
              }
  
          }
  
          println("getDriver: no suitable driver");
          throw new SQLException("No suitable driver", "08001");
      }
  
  
      /**
       * Registers the given driver with the <code>DriverManager</code>.
       * A newly-loaded driver class should call
       * the method <code>registerDriver</code> to make itself
       * known to the <code>DriverManager</code>.
       *
       * @param driver the new JDBC Driver that is to be registered with the
       *               <code>DriverManager</code>
       * @exception SQLException if a database access error occurs
       */
      public static synchronized void registerDriver(Driver driver)
          throws SQLException {
  
          /* Register the driver if it has not already been added to our list */
          if(driver != null) {
              registeredDrivers.addIfAbsent(new DriverInfo(driver));
          } else {
              // This is for compatibility with the original DriverManager
              throw new NullPointerException();
          }
  
          println("registerDriver: " + driver);
  
      }
  
      /**
       * Drops a driver from the <code>DriverManager</code>'s list.
       *  Applets can only deregister drivers from their own classloaders.
       *
       * @param driver the JDBC Driver to drop
       * @exception SQLException if a database access error occurs
       */
      @CallerSensitive
      public static synchronized void deregisterDriver(Driver driver)
          throws SQLException {
          if (driver == null) {
              return;
          }
  
          println("DriverManager.deregisterDriver: " + driver);
  
          DriverInfo aDriver = new DriverInfo(driver);
          if(registeredDrivers.contains(aDriver)) {
              if (isDriverAllowed(driver, Reflection.getCallerClass())) {
                   registeredDrivers.remove(aDriver);
              } else {
                  // If the caller does not have permission to load the driver then
                  // throw a SecurityException.
                  throw new SecurityException();
              }
          } else {
              println("    couldn't find driver to unload");
          }
      }
  
      /**
       * Retrieves an Enumeration with all of the currently loaded JDBC drivers
       * to which the current caller has access.
       *
       * <P><B>Note:</B> The classname of a driver can be found using
       * <CODE>d.getClass().getName()</CODE>
       *
       * @return the list of JDBC Drivers loaded by the caller's class loader
       */
      @CallerSensitive
      public static java.util.Enumeration<Driver> getDrivers() {
          java.util.Vector<Driver> result = new java.util.Vector<Driver>();
  
          Class<?> callerClass = Reflection.getCallerClass();
  
          // Walk through the loaded registeredDrivers.
          for(DriverInfo aDriver : registeredDrivers) {
              // If the caller does not have permission to load the driver then
              // skip it.
              if(isDriverAllowed(aDriver.driver, callerClass)) {
                  result.addElement(aDriver.driver);
              } else {
                  println("    skipping: " + aDriver.getClass().getName());
              }
          }
          return (result.elements());
      }
  
  
      /**
       * Sets the maximum time in seconds that a driver will wait
       * while attempting to connect to a database.
       *
       * @param seconds the login time limit in seconds; zero means there is no limit
       * @see #getLoginTimeout
       */
      public static void setLoginTimeout(int seconds) {
          loginTimeout = seconds;
      }
  
      /**
       * Gets the maximum time in seconds that a driver can wait
       * when attempting to log in to a database.
       *
       * @return the driver login time limit in seconds
       * @see #setLoginTimeout
       */
      public static int getLoginTimeout() {
          return (loginTimeout);
      }
  
      // Android-changed: Added reason to @deprecated to improve the documentation.
      /**
       * Sets the logging/tracing PrintStream that is used
       * by the <code>DriverManager</code>
       * and all drivers.
       *<P>
       * In the Java 2 SDK, Standard Edition, version 1.3 release, this method checks
       * to see that there is an <code>SQLPermission</code> object before setting
       * the logging stream.  If a <code>SecurityManager</code> exists and its
       * <code>checkPermission</code> method denies setting the log writer, this
       * method throws a <code>java.lang.SecurityException</code>.
       *
       * @param out the new logging/tracing PrintStream; to disable, set to <code>null</code>
       * @deprecated Use {@code setLogWriter} instead.
       * @throws SecurityException if a security manager exists and its
       *    <code>checkPermission</code> method denies setting the log stream
       *
       * @see SecurityManager#checkPermission
       * @see #getLogStream
       */
      // Android-added: @Deprecated annotation from OpenJDK8u121-b13 to fix build warnings.
      @Deprecated
      public static void setLogStream(java.io.PrintStream out) {
  
          SecurityManager sec = System.getSecurityManager();
          if (sec != null) {
              sec.checkPermission(SET_LOG_PERMISSION);
          }
  
          logStream = out;
          if ( out != null )
              logWriter = new java.io.PrintWriter(out);
          else
              logWriter = null;
      }
  
      // Android-changed: Added reason to @deprecated to improve the documentation.
      /**
       * Retrieves the logging/tracing PrintStream that is used by the <code>DriverManager</code>
       * and all drivers.
       *
       * @return the logging/tracing PrintStream; if disabled, is <code>null</code>
       * @deprecated Use {@code getLogWriter} instead.
       * @see #setLogStream
       */
      // Android-added: @Deprecated annotation from OpenJDK8u121-b13 to fix build warnings.
      @Deprecated
      public static java.io.PrintStream getLogStream() {
          return logStream;
      }
  
      /**
       * Prints a message to the current JDBC log stream.
       *
       * @param message a log or tracing message
       */
      public static void println(String message) {
          synchronized (logSync) {
              if (logWriter != null) {
                  logWriter.println(message);
  
                  // automatic flushing is never enabled, so we must do it ourselves
                  logWriter.flush();
              }
          }
      }
  
      //------------------------------------------------------------------------
  
      // Indicates whether the class object that would be created if the code calling
      // DriverManager is accessible.
      private static boolean isDriverAllowed(Driver driver, Class<?> caller) {
          ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
          return isDriverAllowed(driver, callerCL);
      }
  
      private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
          boolean result = false;
          if(driver != null) {
              Class<?> aClass = null;
              try {
                  aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
              } catch (Exception ex) {
                  result = false;
              }
  
               result = ( aClass == driver.getClass() ) ? true : false;
          }
  
          return result;
      }
  
      private static void loadInitialDrivers() {
          String drivers;
          try {
              drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                  public String run() {
                      return System.getProperty("jdbc.drivers");
                  }
              });
          } catch (Exception ex) {
              drivers = null;
          }
          // If the driver is packaged as a Service Provider, load it.
          // Get all the drivers through the classloader
          // exposed as a java.sql.Driver.class service.
          // ServiceLoader.load() replaces the sun.misc.Providers()
  
          AccessController.doPrivileged(new PrivilegedAction<Void>() {
              public Void run() {
  
                  ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                  Iterator driversIterator = loadedDrivers.iterator();
  
                  /* Load these drivers, so that they can be instantiated.
                   * It may be the case that the driver class may not be there
                   * i.e. there may be a packaged driver with the service class
                   * as implementation of java.sql.Driver but the actual class
                   * may be missing. In that case a java.util.ServiceConfigurationError
                   * will be thrown at runtime by the VM trying to locate
                   * and load the service.
                   *
                   * Adding a try catch block to catch those runtime errors
                   * if driver not available in classpath but it's
                   * packaged as service and that service is there in classpath.
                   */
                  try{
                      while(driversIterator.hasNext()) {
                          driversIterator.next();
                      }
                  } catch(Throwable t) {
                  // Do nothing
                  }
                  return null;
              }
          });
  
          println("DriverManager.initialize: jdbc.drivers = " + drivers);
  
          if (drivers == null || drivers.equals("")) {
              return;
          }
          String[] driversList = drivers.split(":");
          println("number of Drivers:" + driversList.length);
          for (String aDriver : driversList) {
              try {
                  println("DriverManager.Initialize: loading " + aDriver);
                  Class.forName(aDriver, true,
                          ClassLoader.getSystemClassLoader());
              } catch (Exception ex) {
                  println("DriverManager.Initialize: load failed: " + ex);
              }
          }
      }
  
  
      //  Worker method called by the public getConnection() methods.
      private static Connection getConnection(
          String url, java.util.Properties info, Class<?> caller) throws SQLException {
          /*
           * When callerCl is null, we should check the application's
           * (which is invoking this class indirectly)
           * classloader, so that the JDBC driver class outside rt.jar
           * can be loaded from here.
           */
          ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
          synchronized (DriverManager.class) {
              // synchronize loading of the correct classloader.
              if (callerCL == null) {
                  callerCL = Thread.currentThread().getContextClassLoader();
              }
          }
  
          if(url == null) {
              throw new SQLException("The url cannot be null", "08001");
          }
  
          println("DriverManager.getConnection(\"" + url + "\")");
  
          // Walk through the loaded registeredDrivers attempting to make a connection.
          // Remember the first exception that gets raised so we can reraise it.
          SQLException reason = null;
  
          for(DriverInfo aDriver : registeredDrivers) {
              // If the caller does not have permission to load the driver then
              // skip it.
              if(isDriverAllowed(aDriver.driver, callerCL)) {
                  try {
                      println("    trying " + aDriver.driver.getClass().getName());
                      Connection con = aDriver.driver.connect(url, info);
                      if (con != null) {
                          // Success!
                          println("getConnection returning " + aDriver.driver.getClass().getName());
                          return (con);
                      }
                  } catch (SQLException ex) {
                      if (reason == null) {
                          reason = ex;
                      }
                  }
  
              } else {
                  println("    skipping: " + aDriver.getClass().getName());
              }
  
          }
  
          // if we got here nobody could connect.
          if (reason != null)    {
              println("getConnection failed: " + reason);
              throw reason;
          }
  
          println("getConnection: no suitable driver found for "+ url);
          throw new SQLException("No suitable driver found for "+ url, "08001");
      }
  }
  
  