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 * http://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 package org.apache.commons.dbutils;
18
19 import java.beans.IntrospectionException;
20 import java.beans.Introspector;
21 import java.beans.PropertyDescriptor;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.sql.Connection;
25 import java.sql.ParameterMetaData;
26 import java.sql.PreparedStatement;
27 import java.sql.ResultSet;
28 import java.sql.SQLException;
29 import java.sql.Statement;
30 import java.sql.Types;
31 import java.util.Arrays;
32
33 import javax.sql.DataSource;
34
35 /**
36 * The base class for QueryRunner & AsyncQueryRunner. This class is thread safe.
37 *
38 * @since 1.4 (mostly extracted from QueryRunner)
39 */
40 public abstract class AbstractQueryRunner {
41 /**
42 * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried
43 * it yet)?
44 */
45 private volatile boolean pmdKnownBroken = false;
46
47 /**
48 * The DataSource to retrieve connections from.
49 * @deprecated Access to this field should be through {@link #getDataSource()}.
50 */
51 protected final DataSource ds;
52
53 /**
54 * Default constructor, sets pmdKnownBroken to false and ds to null.
55 */
56 public AbstractQueryRunner() {
57 ds = null;
58 }
59
60 /**
61 * Constructor to control the use of <code>ParameterMetaData</code>.
62 *
63 * @param pmdKnownBroken
64 * Some drivers don't support
65 * {@link ParameterMetaData#getParameterType(int) }; if
66 * <code>pmdKnownBroken</code> is set to true, we won't even try
67 * it; if false, we'll try it, and if it breaks, we'll remember
68 * not to use it again.
69 */
70 public AbstractQueryRunner(boolean pmdKnownBroken) {
71 this.pmdKnownBroken = pmdKnownBroken;
72 ds = null;
73 }
74
75 /**
76 * Constructor to provide a <code>DataSource</code>. Methods that do not
77 * take a <code>Connection</code> parameter will retrieve connections from
78 * this <code>DataSource</code>.
79 *
80 * @param ds
81 * The <code>DataSource</code> to retrieve connections from.
82 */
83 public AbstractQueryRunner(DataSource ds) {
84 this.ds = ds;
85 }
86
87 /**
88 * Constructor to provide a <code>DataSource</code> and control the use of
89 * <code>ParameterMetaData</code>. Methods that do not take a
90 * <code>Connection</code> parameter will retrieve connections from this
91 * <code>DataSource</code>.
92 *
93 * @param ds
94 * The <code>DataSource</code> to retrieve connections from.
95 * @param pmdKnownBroken
96 * Some drivers don't support
97 * {@link ParameterMetaData#getParameterType(int) }; if
98 * <code>pmdKnownBroken</code> is set to true, we won't even try
99 * it; if false, we'll try it, and if it breaks, we'll remember
100 * not to use it again.
101 */
102 public AbstractQueryRunner(DataSource ds, boolean pmdKnownBroken) {
103 this.pmdKnownBroken = pmdKnownBroken;
104 this.ds = ds;
105 }
106
107 /**
108 * Returns the <code>DataSource</code> this runner is using.
109 * <code>QueryRunner</code> methods always call this method to get the
110 * <code>DataSource</code> so subclasses can provide specialized behavior.
111 *
112 * @return DataSource the runner is using
113 */
114 public DataSource getDataSource() {
115 return this.ds;
116 }
117
118 /**
119 * Some drivers don't support
120 * {@link ParameterMetaData#getParameterType(int) }; if
121 * <code>pmdKnownBroken</code> is set to true, we won't even try it; if
122 * false, we'll try it, and if it breaks, we'll remember not to use it
123 * again.
124 *
125 * @return the flag to skip (or not)
126 * {@link ParameterMetaData#getParameterType(int) }
127 * @since 1.4
128 */
129 public boolean isPmdKnownBroken() {
130 return pmdKnownBroken;
131 }
132
133 /**
134 * Factory method that creates and initializes a
135 * <code>PreparedStatement</code> object for the given SQL.
136 * <code>QueryRunner</code> methods always call this method to prepare
137 * statements for them. Subclasses can override this method to provide
138 * special PreparedStatement configuration if needed. This implementation
139 * simply calls <code>conn.prepareStatement(sql)</code>.
140 *
141 * @param conn
142 * The <code>Connection</code> used to create the
143 * <code>PreparedStatement</code>
144 * @param sql
145 * The SQL statement to prepare.
146 * @return An initialized <code>PreparedStatement</code>.
147 * @throws SQLException
148 * if a database access error occurs
149 */
150 protected PreparedStatement prepareStatement(Connection conn, String sql)
151 throws SQLException {
152
153 return conn.prepareStatement(sql);
154 }
155
156 /**
157 * Factory method that creates and initializes a <code>Connection</code>
158 * object. <code>QueryRunner</code> methods always call this method to
159 * retrieve connections from its DataSource. Subclasses can override this
160 * method to provide special <code>Connection</code> configuration if
161 * needed. This implementation simply calls <code>ds.getConnection()</code>.
162 *
163 * @return An initialized <code>Connection</code>.
164 * @throws SQLException
165 * if a database access error occurs
166 * @since DbUtils 1.1
167 */
168 protected Connection prepareConnection() throws SQLException {
169 if (this.getDataSource() == null) {
170 throw new SQLException(
171 "QueryRunner requires a DataSource to be "
172 + "invoked in this way, or a Connection should be passed in");
173 }
174 return this.getDataSource().getConnection();
175 }
176
177 /**
178 * Fill the <code>PreparedStatement</code> replacement parameters with the
179 * given objects.
180 *
181 * @param stmt
182 * PreparedStatement to fill
183 * @param params
184 * Query replacement parameters; <code>null</code> is a valid
185 * value to pass in.
186 * @throws SQLException
187 * if a database access error occurs
188 */
189 public void fillStatement(PreparedStatement stmt, Object... params)
190 throws SQLException {
191
192 // check the parameter count, if we can
193 ParameterMetaData pmd = null;
194 if (!pmdKnownBroken) {
195 pmd = stmt.getParameterMetaData();
196 int stmtCount = pmd.getParameterCount();
197 int paramsCount = params == null ? 0 : params.length;
198
199 if (stmtCount != paramsCount) {
200 throw new SQLException("Wrong number of parameters: expected "
201 + stmtCount + ", was given " + paramsCount);
202 }
203 }
204
205 // nothing to do here
206 if (params == null) {
207 return;
208 }
209
210 for (int i = 0; i < params.length; i++) {
211 if (params[i] != null) {
212 stmt.setObject(i + 1, params[i]);
213 } else {
214 // VARCHAR works with many drivers regardless
215 // of the actual column type. Oddly, NULL and
216 // OTHER don't work with Oracle's drivers.
217 int sqlType = Types.VARCHAR;
218 if (!pmdKnownBroken) {
219 try {
220 /*
221 * It's not possible for pmdKnownBroken to change from
222 * true to false, (once true, always true) so pmd cannot
223 * be null here.
224 */
225 sqlType = pmd.getParameterType(i + 1);
226 } catch (SQLException e) {
227 pmdKnownBroken = true;
228 }
229 }
230 stmt.setNull(i + 1, sqlType);
231 }
232 }
233 }
234
235 /**
236 * Fill the <code>PreparedStatement</code> replacement parameters with the
237 * given object's bean property values.
238 *
239 * @param stmt
240 * PreparedStatement to fill
241 * @param bean
242 * a JavaBean object
243 * @param properties
244 * an ordered array of properties; this gives the order to insert
245 * values in the statement
246 * @throws SQLException
247 * if a database access error occurs
248 */
249 public void fillStatementWithBean(PreparedStatement stmt, Object bean,
250 PropertyDescriptor[] properties) throws SQLException {
251 Object[] params = new Object[properties.length];
252 for (int i = 0; i < properties.length; i++) {
253 PropertyDescriptor property = properties[i];
254 Object value = null;
255 Method method = property.getReadMethod();
256 if (method == null) {
257 throw new RuntimeException("No read method for bean property "
258 + bean.getClass() + " " + property.getName());
259 }
260 try {
261 value = method.invoke(bean, new Object[0]);
262 } catch (InvocationTargetException e) {
263 throw new RuntimeException("Couldn't invoke method: " + method,
264 e);
265 } catch (IllegalArgumentException e) {
266 throw new RuntimeException(
267 "Couldn't invoke method with 0 arguments: " + method, e);
268 } catch (IllegalAccessException e) {
269 throw new RuntimeException("Couldn't invoke method: " + method,
270 e);
271 }
272 params[i] = value;
273 }
274 fillStatement(stmt, params);
275 }
276
277 /**
278 * Fill the <code>PreparedStatement</code> replacement parameters with the
279 * given object's bean property values.
280 *
281 * @param stmt
282 * PreparedStatement to fill
283 * @param bean
284 * A JavaBean object
285 * @param propertyNames
286 * An ordered array of property names (these should match the
287 * getters/setters); this gives the order to insert values in the
288 * statement
289 * @throws SQLException
290 * If a database access error occurs
291 */
292 public void fillStatementWithBean(PreparedStatement stmt, Object bean,
293 String... propertyNames) throws SQLException {
294 PropertyDescriptor[] descriptors;
295 try {
296 descriptors = Introspector.getBeanInfo(bean.getClass())
297 .getPropertyDescriptors();
298 } catch (IntrospectionException e) {
299 throw new RuntimeException("Couldn't introspect bean "
300 + bean.getClass().toString(), e);
301 }
302 PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
303 for (int i = 0; i < propertyNames.length; i++) {
304 String propertyName = propertyNames[i];
305 if (propertyName == null) {
306 throw new NullPointerException("propertyName can't be null: "
307 + i);
308 }
309 boolean found = false;
310 for (int j = 0; j < descriptors.length; j++) {
311 PropertyDescriptor descriptor = descriptors[j];
312 if (propertyName.equals(descriptor.getName())) {
313 sorted[i] = descriptor;
314 found = true;
315 break;
316 }
317 }
318 if (!found) {
319 throw new RuntimeException("Couldn't find bean property: "
320 + bean.getClass() + " " + propertyName);
321 }
322 }
323 fillStatementWithBean(stmt, bean, sorted);
324 }
325
326 /**
327 * Throws a new exception with a more informative error message.
328 *
329 * @param cause
330 * The original exception that will be chained to the new
331 * exception when it's rethrown.
332 *
333 * @param sql
334 * The query that was executing when the exception happened.
335 *
336 * @param params
337 * The query replacement parameters; <code>null</code> is a valid
338 * value to pass in.
339 *
340 * @throws SQLException
341 * if a database access error occurs
342 */
343 protected void rethrow(SQLException cause, String sql, Object... params)
344 throws SQLException {
345
346 String causeMessage = cause.getMessage();
347 if (causeMessage == null) {
348 causeMessage = "";
349 }
350 StringBuffer msg = new StringBuffer(causeMessage);
351
352 msg.append(" Query: ");
353 msg.append(sql);
354 msg.append(" Parameters: ");
355
356 if (params == null) {
357 msg.append("[]");
358 } else {
359 msg.append(Arrays.deepToString(params));
360 }
361
362 SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
363 cause.getErrorCode());
364 e.setNextException(cause);
365
366 throw e;
367 }
368
369 /**
370 * Wrap the <code>ResultSet</code> in a decorator before processing it. This
371 * implementation returns the <code>ResultSet</code> it is given without any
372 * decoration.
373 *
374 * <p>
375 * Often, the implementation of this method can be done in an anonymous
376 * inner class like this:
377 * </p>
378 *
379 * <pre>
380 * QueryRunner run = new QueryRunner() {
381 * protected ResultSet wrap(ResultSet rs) {
382 * return StringTrimmedResultSet.wrap(rs);
383 * }
384 * };
385 * </pre>
386 *
387 * @param rs
388 * The <code>ResultSet</code> to decorate; never
389 * <code>null</code>.
390 * @return The <code>ResultSet</code> wrapped in some decorator.
391 */
392 protected ResultSet wrap(ResultSet rs) {
393 return rs;
394 }
395
396 /**
397 * Close a <code>Connection</code>. This implementation avoids closing if
398 * null and does <strong>not</strong> suppress any exceptions. Subclasses
399 * can override to provide special handling like logging.
400 *
401 * @param conn
402 * Connection to close
403 * @throws SQLException
404 * if a database access error occurs
405 * @since DbUtils 1.1
406 */
407 protected void close(Connection conn) throws SQLException {
408 DbUtils.close(conn);
409 }
410
411 /**
412 * Close a <code>Statement</code>. This implementation avoids closing if
413 * null and does <strong>not</strong> suppress any exceptions. Subclasses
414 * can override to provide special handling like logging.
415 *
416 * @param stmt
417 * Statement to close
418 * @throws SQLException
419 * if a database access error occurs
420 * @since DbUtils 1.1
421 */
422 protected void close(Statement stmt) throws SQLException {
423 DbUtils.close(stmt);
424 }
425
426 /**
427 * Close a <code>ResultSet</code>. This implementation avoids closing if
428 * null and does <strong>not</strong> suppress any exceptions. Subclasses
429 * can override to provide special handling like logging.
430 *
431 * @param rs
432 * ResultSet to close
433 * @throws SQLException
434 * if a database access error occurs
435 * @since DbUtils 1.1
436 */
437 protected void close(ResultSet rs) throws SQLException {
438 DbUtils.close(rs);
439 }
440
441 }