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}