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 package org.apache.commons.dbutils;
018
019 import java.beans.IntrospectionException;
020 import java.beans.Introspector;
021 import java.beans.PropertyDescriptor;
022 import java.lang.reflect.InvocationTargetException;
023 import java.lang.reflect.Method;
024 import java.sql.Connection;
025 import java.sql.ParameterMetaData;
026 import java.sql.PreparedStatement;
027 import java.sql.ResultSet;
028 import java.sql.SQLException;
029 import java.sql.Statement;
030 import java.sql.Types;
031 import java.util.Arrays;
032
033 import javax.sql.DataSource;
034
035 /**
036 * The base class for QueryRunner & AsyncQueryRunner.
037 * This class is thread safe.
038 * @since 1.4 (mostly extracted from QueryRunner)
039 */
040 public abstract class AbstractQueryRunner {
041 /**
042 * Is {@link ParameterMetaData#getParameterType(int)} broken (have we tried it yet)?
043 */
044 private volatile boolean pmdKnownBroken = false;
045
046 /**
047 * The DataSource to retrieve connections from.
048 */
049 protected final DataSource ds;
050
051 /**
052 * Default constructor, sets pmdKnownBroken to false and ds to null.
053 */
054 public AbstractQueryRunner() {
055 ds = null;
056 }
057
058 /**
059 * Constructor to allow workaround for Oracle drivers
060 * @param pmdKnownBroken Oracle drivers don't support {@link ParameterMetaData#getParameterType(int) };
061 * if <code>pmdKnownBroken</code> is set to true, we won't even try it; if false, we'll try it,
062 * and if it breaks, we'll remember not to use it again.
063 */
064 public AbstractQueryRunner(boolean pmdKnownBroken) {
065 this.pmdKnownBroken = pmdKnownBroken;
066 ds = null;
067 }
068
069 /**
070 * Constructor to provide a <code>DataSource</code>.
071 * Methods that do not take a <code>Connection</code> parameter will
072 * retrieve connections from this <code>DataSource</code>.
073 *
074 * @param ds The <code>DataSource</code> to retrieve connections from.
075 */
076 public AbstractQueryRunner(DataSource ds) {
077 this.ds = ds;
078 }
079
080 /**
081 * Constructor to allow workaround for Oracle drivers. Methods that do not take a
082 * <code>Connection</code> parameter will retrieve connections from this
083 * <code>DataSource</code>.
084 *
085 * @param ds The <code>DataSource</code> to retrieve connections from.
086 * @param pmdKnownBroken Oracle drivers don't support {@link ParameterMetaData#getParameterType(int) };
087 * if <code>pmdKnownBroken</code> is set to true, we won't even try it; if false, we'll try it,
088 * and if it breaks, we'll remember not to use it again.
089 */
090 public AbstractQueryRunner(DataSource ds, boolean pmdKnownBroken) {
091 this.pmdKnownBroken = pmdKnownBroken;
092 this.ds = ds;
093 }
094
095 /**
096 * Returns the <code>DataSource</code> this runner is using.
097 * <code>QueryRunner</code> methods always call this method to get the
098 * <code>DataSource</code> so subclasses can provide specialized
099 * behavior.
100 *
101 * @return DataSource the runner is using
102 */
103 public DataSource getDataSource() {
104 return this.ds;
105 }
106
107 /**
108 * Oracle drivers don't support {@link ParameterMetaData#getParameterType(int) };
109 * if <code>pmdKnownBroken</code> is set to true, we won't even try it; if false, we'll try it,
110 * and if it breaks, we'll remember not to use it again.
111 *
112 * @return the flag to skip (or not) {@link ParameterMetaData#getParameterType(int) }
113 * @since 1.4
114 */
115 public boolean isPmdKnownBroken() {
116 return pmdKnownBroken;
117 }
118
119 /**
120 * Factory method that creates and initializes a
121 * <code>PreparedStatement</code> object for the given SQL.
122 * <code>QueryRunner</code> methods always call this method to prepare
123 * statements for them. Subclasses can override this method to provide
124 * special PreparedStatement configuration if needed. This implementation
125 * simply calls <code>conn.prepareStatement(sql)</code>.
126 *
127 * @param conn The <code>Connection</code> used to create the
128 * <code>PreparedStatement</code>
129 * @param sql The SQL statement to prepare.
130 * @return An initialized <code>PreparedStatement</code>.
131 * @throws SQLException if a database access error occurs
132 */
133 protected PreparedStatement prepareStatement(Connection conn, String sql)
134 throws SQLException {
135
136 return conn.prepareStatement(sql);
137 }
138
139 /**
140 * Factory method that creates and initializes a
141 * <code>Connection</code> object. <code>QueryRunner</code> methods
142 * always call this method to retrieve connections from its DataSource.
143 * Subclasses can override this method to provide
144 * special <code>Connection</code> configuration if needed. This
145 * implementation simply calls <code>ds.getConnection()</code>.
146 *
147 * @return An initialized <code>Connection</code>.
148 * @throws SQLException if a database access error occurs
149 * @since DbUtils 1.1
150 */
151 protected Connection prepareConnection() throws SQLException {
152 if (this.getDataSource() == null) {
153 throw new SQLException("QueryRunner requires a DataSource to be " +
154 "invoked in this way, or a Connection should be passed in");
155 }
156 return this.getDataSource().getConnection();
157 }
158
159 /**
160 * Fill the <code>PreparedStatement</code> replacement parameters with
161 * the given objects.
162 * @param stmt PreparedStatement to fill
163 * @param params Query replacement parameters; <code>null</code> is a valid
164 * value to pass in.
165 * @throws SQLException if a database access error occurs
166 */
167 public void fillStatement(PreparedStatement stmt, Object... params) throws SQLException {
168
169 // check the parameter count, if we can
170 ParameterMetaData pmd = null;
171 if (!pmdKnownBroken) {
172 pmd = stmt.getParameterMetaData();
173 int stmtCount = pmd.getParameterCount();
174 int paramsCount = params == null ? 0 : params.length;
175
176 if (stmtCount != paramsCount) {
177 throw new SQLException("Wrong number of parameters: expected "
178 + stmtCount + ", was given " + paramsCount);
179 }
180 }
181
182 // nothing to do here
183 if (params == null) {
184 return;
185 }
186
187 for (int i = 0; i < params.length; i++) {
188 if (params[i] != null) {
189 stmt.setObject(i + 1, params[i]);
190 } else {
191 // VARCHAR works with many drivers regardless
192 // of the actual column type. Oddly, NULL and
193 // OTHER don't work with Oracle's drivers.
194 int sqlType = Types.VARCHAR;
195 if (!pmdKnownBroken) {
196 try {
197 sqlType = pmd.getParameterType(i + 1);
198 } catch (SQLException e) {
199 pmdKnownBroken = true;
200 }
201 }
202 stmt.setNull(i + 1, sqlType);
203 }
204 }
205 }
206
207 /**
208 * Fill the <code>PreparedStatement</code> replacement parameters with the
209 * given object's bean property values.
210 *
211 * @param stmt
212 * PreparedStatement to fill
213 * @param bean
214 * a JavaBean object
215 * @param properties
216 * an ordered array of properties; this gives the order to insert
217 * values in the statement
218 * @throws SQLException
219 * if a database access error occurs
220 */
221 public void fillStatementWithBean(PreparedStatement stmt, Object bean,
222 PropertyDescriptor[] properties) throws SQLException {
223 Object[] params = new Object[properties.length];
224 for (int i = 0; i < properties.length; i++) {
225 PropertyDescriptor property = properties[i];
226 Object value = null;
227 Method method = property.getReadMethod();
228 if (method == null) {
229 throw new RuntimeException("No read method for bean property "
230 + bean.getClass() + " " + property.getName());
231 }
232 try {
233 value = method.invoke(bean, new Object[0]);
234 } catch (InvocationTargetException e) {
235 throw new RuntimeException("Couldn't invoke method: " + method, e);
236 } catch (IllegalArgumentException e) {
237 throw new RuntimeException("Couldn't invoke method with 0 arguments: " + method, e);
238 } catch (IllegalAccessException e) {
239 throw new RuntimeException("Couldn't invoke method: " + method, e);
240 }
241 params[i] = value;
242 }
243 fillStatement(stmt, params);
244 }
245
246 /**
247 * Fill the <code>PreparedStatement</code> replacement parameters with the
248 * given object's bean property values.
249 *
250 * @param stmt PreparedStatement to fill
251 * @param bean A JavaBean object
252 * @param propertyNames An ordered array of property names (these should match the
253 * getters/setters); this gives the order to insert values in the
254 * statement
255 * @throws SQLException If a database access error occurs
256 */
257 public void fillStatementWithBean(PreparedStatement stmt, Object bean, String... propertyNames) throws SQLException {
258 PropertyDescriptor[] descriptors;
259 try {
260 descriptors = Introspector.getBeanInfo(bean.getClass())
261 .getPropertyDescriptors();
262 } catch (IntrospectionException e) {
263 throw new RuntimeException("Couldn't introspect bean " + bean.getClass().toString(), e);
264 }
265 PropertyDescriptor[] sorted = new PropertyDescriptor[propertyNames.length];
266 for (int i = 0; i < propertyNames.length; i++) {
267 String propertyName = propertyNames[i];
268 if (propertyName == null) {
269 throw new NullPointerException("propertyName can't be null: " + i);
270 }
271 boolean found = false;
272 for (int j = 0; j < descriptors.length; j++) {
273 PropertyDescriptor descriptor = descriptors[j];
274 if (propertyName.equals(descriptor.getName())) {
275 sorted[i] = descriptor;
276 found = true;
277 break;
278 }
279 }
280 if (!found) {
281 throw new RuntimeException("Couldn't find bean property: "
282 + bean.getClass() + " " + propertyName);
283 }
284 }
285 fillStatementWithBean(stmt, bean, sorted);
286 }
287
288 /**
289 * Throws a new exception with a more informative error message.
290 *
291 * @param cause The original exception that will be chained to the new
292 * exception when it's rethrown.
293 *
294 * @param sql The query that was executing when the exception happened.
295 *
296 * @param params The query replacement parameters; <code>null</code> is a
297 * valid value to pass in.
298 *
299 * @throws SQLException if a database access error occurs
300 */
301 protected void rethrow(SQLException cause, String sql, Object... params)
302 throws SQLException {
303
304 String causeMessage = cause.getMessage();
305 if (causeMessage == null) {
306 causeMessage = "";
307 }
308 StringBuffer msg = new StringBuffer(causeMessage);
309
310 msg.append(" Query: ");
311 msg.append(sql);
312 msg.append(" Parameters: ");
313
314 if (params == null) {
315 msg.append("[]");
316 } else {
317 msg.append(Arrays.deepToString(params));
318 }
319
320 SQLException e = new SQLException(msg.toString(), cause.getSQLState(),
321 cause.getErrorCode());
322 e.setNextException(cause);
323
324 throw e;
325 }
326
327 /**
328 * Wrap the <code>ResultSet</code> in a decorator before processing it.
329 * This implementation returns the <code>ResultSet</code> it is given
330 * without any decoration.
331 *
332 * <p>
333 * Often, the implementation of this method can be done in an anonymous
334 * inner class like this:
335 * </p>
336 * <pre>
337 * QueryRunner run = new QueryRunner() {
338 * protected ResultSet wrap(ResultSet rs) {
339 * return StringTrimmedResultSet.wrap(rs);
340 * }
341 * };
342 * </pre>
343 *
344 * @param rs The <code>ResultSet</code> to decorate; never
345 * <code>null</code>.
346 * @return The <code>ResultSet</code> wrapped in some decorator.
347 */
348 protected ResultSet wrap(ResultSet rs) {
349 return rs;
350 }
351
352 /**
353 * Close a <code>Connection</code>. This implementation avoids closing if
354 * null and does <strong>not</strong> suppress any exceptions. Subclasses
355 * can override to provide special handling like logging.
356 * @param conn Connection to close
357 * @throws SQLException if a database access error occurs
358 * @since DbUtils 1.1
359 */
360 protected void close(Connection conn) throws SQLException {
361 DbUtils.close(conn);
362 }
363
364 /**
365 * Close a <code>Statement</code>. This implementation avoids closing if
366 * null and does <strong>not</strong> suppress any exceptions. Subclasses
367 * can override to provide special handling like logging.
368 * @param stmt Statement to close
369 * @throws SQLException if a database access error occurs
370 * @since DbUtils 1.1
371 */
372 protected void close(Statement stmt) throws SQLException {
373 DbUtils.close(stmt);
374 }
375
376 /**
377 * Close a <code>ResultSet</code>. This implementation avoids closing if
378 * null and does <strong>not</strong> suppress any exceptions. Subclasses
379 * can override to provide special handling like logging.
380 * @param rs ResultSet to close
381 * @throws SQLException if a database access error occurs
382 * @since DbUtils 1.1
383 */
384 protected void close(ResultSet rs) throws SQLException {
385 DbUtils.close(rs);
386 }
387
388 }