AbstractQueryRunner.java

  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. package org.apache.commons.dbutils;

  18. import java.beans.IntrospectionException;
  19. import java.beans.Introspector;
  20. import java.beans.PropertyDescriptor;
  21. import java.lang.reflect.InvocationTargetException;
  22. import java.lang.reflect.Method;
  23. import java.sql.CallableStatement;
  24. import java.sql.Connection;
  25. import java.sql.ParameterMetaData;
  26. import java.sql.PreparedStatement;
  27. import java.sql.ResultSet;
  28. import java.sql.SQLException;
  29. import java.sql.SQLFeatureNotSupportedException;
  30. import java.sql.Statement;
  31. import java.sql.Types;
  32. import java.util.Arrays;

  33. import javax.sql.DataSource;

  34. /**
  35.  * The base class for QueryRunner & AsyncQueryRunner. This class is thread safe.
  36.  *
  37.  * @since 1.4 (mostly extracted from QueryRunner)
  38.  */
  39. public abstract class AbstractQueryRunner {
  40.     /**
  41.      * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried
  42.      * it yet)?
  43.      */
  44.     private volatile boolean pmdKnownBroken = false;

  45.     /**
  46.      * The DataSource to retrieve connections from.
  47.      * @deprecated Access to this field should be through {@link #getDataSource()}.
  48.      */
  49.     @Deprecated
  50.     protected final DataSource ds;

  51.     /**
  52.      * Configuration to use when preparing statements.
  53.      */
  54.     private final StatementConfiguration stmtConfig;

  55.     /**
  56.      * Default constructor, sets pmdKnownBroken to false, ds to null and stmtConfig to null.
  57.      */
  58.     public AbstractQueryRunner() {
  59.         ds = null;
  60.         this.stmtConfig = null;
  61.     }

  62.     /**
  63.      * Constructor to control the use of {@code ParameterMetaData}.
  64.      *
  65.      * @param pmdKnownBroken
  66.      *            Some drivers don't support
  67.      *            {@link ParameterMetaData#getParameterType(int) }; if
  68.      *            {@code pmdKnownBroken} is set to true, we won't even try
  69.      *            it; if false, we'll try it, and if it breaks, we'll remember
  70.      *            not to use it again.
  71.      */
  72.     public AbstractQueryRunner(final boolean pmdKnownBroken) {
  73.         this.pmdKnownBroken = pmdKnownBroken;
  74.         ds = null;
  75.         this.stmtConfig = null;
  76.     }

  77.     /**
  78.      * Constructor to provide a {@code DataSource}. Methods that do not
  79.      * take a {@code Connection} parameter will retrieve connections from
  80.      * this {@code DataSource}.
  81.      *
  82.      * @param ds
  83.      *            The {@code DataSource} to retrieve connections from.
  84.      */
  85.     public AbstractQueryRunner(final DataSource ds) {
  86.         this.ds = ds;
  87.         this.stmtConfig = null;
  88.     }

  89.     /**
  90.      * Constructor to provide a {@code DataSource} and control the use of
  91.      * {@code ParameterMetaData}. Methods that do not take a
  92.      * {@code Connection} parameter will retrieve connections from this
  93.      * {@code DataSource}.
  94.      *
  95.      * @param ds
  96.      *            The {@code DataSource} to retrieve connections from.
  97.      * @param pmdKnownBroken
  98.      *            Some drivers don't support
  99.      *            {@link ParameterMetaData#getParameterType(int) }; if
  100.      *            {@code pmdKnownBroken} is set to true, we won't even try
  101.      *            it; if false, we'll try it, and if it breaks, we'll remember
  102.      *            not to use it again.
  103.      */
  104.     public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken) {
  105.         this.pmdKnownBroken = pmdKnownBroken;
  106.         this.ds = ds;
  107.         this.stmtConfig = null;
  108.     }

  109.     /**
  110.      * Constructor for QueryRunner that takes a {@code DataSource}, a {@code StatementConfiguration}, and
  111.      * controls the use of {@code ParameterMetaData}.  Methods that do not take a {@code Connection} parameter
  112.      * will retrieve connections from this {@code DataSource}.
  113.      *
  114.      * @param ds The {@code DataSource} to retrieve connections from.
  115.      * @param pmdKnownBroken Some drivers don't support {@link java.sql.ParameterMetaData#getParameterType(int) };
  116.      * if {@code pmdKnownBroken} is set to true, we won't even try it; if false, we'll try it,
  117.      * and if it breaks, we'll remember not to use it again.
  118.      * @param stmtConfig The configuration to apply to statements when they are prepared.
  119.      */
  120.     public AbstractQueryRunner(final DataSource ds, final boolean pmdKnownBroken, final StatementConfiguration stmtConfig) {
  121.         this.pmdKnownBroken = pmdKnownBroken;
  122.         this.ds = ds;
  123.         this.stmtConfig = stmtConfig;
  124.     }

  125.     /**
  126.      * Constructor for QueryRunner that takes a {@code DataSource} to use and a {@code StatementConfiguration}.
  127.      *
  128.      * Methods that do not take a {@code Connection} parameter will retrieve connections from this
  129.      * {@code DataSource}.
  130.      *
  131.      * @param ds The {@code DataSource} to retrieve connections from.
  132.      * @param stmtConfig The configuration to apply to statements when they are prepared.
  133.      */
  134.     public AbstractQueryRunner(final DataSource ds, final StatementConfiguration stmtConfig) {
  135.         this.ds = ds;
  136.         this.stmtConfig = stmtConfig;
  137.     }

  138.     /**
  139.      * Constructor for QueryRunner that takes a {@code StatementConfiguration} to configure statements when
  140.      * preparing them.
  141.      *
  142.      * @param stmtConfig The configuration to apply to statements when they are prepared.
  143.      */
  144.     public AbstractQueryRunner(final StatementConfiguration stmtConfig) {
  145.         this.ds = null;
  146.         this.stmtConfig = stmtConfig;
  147.     }

  148.     /**
  149.      * Close a {@code Connection}. This implementation avoids closing if
  150.      * null and does <strong>not</strong> suppress any exceptions. Subclasses
  151.      * can override to provide special handling like logging.
  152.      *
  153.      * @param conn
  154.      *            Connection to close
  155.      * @throws SQLException
  156.      *             if a database access error occurs
  157.      * @since 1.1
  158.      */
  159.     protected void close(final Connection conn) throws SQLException {
  160.         DbUtils.close(conn);
  161.     }

  162.     /**
  163.      * Close a {@code ResultSet}. This implementation avoids closing if
  164.      * null and does <strong>not</strong> suppress any exceptions. Subclasses
  165.      * can override to provide special handling like logging.
  166.      *
  167.      * @param resultSet
  168.      *            ResultSet to close
  169.      * @throws SQLException
  170.      *             if a database access error occurs
  171.      * @since 1.1
  172.      */
  173.     protected void close(final ResultSet resultSet) throws SQLException {
  174.         DbUtils.close(resultSet);
  175.     }

  176.     /**
  177.      * Close a {@code Statement}. 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 stmt
  182.      *            Statement to close
  183.      * @throws SQLException
  184.      *             if a database access error occurs
  185.      * @since 1.1
  186.      */
  187.     protected void close(final Statement stmt) throws SQLException {
  188.         DbUtils.close(stmt);
  189.     }

  190.     /**
  191.      * Calls {@link DbUtils#closeQuietly(Connection)}.
  192.      *
  193.      * @param conn Connection to close.
  194.      * @since 1.8.0
  195.      */
  196.     protected void closeQuietly(final Connection conn) {
  197.         DbUtils.closeQuietly(conn);
  198.     }

  199.     /**
  200.      * Calls {@link DbUtils#closeQuietly(ResultSet)}.
  201.      *
  202.      * @param resultSet ResultSet to close.
  203.      * @since 1.8.0
  204.      */
  205.     protected void closeQuietly(final ResultSet resultSet) {
  206.         DbUtils.closeQuietly(resultSet);
  207.     }

  208.     /**
  209.      * Calls {@link DbUtils#closeQuietly(Statement)}.
  210.      *
  211.      * @param statement ResultSet to close.
  212.      * @since 1.8.0
  213.      */
  214.     protected void closeQuietly(final Statement statement) {
  215.         DbUtils.closeQuietly(statement);
  216.     }

  217.     private void configureStatement(final Statement stmt) throws SQLException {

  218.         if (stmtConfig != null) {
  219.             if (stmtConfig.isFetchDirectionSet()) {
  220.                 stmt.setFetchDirection(stmtConfig.getFetchDirection());
  221.             }

  222.             if (stmtConfig.isFetchSizeSet()) {
  223.                 stmt.setFetchSize(stmtConfig.getFetchSize());
  224.             }

  225.             if (stmtConfig.isMaxFieldSizeSet()) {
  226.                 stmt.setMaxFieldSize(stmtConfig.getMaxFieldSize());
  227.             }

  228.             if (stmtConfig.isMaxRowsSet()) {
  229.                 stmt.setMaxRows(stmtConfig.getMaxRows());
  230.             }

  231.             if (stmtConfig.isQueryTimeoutSet()) {
  232.                 stmt.setQueryTimeout(stmtConfig.getQueryTimeout());
  233.             }
  234.         }
  235.     }

  236.     /**
  237.      * Fill the {@code PreparedStatement} replacement parameters with the
  238.      * given objects.
  239.      *
  240.      * @param stmt
  241.      *            PreparedStatement to fill
  242.      * @param params
  243.      *            Query replacement parameters; {@code null} is a valid
  244.      *            value to pass in.
  245.      * @throws SQLException
  246.      *             if a database access error occurs
  247.      */
  248.     public void fillStatement(final PreparedStatement stmt, final Object... params) throws SQLException {
  249.         ParameterMetaData pmd = null;
  250.         if (!pmdKnownBroken) {
  251.             try {
  252.                 pmd = this.getParameterMetaData(stmt);
  253.                 if (pmd == null) { // can be returned by implementations that don't support the method
  254.                     pmdKnownBroken = true;
  255.                 }
  256.             } catch (final SQLFeatureNotSupportedException ex) {
  257.                 pmdKnownBroken = true;
  258.             }
  259.             // TODO see DBUTILS-117: would it make sense to catch any other SQLEx types here?
  260.         }
  261.         fillStatement(stmt, pmd, params);
  262.     }

  263.     /**
  264.      * Fill the {@code PreparedStatement} replacement parameters with the
  265.      * given objects, and prefetched parameter metadata.
  266.      *
  267.      * @param stmt
  268.      *            PreparedStatement to fill
  269.      * @param pmd
  270.      *            Prefetched parameter metadata
  271.      * @param params
  272.      *            Query replacement parameters; {@code null} is a valid
  273.      *            value to pass in.
  274.      * @throws SQLException
  275.      *             if a database access error occurs
  276.      */
  277.     public void fillStatement(final PreparedStatement stmt, final ParameterMetaData pmd, final Object... params)
  278.             throws SQLException {

  279.         // check the parameter count, if we can
  280.         if (!pmdKnownBroken && pmd != null) {
  281.             final int stmtCount = pmd.getParameterCount();
  282.             final int paramsCount = params == null ? 0 : params.length;

  283.             if (stmtCount != paramsCount) {
  284.                 throw new SQLException("Wrong number of parameters: expected "
  285.                         + stmtCount + ", was given " + paramsCount);
  286.             }
  287.         }

  288.         // nothing to do here
  289.         if (params == null) {
  290.             return;
  291.         }

  292.         CallableStatement call = null;
  293.         if (stmt instanceof CallableStatement) {
  294.             call = (CallableStatement) stmt;
  295.         }

  296.         for (int i = 0; i < params.length; i++) {
  297.             if (params[i] != null) {
  298.                 if (call != null && params[i] instanceof OutParameter) {
  299.                     ((OutParameter<?>) params[i]).register(call, i + 1);
  300.                 } else {
  301.                     stmt.setObject(i + 1, params[i]);
  302.                 }
  303.             } else {
  304.                 // VARCHAR works with many drivers regardless
  305.                 // of the actual column type. Oddly, NULL and
  306.                 // OTHER don't work with Oracle's drivers.
  307.                 int sqlType = Types.VARCHAR;
  308.                 if (!pmdKnownBroken) {
  309.                     // TODO see DBUTILS-117: does it make sense to catch SQLEx here?
  310.                     try {
  311.                         /*
  312.                          * It's not possible for pmdKnownBroken to change from true to false, (once true, always true) so pmd cannot be null here.
  313.                          */
  314.                         sqlType = pmd.getParameterType(i + 1);
  315.                     } catch (final SQLException e) {
  316.                         pmdKnownBroken = true;
  317.                     }
  318.                 }
  319.                 stmt.setNull(i + 1, sqlType);
  320.             }
  321.         }
  322.     }

  323.     /**
  324.      * Fill the {@code PreparedStatement} replacement parameters with the
  325.      * given object's bean property values.
  326.      *
  327.      * @param stmt
  328.      *            PreparedStatement to fill
  329.      * @param bean
  330.      *            a JavaBean object
  331.      * @param properties
  332.      *            an ordered array of properties; this gives the order to insert
  333.      *            values in the statement
  334.      * @throws SQLException
  335.      *             if a database access error occurs
  336.      */
  337.     public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,
  338.             final PropertyDescriptor[] properties) throws SQLException {
  339.         final Object[] params = new Object[properties.length];
  340.         for (int i = 0; i < properties.length; i++) {
  341.             final PropertyDescriptor property = properties[i];
  342.             Object value = null;
  343.             final Method method = property.getReadMethod();
  344.             if (method == null) {
  345.                 throw new IllegalArgumentException("No read method for bean property " + bean.getClass() + " " + property.getName());
  346.             }
  347.             try {
  348.                 value = method.invoke(bean);
  349.             } catch (final IllegalArgumentException e) {
  350.                 throw new IllegalArgumentException("Couldn't invoke method with 0 arguments: " + method, e);
  351.             } catch (final InvocationTargetException | IllegalAccessException e) {
  352.                 throw new IllegalArgumentException("Couldn't invoke method: " + method, e);
  353.             }
  354.             params[i] = value;
  355.         }
  356.         fillStatement(stmt, params);
  357.     }

  358.     /**
  359.      * Fill the {@code PreparedStatement} replacement parameters with the
  360.      * given object's bean property values.
  361.      *
  362.      * @param stmt
  363.      *            PreparedStatement to fill
  364.      * @param bean
  365.      *            A JavaBean object
  366.      * @param propertyNames
  367.      *            An ordered array of property names (these should match the
  368.      *            getters/setters); this gives the order to insert values in the
  369.      *            statement
  370.      * @throws SQLException
  371.      *             If a database access error occurs
  372.      */
  373.     public void fillStatementWithBean(final PreparedStatement stmt, final Object bean,
  374.             final String... propertyNames) throws SQLException {
  375.         PropertyDescriptor[] descriptors;
  376.         try {
  377.             descriptors = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
  378.         } catch (final IntrospectionException e) {
  379.             throw new RuntimeException("Couldn't introspect bean " + bean.getClass().toString(), e);
  380.         }
  381.         final PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
  382.         for (int i = 0; i < propertyNames.length; i++) {
  383.             final String propertyName = propertyNames[i];
  384.             if (propertyName == null) {
  385.                 throw new NullPointerException("propertyName can't be null: " + i);
  386.             }
  387.             boolean found = false;
  388.             for (final PropertyDescriptor descriptor : descriptors) {
  389.                 if (propertyName.equals(descriptor.getName())) {
  390.                     sorted[i] = descriptor;
  391.                     found = true;
  392.                     break;
  393.                 }
  394.             }
  395.             if (!found) {
  396.                 throw new IllegalStateException("Couldn't find bean property: " + bean.getClass() + " " + propertyName);
  397.             }
  398.         }
  399.         fillStatementWithBean(stmt, bean, sorted);
  400.     }

  401.     /**
  402.      * Returns the {@code DataSource} this runner is using.
  403.      * {@code QueryRunner} methods always call this method to get the
  404.      * {@code DataSource} so subclasses can provide specialized behavior.
  405.      *
  406.      * @return DataSource the runner is using
  407.      */
  408.     public DataSource getDataSource() {
  409.         return this.ds;
  410.     }

  411.     /**
  412.      * Get the {@code ParameterMetaData} of the prepared statement, if the {@code pmdKnownBroken}
  413.      * is set to false.
  414.      *
  415.      * @param stmt
  416.      *            PreparedStatement of which to query the metadata of parameters
  417.      * @return the metadata of parameters
  418.      * @throws SQLException
  419.      *            if a database access error occurs
  420.      */
  421.     public ParameterMetaData getParameterMetaData(final PreparedStatement stmt) throws SQLException {
  422.         ParameterMetaData pmd = null;
  423.         if (!pmdKnownBroken) {
  424.             try {
  425.                 pmd = stmt.getParameterMetaData();
  426.             } catch (final SQLFeatureNotSupportedException ex) {
  427.                 pmdKnownBroken = true;
  428.             }
  429.         }
  430.         return pmd;
  431.     }

  432.     /**
  433.      * Some drivers don't support
  434.      * {@link ParameterMetaData#getParameterType(int) }; if
  435.      * {@code pmdKnownBroken} is set to true, we won't even try it; if
  436.      * false, we'll try it, and if it breaks, we'll remember not to use it
  437.      * again.
  438.      *
  439.      * @return the flag to skip (or not)
  440.      *         {@link ParameterMetaData#getParameterType(int) }
  441.      * @since 1.4
  442.      */
  443.     public boolean isPmdKnownBroken() {
  444.         return pmdKnownBroken;
  445.     }

  446.     /**
  447.      * Factory method that creates and initializes a
  448.      * {@code CallableStatement} object for the given SQL.
  449.      * {@code QueryRunner} methods always call this method to prepare
  450.      * callable statements for them. Subclasses can override this method to
  451.      * provide special CallableStatement configuration if needed. This
  452.      * implementation simply calls {@code conn.prepareCall(sql)}.
  453.      *
  454.      * @param conn
  455.      *            The {@code Connection} used to create the
  456.      *            {@code CallableStatement}
  457.      * @param sql
  458.      *            The SQL statement to prepare.
  459.      * @return An initialized {@code CallableStatement}.
  460.      * @throws SQLException
  461.      *             if a database access error occurs
  462.      */
  463.     protected CallableStatement prepareCall(final Connection conn, final String sql)
  464.             throws SQLException {

  465.         return conn.prepareCall(sql);
  466.     }

  467.     /**
  468.      * Factory method that creates and initializes a {@code Connection}
  469.      * object. {@code QueryRunner} methods always call this method to
  470.      * retrieve connections from its DataSource. Subclasses can override this
  471.      * method to provide special {@code Connection} configuration if
  472.      * needed. This implementation simply calls {@code ds.getConnection()}.
  473.      *
  474.      * @return An initialized {@code Connection}.
  475.      * @throws SQLException
  476.      *             if a database access error occurs
  477.      * @since 1.1
  478.      */
  479.     protected Connection prepareConnection() throws SQLException {
  480.         if (this.getDataSource() == null) {
  481.             throw new SQLException(
  482.                     "QueryRunner requires a DataSource to be "
  483.                             + "invoked in this way, or a Connection should be passed in");
  484.         }
  485.         return this.getDataSource().getConnection();
  486.     }

  487.     /**
  488.      * Factory method that creates and initializes a
  489.      * {@code PreparedStatement} object for the given SQL.
  490.      * {@code QueryRunner} methods always call this method to prepare
  491.      * statements for them. Subclasses can override this method to provide
  492.      * special PreparedStatement configuration if needed. This implementation
  493.      * simply calls {@code conn.prepareStatement(sql)}.
  494.      *
  495.      * @param conn
  496.      *            The {@code Connection} used to create the
  497.      *            {@code PreparedStatement}
  498.      * @param sql
  499.      *            The SQL statement to prepare.
  500.      * @return An initialized {@code PreparedStatement}.
  501.      * @throws SQLException
  502.      *             if a database access error occurs
  503.      */
  504.     protected PreparedStatement prepareStatement(final Connection conn, final String sql)
  505.             throws SQLException {

  506.         @SuppressWarnings("resource")
  507.         final
  508.         PreparedStatement ps = conn.prepareStatement(sql);
  509.         try {
  510.             configureStatement(ps);
  511.         } catch (final SQLException e) {
  512.             ps.close();
  513.             throw e;
  514.         }
  515.         return ps;
  516.     }

  517.     /**
  518.      * Factory method that creates and initializes a
  519.      * {@code PreparedStatement} object for the given SQL.
  520.      * {@code QueryRunner} methods always call this method to prepare
  521.      * statements for them. Subclasses can override this method to provide
  522.      * special PreparedStatement configuration if needed. This implementation
  523.      * simply calls {@code conn.prepareStatement(sql, returnedKeys)}
  524.      * which will result in the ability to retrieve the automatically-generated
  525.      * keys from an auto_increment column.
  526.      *
  527.      * @param conn
  528.      *            The {@code Connection} used to create the
  529.      *            {@code PreparedStatement}
  530.      * @param sql
  531.      *            The SQL statement to prepare.
  532.      * @param returnedKeys
  533.      *            Flag indicating whether to return generated keys or not.
  534.      *
  535.      * @return An initialized {@code PreparedStatement}.
  536.      * @throws SQLException
  537.      *             if a database access error occurs
  538.      * @since 1.6
  539.      */
  540.     protected PreparedStatement prepareStatement(final Connection conn, final String sql, final int returnedKeys)
  541.             throws SQLException {

  542.         @SuppressWarnings("resource")
  543.         final
  544.         PreparedStatement ps = conn.prepareStatement(sql, returnedKeys);
  545.         try {
  546.             configureStatement(ps);
  547.         } catch (final SQLException e) {
  548.             ps.close();
  549.             throw e;
  550.         }
  551.         return ps;
  552.     }

  553.     /**
  554.      * Throws a new exception with a more informative error message.
  555.      *
  556.      * @param cause
  557.      *            The original exception that will be chained to the new
  558.      *            exception when it's rethrown.
  559.      *
  560.      * @param sql
  561.      *            The query that was executing when the exception happened.
  562.      *
  563.      * @param params
  564.      *            The query replacement parameters; {@code null} is a valid
  565.      *            value to pass in.
  566.      *
  567.      * @throws SQLException
  568.      *             if a database access error occurs
  569.      */
  570.     protected void rethrow(final SQLException cause, final String sql, final Object... params)
  571.             throws SQLException {

  572.         String causeMessage = cause.getMessage();
  573.         if (causeMessage == null) {
  574.             causeMessage = "";
  575.         }
  576.         final StringBuilder msg = new StringBuilder(causeMessage);

  577.         msg.append(" Query: ");
  578.         msg.append(sql);
  579.         msg.append(" Parameters: ");

  580.         if (params == null) {
  581.             msg.append("[]");
  582.         } else {
  583.             msg.append(Arrays.deepToString(params));
  584.         }

  585.         final SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
  586.                 cause.getErrorCode());
  587.         e.setNextException(cause);

  588.         throw e;
  589.     }

  590.     /**
  591.      * Wrap the {@code ResultSet} in a decorator before processing it. This
  592.      * implementation returns the {@code ResultSet} it is given without any
  593.      * decoration.
  594.      *
  595.      * <p>
  596.      * Often, the implementation of this method can be done in an anonymous
  597.      * inner class like this:
  598.      * </p>
  599.      *
  600.      * <pre>
  601.      * QueryRunner run = new QueryRunner() {
  602.      *     protected ResultSet wrap(ResultSet rs) {
  603.      *         return StringTrimmedResultSet.wrap(rs);
  604.      *     }
  605.      * };
  606.      * </pre>
  607.      *
  608.      * @param rs
  609.      *            The {@code ResultSet} to decorate; never
  610.      *            {@code null}.
  611.      * @return The {@code ResultSet} wrapped in some decorator.
  612.      */
  613.     protected ResultSet wrap(final ResultSet rs) {
  614.         return rs;
  615.     }

  616. }