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    }