1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.beanutils2.sql;
19
20 import java.sql.ResultSet;
21 import java.sql.SQLException;
22 import java.util.Iterator;
23 import java.util.Objects;
24
25 import org.apache.commons.beanutils2.DynaBean;
26 import org.apache.commons.beanutils2.DynaClass;
27
28 /**
29 * <p>
30 * 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
31 * something like:
32 * </p>
33 *
34 * <pre>
35 * ResultSet rs = ...;
36 * ResultSetDynaClass rsdc = new ResultSetDynaClass(rs);
37 * Iterator rows = rsdc.iterator();
38 * while (rows.hasNext()) {
39 * DynaBean row = (DynaBean) rows.next();
40 * ... process this row ...
41 * }
42 * rs.close();
43 * </pre>
44 *
45 * <p>
46 * Each column in the result set will be represented as a DynaBean property of the corresponding name (optionally forced to lower case for portability).
47 * </p>
48 *
49 * <p>
50 * <strong>WARNING</strong> - Any {@link DynaBean} instance returned by this class, or from the {@code Iterator} returned by the {@code iterator()} method, is
51 * directly linked to the row that the underlying result set is currently positioned at. This has the following implications:
52 * </p>
53 * <ul>
54 * <li>Once you retrieve a different {@link DynaBean} instance, you should no longer use any previous instance.</li>
55 * <li>Changing the position of the underlying result set will change the data that the {@link DynaBean} references.</li>
56 * <li>Once the underlying result set is closed, the {@link DynaBean} instance may no longer be used.</li>
57 * </ul>
58 *
59 * <p>
60 * 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
61 * following code to create standalone copies of the information in a result set:
62 * </p>
63 *
64 * <pre>
65 * List results = new ArrayList(); // To hold copied list
66 * ResultSetDynaClass rsdc = ...;
67 * DynaProperty[] properties = rsdc.getDynaProperties();
68 * BasicDynaClass bdc =
69 * new BasicDynaClass("foo", BasicDynaBean.class,
70 * rsdc.getDynaProperties());
71 * Iterator rows = rsdc.iterator();
72 * while (rows.hasNext()) {
73 * DynaBean oldRow = (DynaBean) rows.next();
74 * DynaBean newRow = bdc.newInstance();
75 * PropertyUtils.copyProperties(newRow, oldRow);
76 * results.add(newRow);
77 * }
78 * </pre>
79 */
80 public class ResultSetDynaClass extends AbstractJdbcDynaClass {
81
82 private static final long serialVersionUID = 1L;
83
84 /**
85 * <p>
86 * The {@code ResultSet} we are wrapping.
87 * </p>
88 */
89 protected ResultSet resultSet;
90
91 /**
92 * <p>
93 * Constructs a new ResultSetDynaClass for the specified {@code ResultSet}. The property names corresponding to column names in the result set will be lower
94 * cased.
95 * </p>
96 *
97 * @param resultSet The result set to be wrapped
98 * @throws NullPointerException if {@code resultSet} is {@code null}
99 * @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 }