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 * @author Craig R. McClanahan
066 * @version $Revision: 926685 $ $Date: 2010-03-23 17:59:08 +0000 (Tue, 23 Mar 2010) $
067 */
068
069public class RowSetDynaClass extends JDBCDynaClass implements DynaClass, Serializable {
070
071
072    // ----------------------------------------------------- Instance variables
073    
074    /**
075     * <p>Limits the size of the returned list.  The call to 
076     * <code>getRows()</code> will return at most limit number of rows.
077     * If less than or equal to 0, does not limit the size of the result.
078     */
079    protected int limit = -1;
080
081    /**
082     * <p>The list of {@link DynaBean}s representing the contents of
083     * the original <code>ResultSet</code> on which this
084     * {@link RowSetDynaClass} was based.</p>
085     */
086    protected List rows = new ArrayList();
087
088    // ----------------------------------------------------------- Constructors
089
090
091    /**
092     * <p>Construct a new {@link RowSetDynaClass} for the specified
093     * <code>ResultSet</code>.  The property names corresponding
094     * to column names in the result set will be lower cased.</p>
095     *
096     * @param resultSet The result set to be wrapped
097     *
098     * @exception NullPointerException if <code>resultSet</code>
099     *  is <code>null</code>
100     * @exception SQLException if the metadata for this result set
101     *  cannot be introspected
102     */
103    public RowSetDynaClass(ResultSet resultSet) throws SQLException {
104
105        this(resultSet, true, -1);
106
107    }
108
109    /**
110     * <p>Construct a new {@link RowSetDynaClass} for the specified
111     * <code>ResultSet</code>.  The property names corresponding
112     * to column names in the result set will be lower cased.</p>
113     * 
114     * If <code>limit</code> is not less than 0, max <code>limit</code>
115     * number of rows will be copied into the list. 
116     *
117     * @param resultSet The result set to be wrapped
118     * @param limit The maximum for the size of the result. 
119     *
120     * @exception NullPointerException if <code>resultSet</code>
121     *  is <code>null</code>
122     * @exception SQLException if the metadata for this result set
123     *  cannot be introspected
124     */
125    public RowSetDynaClass(ResultSet resultSet, int limit) throws SQLException {
126
127        this(resultSet, true, limit);
128
129    }
130
131
132    /**
133     * <p>Construct a new {@link RowSetDynaClass} for the specified
134     * <code>ResultSet</code>.  The property names corresponding
135     * to the column names in the result set will be lower cased or not,
136     * depending on the specified <code>lowerCase</code> value.</p>
137     *
138     * If <code>limit</code> is not less than 0, max <code>limit</code>
139     * number of rows will be copied into the resultset. 
140     *
141     *
142     * @param resultSet The result set to be wrapped
143     * @param lowerCase Should property names be lower cased?
144     *
145     * @exception NullPointerException if <code>resultSet</code>
146     *  is <code>null</code>
147     * @exception SQLException if the metadata for this result set
148     *  cannot be introspected
149     */
150    public RowSetDynaClass(ResultSet resultSet, boolean lowerCase)
151                                                    throws SQLException {
152        this(resultSet, lowerCase, -1);
153
154    }
155
156    /**
157     * <p>Construct a new {@link RowSetDynaClass} for the specified
158     * <code>ResultSet</code>.  The property names corresponding
159     * to the column names in the result set will be lower cased or not,
160     * depending on the specified <code>lowerCase</code> value.</p>
161     *
162     * <p><strong>WARNING</strong> - If you specify <code>false</code>
163     * for <code>lowerCase</code>, the returned property names will
164     * exactly match the column names returned by your JDBC driver.
165     * Because different drivers might return column names in different
166     * cases, the property names seen by your application will vary
167     * depending on which JDBC driver you are using.</p>
168     *
169     * @param resultSet The result set to be wrapped
170     * @param lowerCase Should property names be lower cased?
171     * @param limit Maximum limit for the <code>List</code> of {@link DynaBean}
172     *
173     * @exception NullPointerException if <code>resultSet</code>
174     *  is <code>null</code>
175     * @exception SQLException if the metadata for this result set
176     *  cannot be introspected
177     */
178    public RowSetDynaClass(ResultSet resultSet, boolean lowerCase, int limit)
179                                                            throws SQLException {
180
181        this(resultSet, lowerCase, limit, false);
182
183    }
184
185    /**
186     * <p>Construct a new {@link RowSetDynaClass} for the specified
187     * <code>ResultSet</code>.  The property names corresponding
188     * to the column names in the result set will be lower cased or not,
189     * depending on the specified <code>lowerCase</code> value.</p>
190     *
191     * <p><strong>WARNING</strong> - If you specify <code>false</code>
192     * for <code>lowerCase</code>, the returned property names will
193     * exactly match the column names returned by your JDBC driver.
194     * Because different drivers might return column names in different
195     * cases, the property names seen by your application will vary
196     * depending on which JDBC driver you are using.</p>
197     *
198     * @param resultSet The result set to be wrapped
199     * @param lowerCase Should property names be lower cased?
200     * @param useColumnLabel true if the column label should be used, otherwise false
201     *
202     * @exception NullPointerException if <code>resultSet</code>
203     *  is <code>null</code>
204     * @exception SQLException if the metadata for this result set
205     *  cannot be introspected
206     * @since 1.8.3
207     */
208    public RowSetDynaClass(ResultSet resultSet, boolean lowerCase, boolean useColumnLabel)
209        throws SQLException {
210        this(resultSet, lowerCase, -1, useColumnLabel);
211
212    }
213
214    /**
215     * <p>Construct a new {@link RowSetDynaClass} for the specified
216     * <code>ResultSet</code>.  The property names corresponding
217     * to the column names in the result set will be lower cased or not,
218     * depending on the specified <code>lowerCase</code> value.</p>
219     *
220     * <p><strong>WARNING</strong> - If you specify <code>false</code>
221     * for <code>lowerCase</code>, the returned property names will
222     * exactly match the column names returned by your JDBC driver.
223     * Because different drivers might return column names in different
224     * cases, the property names seen by your application will vary
225     * depending on which JDBC driver you are using.</p>
226     *
227     * @param resultSet The result set to be wrapped
228     * @param lowerCase Should property names be lower cased?
229     * @param limit Maximum limit for the <code>List</code> of {@link DynaBean}
230     * @param useColumnLabel true if the column label should be used, otherwise false
231     *
232     * @exception NullPointerException if <code>resultSet</code>
233     *  is <code>null</code>
234     * @exception SQLException if the metadata for this result set
235     *  cannot be introspected
236     * @since 1.8.3
237     */
238    public RowSetDynaClass(ResultSet resultSet, boolean lowerCase, int limit, boolean useColumnLabel)
239                                                            throws SQLException {
240
241        if (resultSet == null) {
242            throw new NullPointerException();
243        }
244        this.lowerCase = lowerCase;
245        this.limit = limit;
246        setUseColumnLabel(useColumnLabel);
247        introspect(resultSet);
248        copy(resultSet);
249
250    }
251
252    /**
253     * <p>Return a <code>List</code> containing the {@link DynaBean}s that
254     * represent the contents of each <code>Row</code> from the
255     * <code>ResultSet</code> that was the basis of this
256     * {@link RowSetDynaClass} instance.  These {@link DynaBean}s are
257     * disconnected from the database itself, so there is no problem with
258     * modifying the contents of the list, or the values of the properties
259     * of these {@link DynaBean}s.  However, it is the application's
260     * responsibility to persist any such changes back to the database,
261     * if it so desires.</p>
262     *
263     * @return A <code>List</code> of {@link DynaBean} instances
264     */
265    public List getRows() {
266
267        return (this.rows);
268
269    }
270
271
272    // ------------------------------------------------------ Protected Methods
273
274
275    /**
276     * <p>Copy the column values for each row in the specified
277     * <code>ResultSet</code> into a newly created {@link DynaBean}, and add
278     * this bean to the list of {@link DynaBean}s that will later by
279     * returned by a call to <code>getRows()</code>.</p>
280     *
281     * @param resultSet The <code>ResultSet</code> whose data is to be
282     *  copied
283     *
284     * @exception SQLException if an error is encountered copying the data
285     */
286    protected void copy(ResultSet resultSet) throws SQLException {
287
288        int cnt = 0;
289        while (resultSet.next() && (limit < 0  || cnt++ < limit) ) {
290            DynaBean bean = createDynaBean();
291            for (int i = 0; i < properties.length; i++) {
292                String name = properties[i].getName();
293                Object value = getObject(resultSet, name);
294                bean.set(name, value);
295            }
296            rows.add(bean);
297        }
298
299    }
300
301
302    /**
303     * <p>Create and return a new {@link DynaBean} instance to be used for
304     * representing a row in the underlying result set.</p>
305     *
306     * @return A new <code>DynaBean</code> instance
307     */
308    protected DynaBean createDynaBean() {
309
310        return (new BasicDynaBean(this));
311
312    }
313
314
315}