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.BeanInfo;
020    import java.beans.IntrospectionException;
021    import java.beans.Introspector;
022    import java.beans.PropertyDescriptor;
023    import java.lang.reflect.InvocationTargetException;
024    import java.lang.reflect.Method;
025    import java.sql.ResultSet;
026    import java.sql.ResultSetMetaData;
027    import java.sql.SQLException;
028    import java.sql.Timestamp;
029    import java.util.ArrayList;
030    import java.util.Arrays;
031    import java.util.HashMap;
032    import java.util.List;
033    import java.util.Map;
034    
035    /**
036     * <p>
037     * <code>BeanProcessor</code> matches column names to bean property names
038     * and converts <code>ResultSet</code> columns into objects for those bean
039     * properties.  Subclasses should override the methods in the processing chain
040     * to customize behavior.
041     * </p>
042     *
043     * <p>
044     * This class is thread-safe.
045     * </p>
046     *
047     * @see BasicRowProcessor
048     *
049     * @since DbUtils 1.1
050     */
051    public class BeanProcessor {
052    
053        /**
054         * Special array value used by <code>mapColumnsToProperties</code> that
055         * indicates there is no bean property that matches a column from a
056         * <code>ResultSet</code>.
057         */
058        protected static final int PROPERTY_NOT_FOUND = -1;
059    
060        /**
061         * Set a bean's primitive properties to these defaults when SQL NULL
062         * is returned.  These are the same as the defaults that ResultSet get*
063         * methods return in the event of a NULL column.
064         */
065        private static final Map<Class<?>, Object> primitiveDefaults = new HashMap<Class<?>, Object>();
066    
067        static {
068            primitiveDefaults.put(Integer.TYPE, Integer.valueOf(0));
069            primitiveDefaults.put(Short.TYPE, Short.valueOf((short) 0));
070            primitiveDefaults.put(Byte.TYPE, Byte.valueOf((byte) 0));
071            primitiveDefaults.put(Float.TYPE, Float.valueOf(0f));
072            primitiveDefaults.put(Double.TYPE, Double.valueOf(0d));
073            primitiveDefaults.put(Long.TYPE, Long.valueOf(0L));
074            primitiveDefaults.put(Boolean.TYPE, Boolean.FALSE);
075            primitiveDefaults.put(Character.TYPE, Character.valueOf((char) 0));
076        }
077    
078        /**
079         * Constructor for BeanProcessor.
080         */
081        public BeanProcessor() {
082            super();
083        }
084    
085        /**
086         * Convert a <code>ResultSet</code> row into a JavaBean.  This
087         * implementation uses reflection and <code>BeanInfo</code> classes to
088         * match column names to bean property names.  Properties are matched to
089         * columns based on several factors:
090         * <br/>
091         * <ol>
092         *     <li>
093         *     The class has a writable property with the same name as a column.
094         *     The name comparison is case insensitive.
095         *     </li>
096         *
097         *     <li>
098         *     The column type can be converted to the property's set method
099         *     parameter type with a ResultSet.get* method.  If the conversion fails
100         *     (ie. the property was an int and the column was a Timestamp) an
101         *     SQLException is thrown.
102         *     </li>
103         * </ol>
104         *
105         * <p>
106         * Primitive bean properties are set to their defaults when SQL NULL is
107         * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
108         * and booleans are set to false.  Object bean properties are set to
109         * <code>null</code> when SQL NULL is returned.  This is the same behavior
110         * as the <code>ResultSet</code> get* methods.
111         * </p>
112         * @param <T> The type of bean to create
113         * @param rs ResultSet that supplies the bean data
114         * @param type Class from which to create the bean instance
115         * @throws SQLException if a database access error occurs
116         * @return the newly created bean
117         */
118        public <T> T toBean(ResultSet rs, Class<T> type) throws SQLException {
119    
120            PropertyDescriptor[] props = this.propertyDescriptors(type);
121    
122            ResultSetMetaData rsmd = rs.getMetaData();
123            int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
124    
125            return this.createBean(rs, type, props, columnToProperty);
126        }
127    
128        /**
129         * Convert a <code>ResultSet</code> into a <code>List</code> of JavaBeans.
130         * This implementation uses reflection and <code>BeanInfo</code> classes to
131         * match column names to bean property names. Properties are matched to
132         * columns based on several factors:
133         * <br/>
134         * <ol>
135         *     <li>
136         *     The class has a writable property with the same name as a column.
137         *     The name comparison is case insensitive.
138         *     </li>
139         *
140         *     <li>
141         *     The column type can be converted to the property's set method
142         *     parameter type with a ResultSet.get* method.  If the conversion fails
143         *     (ie. the property was an int and the column was a Timestamp) an
144         *     SQLException is thrown.
145         *     </li>
146         * </ol>
147         *
148         * <p>
149         * Primitive bean properties are set to their defaults when SQL NULL is
150         * returned from the <code>ResultSet</code>.  Numeric fields are set to 0
151         * and booleans are set to false.  Object bean properties are set to
152         * <code>null</code> when SQL NULL is returned.  This is the same behavior
153         * as the <code>ResultSet</code> get* methods.
154         * </p>
155         * @param <T> The type of bean to create
156         * @param rs ResultSet that supplies the bean data
157         * @param type Class from which to create the bean instance
158         * @throws SQLException if a database access error occurs
159         * @return the newly created List of beans
160         */
161        public <T> List<T> toBeanList(ResultSet rs, Class<T> type) throws SQLException {
162            List<T> results = new ArrayList<T>();
163    
164            if (!rs.next()) {
165                return results;
166            }
167    
168            PropertyDescriptor[] props = this.propertyDescriptors(type);
169            ResultSetMetaData rsmd = rs.getMetaData();
170            int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
171    
172            do {
173                results.add(this.createBean(rs, type, props, columnToProperty));
174            } while (rs.next());
175    
176            return results;
177        }
178    
179        /**
180         * Creates a new object and initializes its fields from the ResultSet.
181         * @param <T> The type of bean to create
182         * @param rs The result set.
183         * @param type The bean type (the return type of the object).
184         * @param props The property descriptors.
185         * @param columnToProperty The column indices in the result set.
186         * @return An initialized object.
187         * @throws SQLException if a database error occurs.
188         */
189        private <T> T createBean(ResultSet rs, Class<T> type,
190                PropertyDescriptor[] props, int[] columnToProperty)
191                throws SQLException {
192    
193            T bean = this.newInstance(type);
194    
195            for (int i = 1; i < columnToProperty.length; i++) {
196    
197                if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
198                    continue;
199                }
200    
201                PropertyDescriptor prop = props[columnToProperty[i]];
202                Class<?> propType = prop.getPropertyType();
203    
204                Object value = this.processColumn(rs, i, propType);
205    
206                if (propType != null && value == null && propType.isPrimitive()) {
207                    value = primitiveDefaults.get(propType);
208                }
209    
210                this.callSetter(bean, prop, value);
211            }
212    
213            return bean;
214        }
215    
216        /**
217         * Calls the setter method on the target object for the given property.
218         * If no setter method exists for the property, this method does nothing.
219         * @param target The object to set the property on.
220         * @param prop The property to set.
221         * @param value The value to pass into the setter.
222         * @throws SQLException if an error occurs setting the property.
223         */
224        private void callSetter(Object target, PropertyDescriptor prop, Object value)
225                throws SQLException {
226    
227            Method setter = prop.getWriteMethod();
228    
229            if (setter == null) {
230                return;
231            }
232    
233            Class<?>[] params = setter.getParameterTypes();
234            try {
235                // convert types for some popular ones
236                if (value != null) {
237                    if (value instanceof java.util.Date) {
238                        if (params[0].getName().equals("java.sql.Date")) {
239                            value = new java.sql.Date(((java.util.Date) value).getTime());
240                        } else
241                        if (params[0].getName().equals("java.sql.Time")) {
242                            value = new java.sql.Time(((java.util.Date) value).getTime());
243                        } else
244                        if (params[0].getName().equals("java.sql.Timestamp")) {
245                            value = new java.sql.Timestamp(((java.util.Date) value).getTime());
246                        }
247                    }
248                }
249    
250                // Don't call setter if the value object isn't the right type
251                if (this.isCompatibleType(value, params[0])) {
252                    setter.invoke(target, new Object[]{value});
253                } else {
254                  throw new SQLException(
255                      "Cannot set " + prop.getName() + ": incompatible types.");
256                }
257    
258            } catch (IllegalArgumentException e) {
259                throw new SQLException(
260                    "Cannot set " + prop.getName() + ": " + e.getMessage());
261    
262            } catch (IllegalAccessException e) {
263                throw new SQLException(
264                    "Cannot set " + prop.getName() + ": " + e.getMessage());
265    
266            } catch (InvocationTargetException e) {
267                throw new SQLException(
268                    "Cannot set " + prop.getName() + ": " + e.getMessage());
269            }
270        }
271    
272        /**
273         * ResultSet.getObject() returns an Integer object for an INT column.  The
274         * setter method for the property might take an Integer or a primitive int.
275         * This method returns true if the value can be successfully passed into
276         * the setter method.  Remember, Method.invoke() handles the unwrapping
277         * of Integer into an int.
278         *
279         * @param value The value to be passed into the setter method.
280         * @param type The setter's parameter type.
281         * @return boolean True if the value is compatible.
282         */
283        private boolean isCompatibleType(Object value, Class<?> type) {
284            // Do object check first, then primitives
285            if (value == null || type.isInstance(value)) {
286                return true;
287    
288            } else if (type.equals(Integer.TYPE) && Integer.class.isInstance(value)) {
289                return true;
290    
291            } else if (type.equals(Long.TYPE) && Long.class.isInstance(value)) {
292                return true;
293    
294            } else if (type.equals(Double.TYPE) && Double.class.isInstance(value)) {
295                return true;
296    
297            } else if (type.equals(Float.TYPE) && Float.class.isInstance(value)) {
298                return true;
299    
300            } else if (type.equals(Short.TYPE) && Short.class.isInstance(value)) {
301                return true;
302    
303            } else if (type.equals(Byte.TYPE) && Byte.class.isInstance(value)) {
304                return true;
305    
306            } else if (type.equals(Character.TYPE) && Character.class.isInstance(value)) {
307                return true;
308    
309            } else if (type.equals(Boolean.TYPE) && Boolean.class.isInstance(value)) {
310                return true;
311    
312            }
313            return false;
314    
315        }
316    
317        /**
318         * Factory method that returns a new instance of the given Class.  This
319         * is called at the start of the bean creation process and may be
320         * overridden to provide custom behavior like returning a cached bean
321         * instance.
322         * @param <T> The type of object to create
323         * @param c The Class to create an object from.
324         * @return A newly created object of the Class.
325         * @throws SQLException if creation failed.
326         */
327        protected <T> T newInstance(Class<T> c) throws SQLException {
328            try {
329                return c.newInstance();
330    
331            } catch (InstantiationException e) {
332                throw new SQLException(
333                    "Cannot create " + c.getName() + ": " + e.getMessage());
334    
335            } catch (IllegalAccessException e) {
336                throw new SQLException(
337                    "Cannot create " + c.getName() + ": " + e.getMessage());
338            }
339        }
340    
341        /**
342         * Returns a PropertyDescriptor[] for the given Class.
343         *
344         * @param c The Class to retrieve PropertyDescriptors for.
345         * @return A PropertyDescriptor[] describing the Class.
346         * @throws SQLException if introspection failed.
347         */
348        private PropertyDescriptor[] propertyDescriptors(Class<?> c)
349            throws SQLException {
350            // Introspector caches BeanInfo classes for better performance
351            BeanInfo beanInfo = null;
352            try {
353                beanInfo = Introspector.getBeanInfo(c);
354    
355            } catch (IntrospectionException e) {
356                throw new SQLException(
357                    "Bean introspection failed: " + e.getMessage());
358            }
359    
360            return beanInfo.getPropertyDescriptors();
361        }
362    
363        /**
364         * The positions in the returned array represent column numbers.  The
365         * values stored at each position represent the index in the
366         * <code>PropertyDescriptor[]</code> for the bean property that matches
367         * the column name.  If no bean property was found for a column, the
368         * position is set to <code>PROPERTY_NOT_FOUND</code>.
369         *
370         * @param rsmd The <code>ResultSetMetaData</code> containing column
371         * information.
372         *
373         * @param props The bean property descriptors.
374         *
375         * @throws SQLException if a database access error occurs
376         *
377         * @return An int[] with column index to property index mappings.  The 0th
378         * element is meaningless because JDBC column indexing starts at 1.
379         */
380        protected int[] mapColumnsToProperties(ResultSetMetaData rsmd,
381                PropertyDescriptor[] props) throws SQLException {
382    
383            int cols = rsmd.getColumnCount();
384            int[] columnToProperty = new int[cols + 1];
385            Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);
386    
387            for (int col = 1; col <= cols; col++) {
388                String columnName = rsmd.getColumnLabel(col);
389                if (null == columnName || 0 == columnName.length()) {
390                  columnName = rsmd.getColumnName(col);
391                }
392                for (int i = 0; i < props.length; i++) {
393    
394                    if (columnName.equalsIgnoreCase(props[i].getName())) {
395                        columnToProperty[col] = i;
396                        break;
397                    }
398                }
399            }
400    
401            return columnToProperty;
402        }
403    
404        /**
405         * Convert a <code>ResultSet</code> column into an object.  Simple
406         * implementations could just call <code>rs.getObject(index)</code> while
407         * more complex implementations could perform type manipulation to match
408         * the column's type to the bean property type.
409         *
410         * <p>
411         * This implementation calls the appropriate <code>ResultSet</code> getter
412         * method for the given property type to perform the type conversion.  If
413         * the property type doesn't match one of the supported
414         * <code>ResultSet</code> types, <code>getObject</code> is called.
415         * </p>
416         *
417         * @param rs The <code>ResultSet</code> currently being processed.  It is
418         * positioned on a valid row before being passed into this method.
419         *
420         * @param index The current column index being processed.
421         *
422         * @param propType The bean property type that this column needs to be
423         * converted into.
424         *
425         * @throws SQLException if a database access error occurs
426         *
427         * @return The object from the <code>ResultSet</code> at the given column
428         * index after optional type processing or <code>null</code> if the column
429         * value was SQL NULL.
430         */
431        protected Object processColumn(ResultSet rs, int index, Class<?> propType)
432            throws SQLException {
433    
434            if ( !propType.isPrimitive() && rs.getObject(index) == null ) {
435                return null;
436            }
437    
438            if (propType.equals(String.class)) {
439                return rs.getString(index);
440    
441            } else if (
442                propType.equals(Integer.TYPE) || propType.equals(Integer.class)) {
443                return Integer.valueOf(rs.getInt(index));
444    
445            } else if (
446                propType.equals(Boolean.TYPE) || propType.equals(Boolean.class)) {
447                return Boolean.valueOf(rs.getBoolean(index));
448    
449            } else if (propType.equals(Long.TYPE) || propType.equals(Long.class)) {
450                return Long.valueOf(rs.getLong(index));
451    
452            } else if (
453                propType.equals(Double.TYPE) || propType.equals(Double.class)) {
454                return Double.valueOf(rs.getDouble(index));
455    
456            } else if (
457                propType.equals(Float.TYPE) || propType.equals(Float.class)) {
458                return Float.valueOf(rs.getFloat(index));
459    
460            } else if (
461                propType.equals(Short.TYPE) || propType.equals(Short.class)) {
462                return Short.valueOf(rs.getShort(index));
463    
464            } else if (propType.equals(Byte.TYPE) || propType.equals(Byte.class)) {
465                return Byte.valueOf(rs.getByte(index));
466    
467            } else if (propType.equals(Timestamp.class)) {
468                return rs.getTimestamp(index);
469    
470            } else {
471                return rs.getObject(index);
472            }
473    
474        }
475    
476    }