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