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 java.beans.IntrospectionException;
020import java.beans.Introspector;
021import java.beans.PropertyDescriptor;
022import java.lang.reflect.InvocationTargetException;
023import java.lang.reflect.Method;
024import java.sql.CallableStatement;
025import java.sql.Connection;
026import java.sql.ParameterMetaData;
027import java.sql.PreparedStatement;
028import java.sql.ResultSet;
029import java.sql.SQLException;
030import java.sql.SQLFeatureNotSupportedException;
031import java.sql.Statement;
032import java.sql.Types;
033import java.util.Arrays;
034
035import javax.sql.DataSource;
036
037/**
038 * The base class for QueryRunner & AsyncQueryRunner. This class is thread safe.
039 *
040 * @since 1.4 (mostly extracted from QueryRunner)
041 */
042public abstract class AbstractQueryRunner {
043    /**
044     * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried
045     * it yet)?
046     */
047    private volatile boolean pmdKnownBroken = false;
048
049    /**
050     * The DataSource to retrieve connections from.
051     * @deprecated Access to this field should be through {@link #getDataSource()}.
052     */
053    @Deprecated
054    protected final DataSource ds;
055
056    /**
057     * Configuration to use when preparing statements.
058     */
059    private final StatementConfiguration stmtConfig;
060
061    /**
062     * Default constructor, sets pmdKnownBroken to false, ds to null and stmtConfig to null.
063     */
064    public AbstractQueryRunner() {
065        ds = null;
066        this.stmtConfig = null;
067    }
068
069    /**
070     * Constructor to control the use of {@code ParameterMetaData}.
071     *
072     * @param pmdKnownBroken
073     *            Some drivers don't support
074     *            {@link ParameterMetaData#getParameterType(int) }; if
075     *            {@code pmdKnownBroken} is set to true, we won't even try
076     *            it; if false, we'll try it, and if it breaks, we'll remember
077     *            not to use it again.
078     */
079    public AbstractQueryRunner(final boolean pmdKnownBroken) {
080        this.pmdKnownBroken = pmdKnownBroken;
081        ds = null;
082        this.stmtConfig = null;
083    }
084
085    /**
086     * Constructor to provide a {@code DataSource}. Methods that do not
087     * take a {@code Connection} parameter will retrieve connections from
088     * this {@code DataSource}.
089     *
090     * @param ds
091     *            The {@code DataSource} to retrieve connections from.
092     */
093    public AbstractQueryRunner(final DataSource ds) {
094        this.ds = ds;
095        this.stmtConfig = null;
096    }
097
098    /**
099     * Constructor to provide a {@code DataSource} and control the use of
100     * {@code ParameterMetaData}. Methods that do not take a
101     * {@code Connection} parameter will retrieve connections from this
102     * {@code DataSource}.
103     *
104     * @param ds
105     *            The {@code DataSource} to retrieve connections from.
106     * @param pmdKnownBroken
107     *            Some drivers don't support
108     *            {@link ParameterMetaData#getParameterType(int) }; if
109     *            {@code pmdKnownBroken} is set to true, we won't even try
110     *            it; if false, we'll try it, and if it breaks, we'll remember
111     *            not to use it again.
112     */
113    public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken) {
114        this.pmdKnownBroken = pmdKnownBroken;
115        this.ds = ds;
116        this.stmtConfig = null;
117    }
118
119    /**
120     * Constructor for QueryRunner that takes a {@code DataSource}, a {@code StatementConfiguration}, and
121     * controls the use of {@code ParameterMetaData}.  Methods that do not take a {@code Connection} parameter
122     * will retrieve connections from this {@code DataSource}.
123     *
124     * @param ds The {@code DataSource} to retrieve connections from.
125     * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
126     * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
127     * and if it breaks, we'll remember not to use it again.
128     * @param stmtConfig The configuration to apply to statements when they are prepared.
129     */
130    public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken, final StatementConfiguration stmtConfig) {
131        this.pmdKnownBroken = pmdKnownBroken;
132        this.ds = ds;
133        this.stmtConfig = stmtConfig;
134    }
135
136    /**
137     * Constructor for QueryRunner that takes a {@code DataSource} to use and a {@code StatementConfiguration}.
138     *
139     * Methods that do not take a {@code Connection} parameter will retrieve connections from this
140     * {@code DataSource}.
141     *
142     * @param ds The {@code DataSource} to retrieve connections from.
143     * @param stmtConfig The configuration to apply to statements when they are prepared.
144     */
145    public AbstractQueryRunner(final DataSource ds, final StatementConfiguration stmtConfig) {
146        this.ds = ds;
147        this.stmtConfig = stmtConfig;
148    }
149
150    /**
151     * Constructor for QueryRunner that takes a {@code StatementConfiguration} to configure statements when
152     * preparing them.
153     *
154     * @param stmtConfig The configuration to apply to statements when they are prepared.
155     */
156    public AbstractQueryRunner(final StatementConfiguration stmtConfig) {
157        this.ds = null;
158        this.stmtConfig = stmtConfig;
159    }
160
161    /**
162     * Close a {@code Connection}. This implementation avoids closing if
163     * null and does <strong>not</strong> suppress any exceptions. Subclasses
164     * can override to provide special handling like logging.
165     *
166     * @param conn
167     *            Connection to close
168     * @throws SQLException
169     *             if a database access error occurs
170     * @since DbUtils 1.1
171     */
172    protected void close(final Connection conn) throws SQLException {
173        DbUtils.close(conn);
174    }
175
176    /**
177     * Close a {@code ResultSet}. This implementation avoids closing if
178     * null and does <strong>not</strong> suppress any exceptions. Subclasses
179     * can override to provide special handling like logging.
180     *
181     * @param rs
182     *            ResultSet to close
183     * @throws SQLException
184     *             if a database access error occurs
185     * @since DbUtils 1.1
186     */
187    protected void close(final ResultSet rs) throws SQLException {
188        DbUtils.close(rs);
189    }
190
191    /**
192     * Close a {@code Statement}. This implementation avoids closing if
193     * null and does <strong>not</strong> suppress any exceptions. Subclasses
194     * can override to provide special handling like logging.
195     *
196     * @param stmt
197     *            Statement to close
198     * @throws SQLException
199     *             if a database access error occurs
200     * @since DbUtils 1.1
201     */
202    protected void close(final Statement stmt) throws SQLException {
203        DbUtils.close(stmt);
204    }
205
206    /**
207     * Calls {@link DbUtils#closeQuietly(Connection)}.
208     *
209     * @param conn Connection to close.
210     * @since 2.0
211     */
212    protected void closeQuietly(final Connection conn) {
213        DbUtils.closeQuietly(conn);
214    }
215
216    /**
217     * Calls {@link DbUtils#closeQuietly(ResultSet)}.
218     *
219     * @param rs ResultSet to close.
220     * @since 2.0
221     */
222    protected void closeQuietly(final ResultSet rs) {
223        DbUtils.closeQuietly(rs);
224    }
225
226    /**
227     * Calls {@link DbUtils#closeQuietly(Statement)}.
228     *
229     * @param statement ResultSet to close.
230     * @since 2.0
231     */
232    protected void closeQuietly(final Statement statement) {
233        DbUtils.closeQuietly(statement);
234    }
235
236    private void configureStatement(final Statement stmt) throws SQLException {
237
238        if (stmtConfig != null) {
239            if (stmtConfig.isFetchDirectionSet()) {
240                stmt.setFetchDirection(stmtConfig.getFetchDirection());
241            }
242
243            if (stmtConfig.isFetchSizeSet()) {
244                stmt.setFetchSize(stmtConfig.getFetchSize());
245            }
246
247            if (stmtConfig.isMaxFieldSizeSet()) {
248                stmt.setMaxFieldSize(stmtConfig.getMaxFieldSize());
249            }
250
251            if (stmtConfig.isMaxRowsSet()) {
252                stmt.setMaxRows(stmtConfig.getMaxRows());
253            }
254
255            if (stmtConfig.isQueryTimeoutSet()) {
256                stmt.setQueryTimeout(stmtConfig.getQueryTimeout());
257            }
258        }
259    }
260
261    /**
262     * Fill the {@code PreparedStatement} replacement parameters with the
263     * given objects.
264     *
265     * @param stmt
266     *            PreparedStatement to fill
267     * @param params
268     *            Query replacement parameters; {@code null} is a valid
269     *            value to pass in.
270     * @throws SQLException
271     *             if a database access error occurs
272     */
273    public void fillStatement(final PreparedStatement stmt, final Object... params)
274            throws SQLException {
275
276        // check the parameter count, if we can
277        ParameterMetaData pmd = null;
278        if (!pmdKnownBroken) {
279            try {
280                pmd = stmt.getParameterMetaData();
281                if (pmd == null) { // can be returned by implementations that don't support the method
282                    pmdKnownBroken = true;
283                } else {
284                    final int stmtCount = pmd.getParameterCount();
285                    final int paramsCount = params == null ? 0 : params.length;
286
287                    if (stmtCount != paramsCount) {
288                        throw new SQLException("Wrong number of parameters: expected "
289                                + stmtCount + ", was given " + paramsCount);
290                    }
291                }
292            } catch (final SQLFeatureNotSupportedException ex) {
293                pmdKnownBroken = true;
294            }
295            // TODO see DBUTILS-117: would it make sense to catch any other SQLEx types here?
296        }
297
298        // nothing to do here
299        if (params == null) {
300            return;
301        }
302
303        CallableStatement call = null;
304        if (stmt instanceof CallableStatement) {
305            call = (CallableStatement) stmt;
306        }
307
308        for (int i = 0; i < params.length; i++) {
309            if (params[i] != null) {
310                if (call != null && params[i] instanceof OutParameter) {
311                    ((OutParameter)params[i]).register(call, i + 1);
312                } else {
313                    stmt.setObject(i + 1, params[i]);
314                }
315            } else {
316                // VARCHAR works with many drivers regardless
317                // of the actual column type. Oddly, NULL and
318                // OTHER don't work with Oracle's drivers.
319                int sqlType = Types.VARCHAR;
320                if (!pmdKnownBroken) {
321                    // TODO see DBUTILS-117: does it make sense to catch SQLEx here?
322                    try {
323                        /*
324                         * It's not possible for pmdKnownBroken to change from
325                         * true to false, (once true, always true) so pmd cannot
326                         * be null here.
327                         */
328                        sqlType = pmd.getParameterType(i + 1);
329                    } catch (final SQLException e) {
330                        pmdKnownBroken = true;
331                    }
332                }
333                stmt.setNull(i + 1, sqlType);
334            }
335        }
336    }
337
338    /**
339     * Fill the {@code PreparedStatement} replacement parameters with the
340     * given object's bean property values.
341     *
342     * @param stmt
343     *            PreparedStatement to fill
344     * @param bean
345     *            a JavaBean object
346     * @param properties
347     *            an ordered array of properties; this gives the order to insert
348     *            values in the statement
349     * @throws SQLException
350     *             if a database access error occurs
351     */
352    public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,
353            final PropertyDescriptor[] properties) throws SQLException {
354        final Object[] params = new Object[properties.length];
355        for (int i = 0; i < properties.length; i++) {
356            final PropertyDescriptor property = properties[i];
357            Object value = null;
358            final Method method = property.getReadMethod();
359            if (method == null) {
360                throw new RuntimeException("No read method for bean property "
361                        + bean.getClass() + " " + property.getName());
362            }
363            try {
364                value = method.invoke(bean);
365            } catch (final InvocationTargetException e) {
366                throw new RuntimeException("Couldn't invoke method: " + method,
367                        e);
368            } catch (final IllegalArgumentException e) {
369                throw new RuntimeException(
370                        "Couldn't invoke method with 0 arguments: " + method, e);
371            } catch (final IllegalAccessException e) {
372                throw new RuntimeException("Couldn't invoke method: " + method,
373                        e);
374            }
375            params[i] = value;
376        }
377        fillStatement(stmt, params);
378    }
379
380    /**
381     * Fill the {@code PreparedStatement} replacement parameters with the
382     * given object's bean property values.
383     *
384     * @param stmt
385     *            PreparedStatement to fill
386     * @param bean
387     *            A JavaBean object
388     * @param propertyNames
389     *            An ordered array of property names (these should match the
390     *            getters/setters); this gives the order to insert values in the
391     *            statement
392     * @throws SQLException
393     *             If a database access error occurs
394     */
395    public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,
396            final String... propertyNames) throws SQLException {
397        PropertyDescriptor[] descriptors;
398        try {
399            descriptors = Introspector.getBeanInfo(bean.getClass())
400                    .getPropertyDescriptors();
401        } catch (final IntrospectionException e) {
402            throw new RuntimeException("Couldn't introspect bean "
403                    + bean.getClass().toString(), e);
404        }
405        final PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
406        for (int i = 0; i < propertyNames.length; i++) {
407            final String propertyName = propertyNames[i];
408            if (propertyName == null) {
409                throw new NullPointerException("propertyName can't be null: "
410                        + i);
411            }
412            boolean found = false;
413            for (final PropertyDescriptor descriptor : descriptors) {
414                if (propertyName.equals(descriptor.getName())) {
415                    sorted[i] = descriptor;
416                    found = true;
417                    break;
418                }
419            }
420            if (!found) {
421                throw new RuntimeException("Couldn't find bean property: "
422                        + bean.getClass() + " " + propertyName);
423            }
424        }
425        fillStatementWithBean(stmt, bean, sorted);
426    }
427
428    /**
429     * Returns the {@code DataSource} this runner is using.
430     * {@code QueryRunner} methods always call this method to get the
431     * {@code DataSource} so subclasses can provide specialized behavior.
432     *
433     * @return DataSource the runner is using
434     */
435    public DataSource getDataSource() {
436        return this.ds;
437    }
438
439    /**
440     * Some drivers don't support
441     * {@link ParameterMetaData#getParameterType(int) }; if
442     * {@code pmdKnownBroken} is set to true, we won't even try it; if
443     * false, we'll try it, and if it breaks, we'll remember not to use it
444     * again.
445     *
446     * @return the flag to skip (or not)
447     *         {@link ParameterMetaData#getParameterType(int) }
448     * @since 1.4
449     */
450    public boolean isPmdKnownBroken() {
451        return pmdKnownBroken;
452    }
453
454    /**
455     * Factory method that creates and initializes a
456     * {@code CallableStatement} object for the given SQL.
457     * {@code QueryRunner} methods always call this method to prepare
458     * callable statements for them. Subclasses can override this method to
459     * provide special CallableStatement configuration if needed. This
460     * implementation simply calls {@code conn.prepareCall(sql)}.
461     *
462     * @param conn
463     *            The {@code Connection} used to create the
464     *            {@code CallableStatement}
465     * @param sql
466     *            The SQL statement to prepare.
467     * @return An initialized {@code CallableStatement}.
468     * @throws SQLException
469     *             if a database access error occurs
470     */
471    protected CallableStatement prepareCall(final Connection conn, final String sql)
472            throws SQLException {
473
474        return conn.prepareCall(sql);
475    }
476
477    /**
478     * Factory method that creates and initializes a {@code Connection}
479     * object. {@code QueryRunner} methods always call this method to
480     * retrieve connections from its DataSource. Subclasses can override this
481     * method to provide special {@code Connection} configuration if
482     * needed. This implementation simply calls {@code ds.getConnection()}.
483     *
484     * @return An initialized {@code Connection}.
485     * @throws SQLException
486     *             if a database access error occurs
487     * @since DbUtils 1.1
488     */
489    protected Connection prepareConnection() throws SQLException {
490        if (this.getDataSource() == null) {
491            throw new SQLException(
492                    "QueryRunner requires a DataSource to be "
493                            + "invoked in this way, or a Connection should be passed in");
494        }
495        return this.getDataSource().getConnection();
496    }
497
498    /**
499     * Factory method that creates and initializes a
500     * {@code PreparedStatement} object for the given SQL.
501     * {@code QueryRunner} methods always call this method to prepare
502     * statements for them. Subclasses can override this method to provide
503     * special PreparedStatement configuration if needed. This implementation
504     * simply calls {@code conn.prepareStatement(sql)}.
505     *
506     * @param conn
507     *            The {@code Connection} used to create the
508     *            {@code PreparedStatement}
509     * @param sql
510     *            The SQL statement to prepare.
511     * @return An initialized {@code PreparedStatement}.
512     * @throws SQLException
513     *             if a database access error occurs
514     */
515    protected PreparedStatement prepareStatement(final Connection conn, final String sql)
516            throws SQLException {
517
518        @SuppressWarnings("resource")
519        final
520        PreparedStatement ps = conn.prepareStatement(sql);
521        try {
522            configureStatement(ps);
523        } catch (final SQLException e) {
524            ps.close();
525            throw e;
526        }
527        return ps;
528    }
529
530    /**
531     * Factory method that creates and initializes a
532     * {@code PreparedStatement} object for the given SQL.
533     * {@code QueryRunner} methods always call this method to prepare
534     * statements for them. Subclasses can override this method to provide
535     * special PreparedStatement configuration if needed. This implementation
536     * simply calls {@code conn.prepareStatement(sql, returnedKeys)}
537     * which will result in the ability to retrieve the automatically-generated
538     * keys from an auto_increment column.
539     *
540     * @param conn
541     *            The {@code Connection} used to create the
542     *            {@code PreparedStatement}
543     * @param sql
544     *            The SQL statement to prepare.
545     * @param returnedKeys
546     *            Flag indicating whether to return generated keys or not.
547     *
548     * @return An initialized {@code PreparedStatement}.
549     * @throws SQLException
550     *             if a database access error occurs
551     * @since 1.6
552     */
553    protected PreparedStatement prepareStatement(final Connection conn, final String sql, final int returnedKeys)
554            throws SQLException {
555
556        @SuppressWarnings("resource")
557        final
558        PreparedStatement ps = conn.prepareStatement(sql, returnedKeys);
559        try {
560            configureStatement(ps);
561        } catch (final SQLException e) {
562            ps.close();
563            throw e;
564        }
565        return ps;
566    }
567
568    /**
569     * Throws a new exception with a more informative error message.
570     *
571     * @param cause
572     *            The original exception that will be chained to the new
573     *            exception when it's rethrown.
574     *
575     * @param sql
576     *            The query that was executing when the exception happened.
577     *
578     * @param params
579     *            The query replacement parameters; {@code null} is a valid
580     *            value to pass in.
581     *
582     * @throws SQLException
583     *             if a database access error occurs
584     */
585    protected void rethrow(final SQLException cause, final String sql, final Object... params)
586            throws SQLException {
587
588        String causeMessage = cause.getMessage();
589        if (causeMessage == null) {
590            causeMessage = "";
591        }
592        final StringBuffer msg = new StringBuffer(causeMessage);
593
594        msg.append(" Query: ");
595        msg.append(sql);
596        msg.append(" Parameters: ");
597
598        if (params == null) {
599            msg.append("[]");
600        } else {
601            msg.append(Arrays.deepToString(params));
602        }
603
604        final SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
605                cause.getErrorCode());
606        e.setNextException(cause);
607
608        throw e;
609    }
610
611    /**
612     * Wrap the {@code ResultSet} in a decorator before processing it. This
613     * implementation returns the {@code ResultSet} it is given without any
614     * decoration.
615     *
616     * <p>
617     * Often, the implementation of this method can be done in an anonymous
618     * inner class like this:
619     * </p>
620     *
621     * <pre>
622     * QueryRunner run = new QueryRunner() {
623     *     protected ResultSet wrap(ResultSet rs) {
624     *         return StringTrimmedResultSet.wrap(rs);
625     *     }
626     * };
627     * </pre>
628     *
629     * @param rs
630     *            The {@code ResultSet} to decorate; never
631     *            {@code null}.
632     * @return The {@code ResultSet} wrapped in some decorator.
633     */
634    protected ResultSet wrap(final ResultSet rs) {
635        return rs;
636    }
637
638}