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    
019    package org.apache.commons.beanutils;
020    
021    
022    import java.io.Serializable;
023    import java.sql.ResultSet;
024    import java.sql.SQLException;
025    import java.util.ArrayList;
026    import 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    
069    public 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    }