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}