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