1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package java.sql;
19
20import java.util.ArrayList;
21import java.util.List;
22import java.util.Properties;
23import java.util.Enumeration;
24import java.util.Iterator;
25import java.io.PrintStream;
26import java.io.PrintWriter;
27import java.util.Vector;
28import java.security.AccessController;
29import org.apache.harmony.luni.util.PriviAction;
30import org.apache.harmony.sql.internal.nls.Messages;
31// BEGIN android-changed
32import dalvik.system.VMStack;
33// END android-changed
34
35/**
36 * Provides facilities for managing JDBC drivers.
37 * <p>
38 * The {@code DriverManager} class loads JDBC drivers during its initialization,
39 * from the list of drivers referenced by the system property {@code
40 * "jdbc.drivers"}.
41 */
42public class DriverManager {
43
44    /*
45     * Facilities for logging. The Print Stream is deprecated but is maintained
46     * here for compatibility.
47     */
48    private static PrintStream thePrintStream;
49
50    private static PrintWriter thePrintWriter;
51
52    // Login timeout value - by default set to 0 -> "wait forever"
53    private static int loginTimeout = 0;
54
55    /*
56     * Set to hold Registered Drivers - initial capacity 10 drivers (will expand
57     * automatically if necessary.
58     */
59    private static final List<Driver> theDrivers = new ArrayList<Driver>(10);
60
61    // Permission for setting log
62    private static final SQLPermission logPermission = new SQLPermission(
63            "setLog"); //$NON-NLS-1$
64
65    /*
66     * Load drivers on initialization
67     */
68    static {
69        loadInitialDrivers();
70    }
71
72    /*
73     * Loads the set of JDBC drivers defined by the Property "jdbc.drivers" if
74     * it is defined.
75     */
76    private static void loadInitialDrivers() {
77        String theDriverList = AccessController
78                .doPrivileged(new PriviAction<String>("jdbc.drivers", null)); //$NON-NLS-1$
79
80        if (theDriverList == null) {
81            return;
82        }
83
84        /*
85         * Get the names of the drivers as an array of Strings from the system
86         * property by splitting the property at the separator character ':'
87         */
88        String[] theDriverNames = theDriverList.split(":"); //$NON-NLS-1$
89
90        for (String element : theDriverNames) {
91            try {
92                // Load the driver class
93                Class
94                        .forName(element, true, ClassLoader
95                                .getSystemClassLoader());
96            } catch (Throwable t) {
97                // Ignored
98            }
99        }
100    }
101
102    /*
103     * A private constructor to prevent allocation
104     */
105    private DriverManager() {
106        super();
107    }
108
109    /**
110     * Removes a driver from the {@code DriverManager}'s registered driver list.
111     * This will only succeed when the caller's class loader loaded the driver
112     * that is to be removed. If the driver was loaded by a different class
113     * loader, the removal of the driver fails silently.
114     * <p>
115     * If the removal succeeds, the {@code DriverManager} will not use this
116     * driver in the future when asked to get a {@code Connection}.
117     *
118     * @param driver
119     *            the JDBC driver to remove.
120     * @throws SQLException
121     *             if there is a problem interfering with accessing the
122     *             database.
123     */
124    public static void deregisterDriver(Driver driver) throws SQLException {
125        if (driver == null) {
126            return;
127        }
128        // BEGIN android-changed
129        ClassLoader callerClassLoader = VMStack.getCallingClassLoader();
130        // END android-changed
131
132        if (!DriverManager.isClassFromClassLoader(driver, callerClassLoader)) {
133            // sql.1=DriverManager: calling class not authorized to deregister
134            // JDBC driver
135            throw new SecurityException(Messages.getString("sql.1")); //$NON-NLS-1$
136        } // end if
137        synchronized (theDrivers) {
138            theDrivers.remove(driver);
139        }
140    }
141
142    /**
143     * Attempts to establish a connection to the given database URL.
144     *
145     * @param url
146     *            a URL string representing the database target to connect with.
147     * @return a {@code Connection} to the database identified by the URL.
148     *         {@code null} if no connection can be established.
149     * @throws SQLException
150     *             if there is an error while attempting to connect to the
151     *             database identified by the URL.
152     */
153    public static Connection getConnection(String url) throws SQLException {
154        return getConnection(url, new Properties());
155    }
156
157    /**
158     * Attempts to establish a connection to the given database URL.
159     *
160     * @param url
161     *            a URL string representing the database target to connect with
162     * @param info
163     *            a set of properties to use as arguments to set up the
164     *            connection. Properties are arbitrary string/value pairs.
165     *            Normally, at least the properties {@code "user"} and {@code
166     *            "password"} should be passed, with appropriate settings for
167     *            the user ID and its corresponding password to get access to
168     *            the corresponding database.
169     * @return a {@code Connection} to the database identified by the URL.
170     *         {@code null} if no connection can be established.
171     * @throws SQLException
172     *             if there is an error while attempting to connect to the
173     *             database identified by the URL.
174     */
175    public static Connection getConnection(String url, Properties info)
176            throws SQLException {
177        // 08 - connection exception
178        // 001 - SQL-client unable to establish SQL-connection
179        String sqlState = "08001"; //$NON-NLS-1$
180        if (url == null) {
181            // sql.5=The url cannot be null
182            throw new SQLException(Messages.getString("sql.5"), sqlState); //$NON-NLS-1$
183        }
184        synchronized (theDrivers) {
185            /*
186             * Loop over the drivers in the DriverSet checking to see if one can
187             * open a connection to the supplied URL - return the first
188             * connection which is returned
189             */
190            for (Driver theDriver : theDrivers) {
191                Connection theConnection = theDriver.connect(url, info);
192                if (theConnection != null) {
193                    return theConnection;
194                }
195            }
196        }
197        // If we get here, none of the drivers are able to resolve the URL
198        // sql.6=No suitable driver
199        throw new SQLException(Messages.getString("sql.6"), sqlState); //$NON-NLS-1$
200    }
201
202    /**
203     * Attempts to establish a connection to the given database URL.
204     *
205     * @param url
206     *            a URL string representing the database target to connect with.
207     * @param user
208     *            a user ID used to login to the database.
209     * @param password
210     *            a password for the user ID to login to the database.
211     * @return a {@code Connection} to the database identified by the URL.
212     *         {@code null} if no connection can be established.
213     * @throws SQLException
214     *             if there is an error while attempting to connect to the
215     *             database identified by the URL.
216     */
217    public static Connection getConnection(String url, String user,
218            String password) throws SQLException {
219        Properties theProperties = new Properties();
220        if (null != user) {
221            theProperties.setProperty("user", user); //$NON-NLS-1$
222        }
223        if (null != password) {
224            theProperties.setProperty("password", password); //$NON-NLS-1$
225        }
226        return getConnection(url, theProperties);
227    }
228
229    /**
230     * Tries to find a driver that can interpret the supplied URL.
231     *
232     * @param url
233     *            the URL of a database.
234     * @return a {@code Driver} that matches the provided URL. {@code null} if
235     *         no {@code Driver} understands the URL
236     * @throws SQLException
237     *             if there is any kind of problem accessing the database.
238     */
239    public static Driver getDriver(String url) throws SQLException {
240        // BEGIN android-changed
241        ClassLoader callerClassLoader = VMStack.getCallingClassLoader();
242        // END android-changed
243
244        synchronized (theDrivers) {
245            /*
246             * Loop over the drivers in the DriverSet checking to see if one
247             * does understand the supplied URL - return the first driver which
248             * does understand the URL
249             */
250            Iterator<Driver> theIterator = theDrivers.iterator();
251            while (theIterator.hasNext()) {
252                Driver theDriver = theIterator.next();
253                if (theDriver.acceptsURL(url)
254                        && DriverManager.isClassFromClassLoader(theDriver,
255                                callerClassLoader)) {
256                    return theDriver;
257                }
258            }
259        }
260        // If no drivers understand the URL, throw an SQLException
261        // sql.6=No suitable driver
262        // SQLState: 08 - connection exception
263        // 001 - SQL-client unable to establish SQL-connection
264        throw new SQLException(Messages.getString("sql.6"), "08001"); //$NON-NLS-1$ //$NON-NLS-2$
265    }
266
267    /**
268     * Returns an {@code Enumeration} that contains all of the loaded JDBC
269     * drivers that the current caller can access.
270     *
271     * @return An {@code Enumeration} containing all the currently loaded JDBC
272     *         {@code Drivers}.
273     */
274    public static Enumeration<Driver> getDrivers() {
275        // BEGIN android-changed
276        ClassLoader callerClassLoader = VMStack.getCallingClassLoader();
277        // END android-changed
278        /*
279         * Synchronize to avoid clashes with additions and removals of drivers
280         * in the DriverSet
281         */
282        synchronized (theDrivers) {
283            /*
284             * Create the Enumeration by building a Vector from the elements of
285             * the DriverSet
286             */
287            Vector<Driver> theVector = new Vector<Driver>();
288            Iterator<Driver> theIterator = theDrivers.iterator();
289            while (theIterator.hasNext()) {
290                Driver theDriver = theIterator.next();
291                if (DriverManager.isClassFromClassLoader(theDriver,
292                        callerClassLoader)) {
293                    theVector.add(theDriver);
294                }
295            }
296            return theVector.elements();
297        }
298    }
299
300    /**
301     * Returns the login timeout when connecting to a database in seconds.
302     *
303     * @return the login timeout in seconds.
304     */
305    public static int getLoginTimeout() {
306        return loginTimeout;
307    }
308
309    /**
310     * Gets the log {@code PrintStream} used by the {@code DriverManager} and
311     * all the JDBC Drivers.
312     *
313     * @deprecated use {@link #getLogWriter()} instead.
314     * @return the {@code PrintStream} used for logging activities.
315     */
316    @Deprecated
317    public static PrintStream getLogStream() {
318        return thePrintStream;
319    }
320
321    /**
322     * Retrieves the log writer.
323     *
324     * @return A {@code PrintWriter} object used as the log writer. {@code null}
325     *         if no log writer is set.
326     */
327    public static PrintWriter getLogWriter() {
328        return thePrintWriter;
329    }
330
331    /**
332     * Prints a message to the current JDBC log stream. This is either the
333     * {@code PrintWriter} or (deprecated) the {@code PrintStream}, if set.
334     *
335     * @param message
336     *            the message to print to the JDBC log stream.
337     */
338    public static void println(String message) {
339        if (thePrintWriter != null) {
340            thePrintWriter.println(message);
341            thePrintWriter.flush();
342        } else if (thePrintStream != null) {
343            thePrintStream.println(message);
344            thePrintStream.flush();
345        }
346        /*
347         * If neither the PrintWriter not the PrintStream are set, then silently
348         * do nothing the message is not recorded and no exception is generated.
349         */
350        return;
351    }
352
353    /**
354     * Registers a given JDBC driver with the {@code DriverManager}.
355     * <p>
356     * A newly loaded JDBC driver class should register itself with the
357     * {@code DriverManager} by calling this method.
358     *
359     * @param driver
360     *            the {@code Driver} to register with the {@code DriverManager}.
361     * @throws SQLException
362     *             if a database access error occurs.
363     */
364    public static void registerDriver(Driver driver) throws SQLException {
365        if (driver == null) {
366            throw new NullPointerException();
367        }
368        synchronized (theDrivers) {
369            theDrivers.add(driver);
370        }
371    }
372
373    /**
374     * Sets the login timeout when connecting to a database in seconds.
375     *
376     * @param seconds
377     *            seconds until timeout. 0 indicates wait forever.
378     */
379    public static void setLoginTimeout(int seconds) {
380        loginTimeout = seconds;
381        return;
382    }
383
384    /**
385     * Sets the print stream to use for logging data from the {@code
386     * DriverManager} and the JDBC drivers.
387     *
388     * @deprecated Use {@link #setLogWriter} instead.
389     * @param out
390     *            the {@code PrintStream} to use for logging.
391     */
392    @Deprecated
393    public static void setLogStream(PrintStream out) {
394        checkLogSecurity();
395        thePrintStream = out;
396    }
397
398    /**
399     * Sets the {@code PrintWriter} that is used by all loaded drivers, and also
400     * the {@code DriverManager}.
401     *
402     * @param out
403     *            the {@code PrintWriter} to be used.
404     */
405    public static void setLogWriter(PrintWriter out) {
406        checkLogSecurity();
407        thePrintWriter = out;
408    }
409
410    /*
411     * Method which checks to see if setting a logging stream is allowed by the
412     * Security manager
413     */
414    private static void checkLogSecurity() {
415        SecurityManager securityManager = System.getSecurityManager();
416        if (securityManager != null) {
417            // Throws a SecurityException if setting the log is not permitted
418            securityManager.checkPermission(logPermission);
419        }
420    }
421
422    /**
423     * Determines whether the supplied object was loaded by the given {@code ClassLoader}.
424     *
425     * @param theObject
426     *            the object to check.
427     * @param theClassLoader
428     *            the {@code ClassLoader}.
429     * @return {@code true} if the Object does belong to the {@code ClassLoader}
430     *         , {@code false} otherwise
431     */
432    private static boolean isClassFromClassLoader(Object theObject,
433            ClassLoader theClassLoader) {
434
435        if ((theObject == null) || (theClassLoader == null)) {
436            return false;
437        }
438
439        Class<?> objectClass = theObject.getClass();
440
441        try {
442            Class<?> checkClass = Class.forName(objectClass.getName(), true,
443                    theClassLoader);
444            if (checkClass == objectClass) {
445                return true;
446            }
447        } catch (Throwable t) {
448            // Empty
449        }
450        return false;
451    }
452}
453