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