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: 556233 $ $Date: 2007-07-14 07:37:06 +0100 (Sat, 14 Jul 2007) $
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            if (resultSet == null) {
182                throw new NullPointerException();
183            }
184            this.lowerCase = lowerCase;
185            this.limit = limit;
186            introspect(resultSet);
187            copy(resultSet);
188    
189        }
190    
191        /**
192         * <p>Return a <code>List</code> containing the {@link DynaBean}s that
193         * represent the contents of each <code>Row</code> from the
194         * <code>ResultSet</code> that was the basis of this
195         * {@link RowSetDynaClass} instance.  These {@link DynaBean}s are
196         * disconnected from the database itself, so there is no problem with
197         * modifying the contents of the list, or the values of the properties
198         * of these {@link DynaBean}s.  However, it is the application's
199         * responsibility to persist any such changes back to the database,
200         * if it so desires.</p>
201         *
202         * @return A <code>List</code> of {@link DynaBean} instances
203         */
204        public List getRows() {
205    
206            return (this.rows);
207    
208        }
209    
210    
211        // ------------------------------------------------------ Protected Methods
212    
213    
214        /**
215         * <p>Copy the column values for each row in the specified
216         * <code>ResultSet</code> into a newly created {@link DynaBean}, and add
217         * this bean to the list of {@link DynaBean}s that will later by
218         * returned by a call to <code>getRows()</code>.</p>
219         *
220         * @param resultSet The <code>ResultSet</code> whose data is to be
221         *  copied
222         *
223         * @exception SQLException if an error is encountered copying the data
224         */
225        protected void copy(ResultSet resultSet) throws SQLException {
226    
227            int cnt = 0;
228            while (resultSet.next() && (limit < 0  || cnt++ < limit) ) {
229                DynaBean bean = createDynaBean();
230                for (int i = 0; i < properties.length; i++) {
231                    String name = properties[i].getName();
232                    Object value = getObject(resultSet, name);
233                    bean.set(name, value);
234                }
235                rows.add(bean);
236            }
237    
238        }
239    
240    
241        /**
242         * <p>Create and return a new {@link DynaBean} instance to be used for
243         * representing a row in the underlying result set.</p>
244         *
245         * @return A new <code>DynaBean</code> instance
246         */
247        protected DynaBean createDynaBean() {
248    
249            return (new BasicDynaBean(this));
250    
251        }
252    
253    
254    }