    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.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}