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