001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbutils;
018
019import static java.sql.DriverManager.registerDriver;
020
021import java.io.PrintWriter;
022import java.lang.reflect.Constructor;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.sql.Connection;
026import java.sql.Driver;
027import java.sql.DriverPropertyInfo;
028import java.sql.ResultSet;
029import java.sql.SQLException;
030import java.sql.SQLFeatureNotSupportedException;
031import java.sql.Statement;
032import java.util.logging.Logger;
033import java.util.Properties;
034
035/**
036 * A collection of JDBC helper methods.  This class is thread safe.
037 */
038public final class DbUtils {
039
040    /**
041     * Default constructor.
042     *
043     * Utility classes should not have a public or default constructor,
044     * but this one preserves retro-compatibility.
045     *
046     * @since 1.4
047     */
048    public DbUtils() {
049        // do nothing
050    }
051
052    /**
053     * Close a {@code Connection}, avoid closing if null.
054     *
055     * @param conn Connection to close.
056     * @throws SQLException if a database access error occurs
057     */
058    public static void close(final Connection conn) throws SQLException {
059        if (conn != null) {
060            conn.close();
061        }
062    }
063
064    /**
065     * Close a {@code ResultSet}, avoid closing if null.
066     *
067     * @param rs ResultSet to close.
068     * @throws SQLException if a database access error occurs
069     */
070    public static void close(final ResultSet rs) throws SQLException {
071        if (rs != null) {
072            rs.close();
073        }
074    }
075
076    /**
077     * Close a {@code Statement}, avoid closing if null.
078     *
079     * @param stmt Statement to close.
080     * @throws SQLException if a database access error occurs
081     */
082    public static void close(final Statement stmt) throws SQLException {
083        if (stmt != null) {
084            stmt.close();
085        }
086    }
087
088    /**
089     * Close a {@code Connection}, avoid closing if null and hide
090     * any SQLExceptions that occur.
091     *
092     * @param conn Connection to close.
093     */
094    public static void closeQuietly(final Connection conn) {
095        try {
096            close(conn);
097        } catch (final SQLException e) { // NOPMD
098            // quiet
099        }
100    }
101
102    /**
103     * Close a {@code Connection}, {@code Statement} and
104     * {@code ResultSet}.  Avoid closing if null and hide any
105     * SQLExceptions that occur.
106     *
107     * @param conn Connection to close.
108     * @param stmt Statement to close.
109     * @param rs ResultSet to close.
110     */
111    public static void closeQuietly(final Connection conn, final Statement stmt,
112            final ResultSet rs) {
113
114        try {
115            closeQuietly(rs);
116        } finally {
117            try {
118                closeQuietly(stmt);
119            } finally {
120                closeQuietly(conn);
121            }
122        }
123
124    }
125
126    /**
127     * Close a {@code ResultSet}, avoid closing if null and hide any
128     * SQLExceptions that occur.
129     *
130     * @param rs ResultSet to close.
131     */
132    public static void closeQuietly(final ResultSet rs) {
133        try {
134            close(rs);
135        } catch (final SQLException e) { // NOPMD
136            // quiet
137        }
138    }
139
140    /**
141     * Close a {@code Statement}, avoid closing if null and hide
142     * any SQLExceptions that occur.
143     *
144     * @param stmt Statement to close.
145     */
146    public static void closeQuietly(final Statement stmt) {
147        try {
148            close(stmt);
149        } catch (final SQLException e) { // NOPMD
150            // quiet
151        }
152    }
153
154    /**
155     * Commits a {@code Connection} then closes it, avoid closing if null.
156     *
157     * @param conn Connection to close.
158     * @throws SQLException if a database access error occurs
159     */
160    public static void commitAndClose(final Connection conn) throws SQLException {
161        if (conn != null) {
162            try {
163                conn.commit();
164            } finally {
165                conn.close();
166            }
167        }
168    }
169
170    /**
171     * Commits a {@code Connection} then closes it, avoid closing if null
172     * and hide any SQLExceptions that occur.
173     *
174     * @param conn Connection to close.
175     */
176    public static void commitAndCloseQuietly(final Connection conn) {
177        try {
178            commitAndClose(conn);
179        } catch (final SQLException e) { // NOPMD
180            // quiet
181        }
182    }
183
184    /**
185     * Loads and registers a database driver class.
186     * If this succeeds, it returns true, else it returns false.
187     *
188     * @param driverClassName of driver to load
189     * @return boolean {@code true} if the driver was found, otherwise {@code false}
190     */
191    public static boolean loadDriver(final String driverClassName) {
192        return loadDriver(DbUtils.class.getClassLoader(), driverClassName);
193    }
194
195    /**
196     * Loads and registers a database driver class.
197     * If this succeeds, it returns true, else it returns false.
198     *
199     * @param classLoader the class loader used to load the driver class
200     * @param driverClassName of driver to load
201     * @return boolean {@code true} if the driver was found, otherwise {@code false}
202     * @since 1.4
203     */
204    public static boolean loadDriver(final ClassLoader classLoader, final String driverClassName) {
205        try {
206            final Class<?> loadedClass = classLoader.loadClass(driverClassName);
207
208            if (!Driver.class.isAssignableFrom(loadedClass)) {
209                return false;
210            }
211
212            @SuppressWarnings("unchecked") // guarded by previous check
213            final
214            Class<Driver> driverClass = (Class<Driver>) loadedClass;
215            final Constructor<Driver> driverConstructor = driverClass.getConstructor();
216
217            // make Constructor accessible if it is private
218            @SuppressWarnings("deprecation")
219            // TODO This is deprecated in Java9 and canAccess() should be used. Adding suppression for building on
220            //      later JDKs without a warning.
221            final boolean isConstructorAccessible = driverConstructor.isAccessible();
222            if (!isConstructorAccessible) {
223                driverConstructor.setAccessible(true);
224            }
225
226            try {
227                final Driver driver = driverConstructor.newInstance();
228                registerDriver(new DriverProxy(driver));
229            } finally {
230                driverConstructor.setAccessible(isConstructorAccessible);
231            }
232
233            return true;
234        } catch (final RuntimeException e) {
235            return false;
236        } catch (final Exception e) {
237            return false;
238        }
239    }
240
241    /**
242     * Print the stack trace for a SQLException to STDERR.
243     *
244     * @param e SQLException to print stack trace of
245     */
246    public static void printStackTrace(final SQLException e) {
247        printStackTrace(e, new PrintWriter(System.err));
248    }
249
250    /**
251     * Print the stack trace for a SQLException to a
252     * specified PrintWriter.
253     *
254     * @param e SQLException to print stack trace of
255     * @param pw PrintWriter to print to
256     */
257    public static void printStackTrace(final SQLException e, final PrintWriter pw) {
258
259        SQLException next = e;
260        while (next != null) {
261            next.printStackTrace(pw);
262            next = next.getNextException();
263            if (next != null) {
264                pw.println("Next SQLException:");
265            }
266        }
267    }
268
269    /**
270     * Print warnings on a Connection to STDERR.
271     *
272     * @param conn Connection to print warnings from
273     */
274    public static void printWarnings(final Connection conn) {
275        printWarnings(conn, new PrintWriter(System.err));
276    }
277
278    /**
279     * Print warnings on a Connection to a specified PrintWriter.
280     *
281     * @param conn Connection to print warnings from
282     * @param pw PrintWriter to print to
283     */
284    public static void printWarnings(final Connection conn, final PrintWriter pw) {
285        if (conn != null) {
286            try {
287                printStackTrace(conn.getWarnings(), pw);
288            } catch (final SQLException e) {
289                printStackTrace(e, pw);
290            }
291        }
292    }
293
294    /**
295     * Rollback any changes made on the given connection.
296     * @param conn Connection to rollback.  A null value is legal.
297     * @throws SQLException if a database access error occurs
298     */
299    public static void rollback(final Connection conn) throws SQLException {
300        if (conn != null) {
301            conn.rollback();
302        }
303    }
304
305    /**
306     * Performs a rollback on the {@code Connection} then closes it,
307     * avoid closing if null.
308     *
309     * @param conn Connection to rollback.  A null value is legal.
310     * @throws SQLException if a database access error occurs
311     * @since DbUtils 1.1
312     */
313    public static void rollbackAndClose(final Connection conn) throws SQLException {
314        if (conn != null) {
315            try {
316                conn.rollback();
317            } finally {
318                conn.close();
319            }
320        }
321    }
322
323    /**
324     * Performs a rollback on the {@code Connection} then closes it,
325     * avoid closing if null and hide any SQLExceptions that occur.
326     *
327     * @param conn Connection to rollback.  A null value is legal.
328     * @since DbUtils 1.1
329     */
330    public static void rollbackAndCloseQuietly(final Connection conn) {
331        try {
332            rollbackAndClose(conn);
333        } catch (final SQLException e) { // NOPMD
334            // quiet
335        }
336    }
337
338    /**
339     * Simple {@link Driver} proxy class that proxies a JDBC Driver loaded dynamically.
340     *
341     * @since 1.6
342     */
343    static final class DriverProxy implements Driver {
344
345        private boolean parentLoggerSupported = true;
346
347        /**
348         * The adapted JDBC Driver loaded dynamically.
349         */
350        private final Driver adapted;
351
352        /**
353         * Creates a new JDBC Driver that adapts a JDBC Driver loaded dynamically.
354         *
355         * @param adapted the adapted JDBC Driver loaded dynamically.
356         */
357        public DriverProxy(final Driver adapted) {
358            this.adapted = adapted;
359        }
360
361        /**
362         * {@inheritDoc}
363         */
364        @Override
365        public boolean acceptsURL(final String url) throws SQLException {
366            return adapted.acceptsURL(url);
367        }
368
369        /**
370         * {@inheritDoc}
371         */
372        @Override
373        public Connection connect(final String url, final Properties info) throws SQLException {
374            return adapted.connect(url, info);
375        }
376
377        /**
378         * {@inheritDoc}
379         */
380        @Override
381        public int getMajorVersion() {
382            return adapted.getMajorVersion();
383        }
384
385        /**
386         * {@inheritDoc}
387         */
388        @Override
389        public int getMinorVersion() {
390            return adapted.getMinorVersion();
391        }
392
393        /**
394         * {@inheritDoc}
395         */
396        @Override
397        public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) throws SQLException {
398            return adapted.getPropertyInfo(url, info);
399        }
400
401        /**
402         * {@inheritDoc}
403         */
404        @Override
405        public boolean jdbcCompliant() {
406            return adapted.jdbcCompliant();
407        }
408
409        /**
410         * Java 1.7 method.
411         */
412        @Override
413        public Logger getParentLogger() throws SQLFeatureNotSupportedException {
414            if (parentLoggerSupported) {
415                try {
416                    final Method method = adapted.getClass().getMethod("getParentLogger");
417                    return (Logger)method.invoke(adapted);
418                } catch (final NoSuchMethodException e) {
419                    parentLoggerSupported = false;
420                    throw new SQLFeatureNotSupportedException(e);
421                } catch (final IllegalAccessException e) {
422                    parentLoggerSupported = false;
423                    throw new SQLFeatureNotSupportedException(e);
424                } catch (final InvocationTargetException e) {
425                    parentLoggerSupported = false;
426                    throw new SQLFeatureNotSupportedException(e);
427                }
428            }
429            throw new SQLFeatureNotSupportedException();
430        }
431
432    }
433
434}