    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 */
    017
    018package org.apache.commons.beanutils;
    019
    020import java.sql.ResultSet;
    021import java.sql.SQLException;
    022import java.util.ArrayList;
    023import java.util.List;
    024import java.util.Objects;
    025
    026/**
    027 * <p>Implements {@link DynaClass} to create an in-memory collection
    028 * of {@link DynaBean}s representing the results of an SQL query.  Once the
    029 * {@link DynaClass} instance has been created, the JDBC <code>ResultSet</code>
    030 * and <code>Statement</code> on which it is based can be closed, and the
    031 * underlying <code>Connection</code> can be returned to its connection pool
    032 * (if you are using one).</p>
    033 *
    034 * <p>The normal usage pattern is something like:</p>
    035 * <pre>
    036 *   Connection conn = ...;  // Acquire connection from pool
    037 *   Statement stmt = conn.createStatement();
    038 *   ResultSet rs = stmt.executeQuery("SELECT ...");
    039 *   RowSetDynaClass rsdc = new RowSetDynaClass(rs);
    040 *   rs.close();
    041 *   stmt.close();
    042 *   ...;                    // Return connection to pool
    043 *   List rows = rsdc.getRows();
    044 *   ...;                   // Process the rows as desired
    045 * </pre>
    046 *
    047 * <p>Each column in the result set will be represented as a {@link DynaBean}
    048 * property of the corresponding name (optionally forced to lower case
    049 * for portability).  There will be one {@link DynaBean} in the
    050 * <code>List</code> returned by <code>getRows()</code> for each
    051 * row in the original <code>ResultSet</code>.</p>
    052 *
    053 * <p>In general, instances of {@link RowSetDynaClass} can be serialized
    054 * and deserialized, which will automatically include the list of
    055 * {@link DynaBean}s representing the data content.  The only exception
    056 * to this rule would be when the underlying property values that were
    057 * copied from the <code>ResultSet</code> originally cannot themselves
    058 * be serialized.  Therefore, a {@link RowSetDynaClass} makes a very
    059 * convenient mechanism for transporting data sets to remote Java-based
    060 * application components.</p>
    061 *
    062 */
    063
    064public class RowSetDynaClass extends JDBCDynaClass {
    065
    066    private static final long serialVersionUID = 1L;
    067
    068    /**
    069     * <p>Limits the size of the returned list.  The call to
    070     * <code>getRows()</code> will return at most limit number of rows.
    071     * If less than or equal to 0, does not limit the size of the result.
    072     */
    073    protected int limit = -1;
    074
    075    /**
    076     * <p>The list of {@link DynaBean}s representing the contents of
    077     * the original <code>ResultSet</code> on which this
    078     * {@link RowSetDynaClass} was based.</p>
    079     */
    080    protected List<DynaBean> rows = new ArrayList<>();
    081
    082    /**
    083     * <p>Construct a new {@link RowSetDynaClass} for the specified
    084     * <code>ResultSet</code>.  The property names corresponding
    085     * to column names in the result set will be lower cased.</p>
    086     *
    087     * @param resultSet The result set to be wrapped
    088     * @throws NullPointerException if <code>resultSet</code>
    089     *  is <code>null</code>
    090     * @throws SQLException if the metadata for this result set
    091     *  cannot be introspected
    092     */
    093    public RowSetDynaClass(final ResultSet resultSet) throws SQLException {
    094
    095        this(resultSet, true, -1);
    096
    097    }
    098
    099    /**
    100     * <p>Construct a new {@link RowSetDynaClass} for the specified
    101     * <code>ResultSet</code>.  The property names corresponding
    102     * to the column names in the result set will be lower cased or not,
    103     * depending on the specified <code>lowerCase</code> value.</p>
    104     *
    105     * If <code>limit</code> is not less than 0, max <code>limit</code>
    106     * number of rows will be copied into the resultset.
    107     *
    108     *
    109     * @param resultSet The result set to be wrapped
    110     * @param lowerCase Should property names be lower cased?
    111     * @throws NullPointerException if <code>resultSet</code>
    112     *  is <code>null</code>
    113     * @throws SQLException if the metadata for this result set
    114     *  cannot be introspected
    115     */
    116    public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase)
    117                                                    throws SQLException {
    118        this(resultSet, lowerCase, -1);
    119
    120    }
    121
    122    /**
    123     * <p>Construct a new {@link RowSetDynaClass} for the specified
    124     * <code>ResultSet</code>.  The property names corresponding
    125     * to the column names in the result set will be lower cased or not,
    126     * depending on the specified <code>lowerCase</code> value.</p>
    127     *
    128     * <p><strong>WARNING</strong> - If you specify <code>false</code>
    129     * for <code>lowerCase</code>, the returned property names will
    130     * exactly match the column names returned by your JDBC driver.
    131     * Because different drivers might return column names in different
    132     * cases, the property names seen by your application will vary
    133     * depending on which JDBC driver you are using.</p>
    134     *
    135     * @param resultSet The result set to be wrapped
    136     * @param lowerCase Should property names be lower cased?
    137     * @param useColumnLabel true if the column label should be used, otherwise false
    138     * @throws NullPointerException if <code>resultSet</code>
    139     *  is <code>null</code>
    140     * @throws SQLException if the metadata for this result set
    141     *  cannot be introspected
    142     * @since 1.8.3
    143     */
    144    public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final boolean useColumnLabel)
    145        throws SQLException {
    146        this(resultSet, lowerCase, -1, useColumnLabel);
    147
    148    }
    149
    150    /**
    151     * <p>Construct a new {@link RowSetDynaClass} for the specified
    152     * <code>ResultSet</code>.  The property names corresponding
    153     * to the column names in the result set will be lower cased or not,
    154     * depending on the specified <code>lowerCase</code> value.</p>
    155     *
    156     * <p><strong>WARNING</strong> - If you specify <code>false</code>
    157     * for <code>lowerCase</code>, the returned property names will
    158     * exactly match the column names returned by your JDBC driver.
    159     * Because different drivers might return column names in different
    160     * cases, the property names seen by your application will vary
    161     * depending on which JDBC driver you are using.</p>
    162     *
    163     * @param resultSet The result set to be wrapped
    164     * @param lowerCase Should property names be lower cased?
    165     * @param limit Maximum limit for the <code>List</code> of {@link DynaBean}
    166     * @throws NullPointerException if <code>resultSet</code>
    167     *  is <code>null</code>
    168     * @throws SQLException if the metadata for this result set
    169     *  cannot be introspected
    170     */
    171    public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final int limit)
    172                                                            throws SQLException {
    173
    174        this(resultSet, lowerCase, limit, false);
    175
    176    }
    177
    178    /**
    179     * <p>Construct a new {@link RowSetDynaClass} for the specified
    180     * <code>ResultSet</code>.  The property names corresponding
    181     * to the column names in the result set will be lower cased or not,
    182     * depending on the specified <code>lowerCase</code> value.</p>
    183     *
    184     * <p><strong>WARNING</strong> - If you specify <code>false</code>
    185     * for <code>lowerCase</code>, the returned property names will
    186     * exactly match the column names returned by your JDBC driver.
    187     * Because different drivers might return column names in different
    188     * cases, the property names seen by your application will vary
    189     * depending on which JDBC driver you are using.</p>
    190     *
    191     * @param resultSet The result set to be wrapped
    192     * @param lowerCase Should property names be lower cased?
    193     * @param limit Maximum limit for the <code>List</code> of {@link DynaBean}
    194     * @param useColumnLabel true if the column label should be used, otherwise false
    195     * @throws NullPointerException if <code>resultSet</code>
    196     *  is <code>null</code>
    197     * @throws SQLException if the metadata for this result set
    198     *  cannot be introspected
    199     * @since 1.8.3
    200     */
    201    public RowSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final int limit, final boolean useColumnLabel)
    202            throws SQLException {
    203        Objects.requireNonNull(resultSet, "resultSet");
    204        this.lowerCase = lowerCase;
    205        this.limit = limit;
    206        setUseColumnLabel(useColumnLabel);
    207        introspect(resultSet);
    208        copy(resultSet);
    209    }
    210
    211    /**
    212     * <p>Construct a new {@link RowSetDynaClass} for the specified
    213     * <code>ResultSet</code>.  The property names corresponding
    214     * to column names in the result set will be lower cased.</p>
    215     *
    216     * If <code>limit</code> is not less than 0, max <code>limit</code>
    217     * number of rows will be copied into the list.
    218     *
    219     * @param resultSet The result set to be wrapped
    220     * @param limit The maximum for the size of the result.
    221     * @throws NullPointerException if <code>resultSet</code>
    222     *  is <code>null</code>
    223     * @throws SQLException if the metadata for this result set
    224     *  cannot be introspected
    225     */
    226    public RowSetDynaClass(final ResultSet resultSet, final int limit) throws SQLException {
    227
    228        this(resultSet, true, limit);
    229
    230    }
    231
    232    /**
    233     * <p>Copy the column values for each row in the specified
    234     * <code>ResultSet</code> into a newly created {@link DynaBean}, and add
    235     * this bean to the list of {@link DynaBean}s that will later by
    236     * returned by a call to <code>getRows()</code>.</p>
    237     *
    238     * @param resultSet The <code>ResultSet</code> whose data is to be
    239     *  copied
    240     *
    241     * @throws SQLException if an error is encountered copying the data
    242     */
    243    protected void copy(final ResultSet resultSet) throws SQLException {
    244
    245        int cnt = 0;
    246        while (resultSet.next() && (limit < 0 || cnt++ < limit)) {
    247            final DynaBean bean = createDynaBean();
    248            for (final DynaProperty propertie : properties) {
    249                final String name = propertie.getName();
    250                final Object value = getObject(resultSet, name);
    251                bean.set(name, value);
    252            }
    253            rows.add(bean);
    254        }
    255
    256    }
    257
    258    /**
    259     * <p>Create and return a new {@link DynaBean} instance to be used for
    260     * representing a row in the underlying result set.</p>
    261     *
    262     * @return A new <code>DynaBean</code> instance
    263     */
    264    protected DynaBean createDynaBean() {
    265
    266        return new BasicDynaBean(this);
    267
    268    }
    269
    270    /**
    271     * <p>Return a <code>List</code> containing the {@link DynaBean}s that
    272     * represent the contents of each <code>Row</code> from the
    273     * <code>ResultSet</code> that was the basis of this
    274     * {@link RowSetDynaClass} instance.  These {@link DynaBean}s are
    275     * disconnected from the database itself, so there is no problem with
    276     * modifying the contents of the list, or the values of the properties
    277     * of these {@link DynaBean}s.  However, it is the application's
    278     * responsibility to persist any such changes back to the database,
    279     * if it so desires.</p>
    280     *
    281     * @return A <code>List</code> of {@link DynaBean} instances
    282     */
    283    public List<DynaBean> getRows() {
    284
    285        return this.rows;
    286
    287    }
    288
    289}