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 *      https://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.Iterator;
023import java.util.Objects;
024
025/**
026 * <p>Implements <code>DynaClass</code> for DynaBeans that wrap the
027 * <code>java.sql.Row</code> objects of a <code>java.sql.ResultSet</code>.
028 * The normal usage pattern is something like:</p>
029 * <pre>
030 *   ResultSet rs = ...;
031 *   ResultSetDynaClass rsdc = new ResultSetDynaClass(rs);
032 *   Iterator rows = rsdc.iterator();
033 *   while (rows.hasNext())  {
034 *     DynaBean row = (DynaBean) rows.next();
035 *     ... process this row ...
036 *   }
037 *   rs.close();
038 * </pre>
039 *
040 * <p>Each column in the result set will be represented as a DynaBean
041 * property of the corresponding name (optionally forced to lower case
042 * for portability).</p>
043 *
044 * <p><strong>WARNING</strong> - Any {@link DynaBean} instance returned by
045 * this class, or from the <code>Iterator</code> returned by the
046 * <code>iterator()</code> method, is directly linked to the row that the
047 * underlying result set is currently positioned at.  This has the following
048 * implications:</p>
049 * <ul>
050 * <li>Once you retrieve a different {@link DynaBean} instance, you should
051 *     no longer use any previous instance.</li>
052 * <li>Changing the position of the underlying result set will change the
053 *     data that the {@link DynaBean} references.</li>
054 * <li>Once the underlying result set is closed, the {@link DynaBean}
055 *     instance may no longer be used.</li>
056 * </ul>
057 *
058 * <p>Any database data that you wish to utilize outside the context of the
059 * current row of an open result set must be copied.  For example, you could
060 * use the following code to create standalone copies of the information in
061 * a result set:</p>
062 * <pre>
063 *   ArrayList results = new ArrayList(); // To hold copied list
064 *   ResultSetDynaClass rsdc = ...;
065 *   DynaProperty[] properties = rsdc.getDynaProperties();
066 *   BasicDynaClass bdc =
067 *     new BasicDynaClass("foo", BasicDynaBean.class,
068 *                        rsdc.getDynaProperties());
069 *   Iterator rows = rsdc.iterator();
070 *   while (rows.hasNext()) {
071 *     DynaBean oldRow = (DynaBean) rows.next();
072 *     DynaBean newRow = bdc.newInstance();
073 *     PropertyUtils.copyProperties(newRow, oldRow);
074 *     results.add(newRow);
075 *   }
076 * </pre>
077 *
078 */
079
080public class ResultSetDynaClass extends JDBCDynaClass {
081
082    private static final long serialVersionUID = 1L;
083
084    /**
085     * <p>The <code>ResultSet</code> we are wrapping.</p>
086     */
087    protected ResultSet resultSet;
088
089    /**
090     * <p>Construct a new ResultSetDynaClass for the specified
091     * <code>ResultSet</code>.  The property names corresponding
092     * to column names in the result set will be lower cased.</p>
093     *
094     * @param resultSet The result set to be wrapped
095     * @throws NullPointerException if <code>resultSet</code>
096     *  is <code>null</code>
097     * @throws SQLException if the metadata for this result set
098     *  cannot be introspected
099     */
100    public ResultSetDynaClass(final ResultSet resultSet) throws SQLException {
101
102        this(resultSet, true);
103
104    }
105
106    /**
107     * <p>Construct a new ResultSetDynaClass for the specified
108     * <code>ResultSet</code>.  The property names corresponding
109     * to the column names in the result set will be lower cased or not,
110     * depending on the specified <code>lowerCase</code> value.</p>
111     *
112     * <p><strong>WARNING</strong> - If you specify <code>false</code>
113     * for <code>lowerCase</code>, the returned property names will
114     * exactly match the column names returned by your JDBC driver.
115     * Because different drivers might return column names in different
116     * cases, the property names seen by your application will vary
117     * depending on which JDBC driver you are using.</p>
118     *
119     * @param resultSet The result set to be wrapped
120     * @param lowerCase Should property names be lower cased?
121     * @throws NullPointerException if <code>resultSet</code>
122     *  is <code>null</code>
123     * @throws SQLException if the metadata for this result set
124     *  cannot be introspected
125     */
126    public ResultSetDynaClass(final ResultSet resultSet, final boolean lowerCase)
127        throws SQLException {
128
129        this(resultSet, lowerCase, false);
130
131    }
132
133    /**
134     * <p>Construct a new ResultSetDynaClass for the specified
135     * <code>ResultSet</code>.  The property names corresponding
136     * to the column names in the result set will be lower cased or not,
137     * depending on the specified <code>lowerCase</code> value.</p>
138     *
139     * <p><strong>WARNING</strong> - If you specify <code>false</code>
140     * for <code>lowerCase</code>, the returned property names will
141     * exactly match the column names returned by your JDBC driver.
142     * Because different drivers might return column names in different
143     * cases, the property names seen by your application will vary
144     * depending on which JDBC driver you are using.</p>
145     *
146     * @param resultSet The result set to be wrapped
147     * @param lowerCase Should property names be lower cased?
148     * @param useColumnLabel true if the column label should be used, otherwise false
149     * @throws NullPointerException if <code>resultSet</code>
150     *  is <code>null</code>
151     * @throws SQLException if the metadata for this result set
152     *  cannot be introspected
153     * @since 1.8.3
154     */
155    public ResultSetDynaClass(final ResultSet resultSet, final boolean lowerCase, final boolean useColumnLabel)
156        throws SQLException {
157        this.resultSet = Objects.requireNonNull(resultSet, "resultSet");
158        this.lowerCase = lowerCase;
159        setUseColumnLabel(useColumnLabel);
160        introspect(resultSet);
161
162    }
163
164    /**
165     * Get a value from the {@link ResultSet} for the specified
166     * property name.
167     *
168     * @param name The property name
169     * @return The value
170     * @throws SQLException if an error occurs
171     * @since 1.8.0
172     */
173    public Object getObjectFromResultSet(final String name) throws SQLException {
174        return getObject(getResultSet(), name);
175    }
176
177    /**
178     * <p>Return the result set we are wrapping.</p>
179     */
180    ResultSet getResultSet() {
181
182        return this.resultSet;
183
184    }
185
186    /**
187     * <p>Return an <code>Iterator</code> of {@link DynaBean} instances for
188     * each row of the wrapped <code>ResultSet</code>, in "forward" order.
189     * Unless the underlying result set supports scrolling, this method
190     * should be called only once.</p>
191     * @return An <code>Iterator</code> of {@link DynaBean} instances
192     */
193    public Iterator<DynaBean> iterator() {
194
195        return new ResultSetIterator(this);
196
197    }
198
199    /**
200     * <p>Loads the class of the given name which by default uses the class loader used
201     * to load this library.
202     * Dervations of this class could implement alternative class loading policies such as
203     * using custom ClassLoader or using the Threads's context class loader etc.
204     * </p>
205     * @param className The name of the class to load
206     * @return The loaded class
207     * @throws SQLException if the class cannot be loaded
208     */
209    @Override
210    protected Class<?> loadClass(final String className) throws SQLException {
211
212        try {
213            return getClass().getClassLoader().loadClass(className);
214        }
215        catch (final Exception e) {
216            throw new SQLException("Cannot load column class '" +
217                                   className + "': " + e);
218        }
219    }
220}