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