BeanProcessor.java

  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. import java.beans.BeanInfo;
  19. import java.beans.IntrospectionException;
  20. import java.beans.Introspector;
  21. import java.beans.PropertyDescriptor;
  22. import java.lang.reflect.Field;
  23. import java.lang.reflect.InvocationTargetException;
  24. import java.lang.reflect.Method;
  25. import java.sql.ResultSet;
  26. import java.sql.ResultSetMetaData;
  27. import java.sql.SQLException;
  28. import java.util.ArrayList;
  29. import java.util.Arrays;
  30. import java.util.HashMap;
  31. import java.util.List;
  32. import java.util.Map;
  33. import java.util.ServiceLoader;

  34. /**
  35.  * <p>
  36.  * {@code BeanProcessor} matches column names to bean property names
  37.  * and converts {@code ResultSet} columns into objects for those bean
  38.  * properties.  Subclasses should override the methods in the processing chain
  39.  * to customize behavior.
  40.  * </p>
  41.  *
  42.  * <p>
  43.  * This class is thread-safe.
  44.  * </p>
  45.  *
  46.  * @see BasicRowProcessor
  47.  *
  48.  * @since 1.1
  49.  */
  50. public class BeanProcessor {

  51.     /**
  52.      * Special array value used by {@code mapColumnsToProperties} that
  53.      * indicates there is no bean property that matches a column from a
  54.      * {@code ResultSet}.
  55.      */
  56.     protected static final int PROPERTY_NOT_FOUND = -1;

  57.     /**
  58.      * Set a bean's primitive properties to these defaults when SQL NULL
  59.      * is returned.  These are the same as the defaults that ResultSet get*
  60.      * methods return in the event of a NULL column.
  61.      */
  62.     private static final Map<Class<?>, Object> PRIMITIVE_DEFAULTS = new HashMap<>();

  63.     private static final List<ColumnHandler<?>> COLUMN_HANDLERS = new ArrayList<>();

  64.     private static final List<PropertyHandler> PROPERTY_HANDLERS = new ArrayList<>();

  65.     static {
  66.         PRIMITIVE_DEFAULTS.put(Integer.TYPE, Integer.valueOf(0));
  67.         PRIMITIVE_DEFAULTS.put(Short.TYPE, Short.valueOf((short) 0));
  68.         PRIMITIVE_DEFAULTS.put(Byte.TYPE, Byte.valueOf((byte) 0));
  69.         PRIMITIVE_DEFAULTS.put(Float.TYPE, Float.valueOf(0f));
  70.         PRIMITIVE_DEFAULTS.put(Double.TYPE, Double.valueOf(0d));
  71.         PRIMITIVE_DEFAULTS.put(Long.TYPE, Long.valueOf(0L));
  72.         PRIMITIVE_DEFAULTS.put(Boolean.TYPE, Boolean.FALSE);
  73.         PRIMITIVE_DEFAULTS.put(Character.TYPE, Character.valueOf((char) 0));

  74.         // Use a ServiceLoader to find implementations
  75.         ServiceLoader.load(ColumnHandler.class).forEach(COLUMN_HANDLERS::add);

  76.         // Use a ServiceLoader to find implementations
  77.         ServiceLoader.load(PropertyHandler.class).forEach(PROPERTY_HANDLERS::add);
  78.     }

  79.     /**
  80.      * ResultSet column to bean property name overrides.
  81.      */
  82.     private final Map<String, String> columnToPropertyOverrides;

  83.     /**
  84.      * Constructor for BeanProcessor.
  85.      */
  86.     public BeanProcessor() {
  87.         this(new HashMap<>());
  88.     }

  89.     /**
  90.      * Constructor for BeanProcessor configured with column to property name overrides.
  91.      *
  92.      * @param columnToPropertyOverrides ResultSet column to bean property name overrides
  93.      * @since 1.5
  94.      */
  95.     public BeanProcessor(final Map<String, String> columnToPropertyOverrides) {
  96.         if (columnToPropertyOverrides == null) {
  97.             throw new IllegalArgumentException("columnToPropertyOverrides map cannot be null");
  98.         }
  99.         this.columnToPropertyOverrides = columnToPropertyOverrides;
  100.     }

  101.     /**
  102.      * Calls the setter method on the target object for the given property.
  103.      * If no setter method exists for the property, this method does nothing.
  104.      * @param target The object to set the property on.
  105.      * @param prop The property to set.
  106.      * @param value The value to pass into the setter.
  107.      * @throws SQLException if an error occurs setting the property.
  108.      */
  109.     private void callSetter(final Object target, final PropertyDescriptor prop, Object value)
  110.             throws SQLException {

  111.         final Method setter = getWriteMethod(target, prop, value);

  112.         if (setter == null || setter.getParameterTypes().length != 1) {
  113.             return;
  114.         }

  115.         try {
  116.             final Class<?> firstParam = setter.getParameterTypes()[0];
  117.             for (final PropertyHandler handler : PROPERTY_HANDLERS) {
  118.                 if (handler.match(firstParam, value)) {
  119.                     value = handler.apply(firstParam, value);
  120.                     break;
  121.                 }
  122.             }

  123.             // Don't call setter if the value object isn't the right type
  124.             if (!this.isCompatibleType(value, firstParam)) {
  125.                 throw new SQLException(
  126.                         "Cannot set " + prop.getName() + ": incompatible types, cannot convert " + value.getClass().getName() + " to " + firstParam.getName());
  127.                 // value cannot be null here because isCompatibleType allows null
  128.             }
  129.             setter.invoke(target, value);

  130.         } catch (final IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
  131.             throw new SQLException("Cannot set " + prop.getName() + ": " + e.getMessage());
  132.         }
  133.     }

  134.     /**
  135.      * Creates a new object and initializes its fields from the ResultSet.
  136.      * @param <T> The type of bean to create
  137.      * @param resultSet The result set.
  138.      * @param type The bean type (the return type of the object).
  139.      * @param props The property descriptors.
  140.      * @param columnToProperty The column indices in the result set.
  141.      * @return An initialized object.
  142.      * @throws SQLException if a database error occurs.
  143.      */
  144.     private <T> T createBean(final ResultSet resultSet, final Class<T> type, final PropertyDescriptor[] props, final int[] columnToProperty)
  145.             throws SQLException {
  146.         return populateBean(resultSet, this.newInstance(type), props, columnToProperty);
  147.     }

  148.     /**
  149.      * Get the write method to use when setting {@code value} to the {@code target}.
  150.      *
  151.      * @param target Object where the write method will be called.
  152.      * @param prop   BeanUtils information.
  153.      * @param value  The value that will be passed to the write method.
  154.      * @return The {@link java.lang.reflect.Method} to call on {@code target} to write {@code value} or {@code null} if
  155.      *         there is no suitable write method.
  156.      */
  157.     protected Method getWriteMethod(final Object target, final PropertyDescriptor prop, final Object value) {
  158.         return prop.getWriteMethod();
  159.     }

  160.     /**
  161.      * ResultSet.getObject() returns an Integer object for an INT column.  The
  162.      * setter method for the property might take an Integer or a primitive int.
  163.      * This method returns true if the value can be successfully passed into
  164.      * the setter method.  Remember, Method.invoke() handles the unwrapping
  165.      * of Integer into an int.
  166.      *
  167.      * @param value The value to be passed into the setter method.
  168.      * @param type The setter's parameter type (non-null)
  169.      * @return boolean True if the value is compatible (null => true)
  170.      */
  171.     private boolean isCompatibleType(final Object value, final Class<?> type) {
  172.         // Do object check first, then primitives
  173.         return value == null || type.isInstance(value) || matchesPrimitive(type, value.getClass());
  174.     }

  175.     /**
  176.      * The positions in the returned array represent column numbers.  The
  177.      * values stored at each position represent the index in the
  178.      * {@code PropertyDescriptor[]} for the bean property that matches
  179.      * the column name.  If no bean property was found for a column, the
  180.      * position is set to {@code PROPERTY_NOT_FOUND}.
  181.      *
  182.      * @param rsmd The {@code ResultSetMetaData} containing column
  183.      * information.
  184.      *
  185.      * @param props The bean property descriptors.
  186.      *
  187.      * @throws SQLException if a database access error occurs
  188.      *
  189.      * @return An int[] with column index to property index mappings.  The 0th
  190.      * element is meaningless because JDBC column indexing starts at 1.
  191.      */
  192.     protected int[] mapColumnsToProperties(final ResultSetMetaData rsmd,
  193.             final PropertyDescriptor[] props) throws SQLException {

  194.         final int cols = rsmd.getColumnCount();
  195.         final int[] columnToProperty = new int[cols + 1];
  196.         Arrays.fill(columnToProperty, PROPERTY_NOT_FOUND);

  197.         for (int col = 1; col <= cols; col++) {
  198.             String columnName = rsmd.getColumnLabel(col);
  199.             if (null == columnName || 0 == columnName.length()) {
  200.               columnName = rsmd.getColumnName(col);
  201.             }
  202.             String propertyName = columnToPropertyOverrides.get(columnName);
  203.             if (propertyName == null) {
  204.                 propertyName = columnName;
  205.             }
  206.             if (propertyName == null) {
  207.                 propertyName = Integer.toString(col);
  208.             }

  209.             for (int i = 0; i < props.length; i++) {
  210.                 final PropertyDescriptor prop = props[i];
  211.                 final Method reader = prop.getReadMethod();

  212.                 // Check for @Column annotations as explicit marks
  213.                 final Column column;
  214.                 if (reader != null) {
  215.                     column = reader.getAnnotation(Column.class);
  216.                 } else {
  217.                     column = null;
  218.                 }

  219.                 final String propertyColumnName;
  220.                 if (column != null) {
  221.                     propertyColumnName = column.name();
  222.                 } else {
  223.                     propertyColumnName = prop.getName();
  224.                 }
  225.                 if (propertyName.equalsIgnoreCase(propertyColumnName)) {
  226.                     columnToProperty[col] = i;
  227.                     break;
  228.                 }
  229.             }
  230.         }

  231.         return columnToProperty;
  232.     }

  233.     /**
  234.      * Check whether a value is of the same primitive type as {@code targetType}.
  235.      *
  236.      * @param targetType The primitive type to target.
  237.      * @param valueType The value to match to the primitive type.
  238.      * @return Whether {@code valueType} can be coerced (e.g. autoboxed) into {@code targetType}.
  239.      */
  240.     private boolean matchesPrimitive(final Class<?> targetType, final Class<?> valueType) {
  241.         if (!targetType.isPrimitive()) {
  242.             return false;
  243.         }

  244.         try {
  245.             // see if there is a "TYPE" field.  This is present for primitive wrappers.
  246.             final Field typeField = valueType.getField("TYPE");
  247.             final Object primitiveValueType = typeField.get(valueType);

  248.             if (targetType == primitiveValueType) {
  249.                 return true;
  250.             }
  251.         } catch (final NoSuchFieldException | IllegalAccessException ignored) {
  252.             // an inaccessible TYPE field is a good sign that we're not working with a primitive wrapper.
  253.             // nothing to do.  we can't match for compatibility
  254.         }
  255.         return false;
  256.     }

  257.     /**
  258.      * Factory method that returns a new instance of the given Class.  This
  259.      * is called at the start of the bean creation process and may be
  260.      * overridden to provide custom behavior like returning a cached bean
  261.      * instance.
  262.      * @param <T> The type of object to create
  263.      * @param c The Class to create an object from.
  264.      * @return A newly created object of the Class.
  265.      * @throws SQLException if creation failed.
  266.      */
  267.     protected <T> T newInstance(final Class<T> c) throws SQLException {
  268.         try {
  269.             return c.getDeclaredConstructor().newInstance();

  270.         } catch (final IllegalAccessException | InstantiationException | InvocationTargetException |
  271.             NoSuchMethodException e) {
  272.             throw new SQLException("Cannot create " + c.getName() + ": " + e.getMessage());
  273.         }
  274.     }

  275.     /**
  276.      * Initializes the fields of the provided bean from the ResultSet.
  277.      * @param <T> The type of bean
  278.      * @param resultSet The result set.
  279.      * @param bean The bean to be populated.
  280.      * @return An initialized object.
  281.      * @throws SQLException if a database error occurs.
  282.      */
  283.     public <T> T populateBean(final ResultSet resultSet, final T bean) throws SQLException {
  284.         final PropertyDescriptor[] props = this.propertyDescriptors(bean.getClass());
  285.         final ResultSetMetaData rsmd = resultSet.getMetaData();
  286.         final int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

  287.         return populateBean(resultSet, bean, props, columnToProperty);
  288.     }

  289.     /**
  290.      * This method populates a bean from the ResultSet based upon the underlying meta-data.
  291.      *
  292.      * @param <T> The type of bean
  293.      * @param resultSet The result set.
  294.      * @param bean The bean to be populated.
  295.      * @param props The property descriptors.
  296.      * @param columnToProperty The column indices in the result set.
  297.      * @return An initialized object.
  298.      * @throws SQLException if a database error occurs.
  299.      */
  300.     private <T> T populateBean(final ResultSet resultSet, final T bean,
  301.             final PropertyDescriptor[] props, final int[] columnToProperty)
  302.             throws SQLException {

  303.         for (int i = 1; i < columnToProperty.length; i++) {

  304.             if (columnToProperty[i] == PROPERTY_NOT_FOUND) {
  305.                 continue;
  306.             }

  307.             final PropertyDescriptor prop = props[columnToProperty[i]];
  308.             final Class<?> propType = prop.getPropertyType();

  309.             Object value = null;
  310.             if (propType != null) {
  311.                 value = this.processColumn(resultSet, i, propType);

  312.                 if (value == null && propType.isPrimitive()) {
  313.                     value = PRIMITIVE_DEFAULTS.get(propType);
  314.                 }
  315.             }

  316.             this.callSetter(bean, prop, value);
  317.         }

  318.         return bean;
  319.     }

  320.     /**
  321.      * Convert a {@code ResultSet} column into an object.  Simple
  322.      * implementations could just call {@code rs.getObject(index)} while
  323.      * more complex implementations could perform type manipulation to match
  324.      * the column's type to the bean property type.
  325.      *
  326.      * <p>
  327.      * This implementation calls the appropriate {@code ResultSet} getter
  328.      * method for the given property type to perform the type conversion.  If
  329.      * the property type doesn't match one of the supported
  330.      * {@code ResultSet} types, {@code getObject} is called.
  331.      * </p>
  332.      *
  333.      * @param resultSet The {@code ResultSet} currently being processed.  It is
  334.      * positioned on a valid row before being passed into this method.
  335.      *
  336.      * @param index The current column index being processed.
  337.      *
  338.      * @param propType The bean property type that this column needs to be
  339.      * converted into.
  340.      *
  341.      * @throws SQLException if a database access error occurs
  342.      *
  343.      * @return The object from the {@code ResultSet} at the given column
  344.      * index after optional type processing or {@code null} if the column
  345.      * value was SQL NULL.
  346.      */
  347.     protected Object processColumn(final ResultSet resultSet, final int index, final Class<?> propType)
  348.         throws SQLException {

  349.         Object retval = resultSet.getObject(index);

  350.         if ( !propType.isPrimitive() && retval == null ) {
  351.             return null;
  352.         }

  353.         for (final ColumnHandler<?> handler : COLUMN_HANDLERS) {
  354.             if (handler.match(propType)) {
  355.                 retval = handler.apply(resultSet, index);
  356.                 break;
  357.             }
  358.         }

  359.         return retval;

  360.     }

  361.     /**
  362.      * Returns a PropertyDescriptor[] for the given Class.
  363.      *
  364.      * @param c The Class to retrieve PropertyDescriptors for.
  365.      * @return A PropertyDescriptor[] describing the Class.
  366.      * @throws SQLException if introspection failed.
  367.      */
  368.     private PropertyDescriptor[] propertyDescriptors(final Class<?> c)
  369.         throws SQLException {
  370.         // Introspector caches BeanInfo classes for better performance
  371.         BeanInfo beanInfo = null;
  372.         try {
  373.             beanInfo = Introspector.getBeanInfo(c);

  374.         } catch (final IntrospectionException e) {
  375.             throw new SQLException(
  376.                 "Bean introspection failed: " + e.getMessage());
  377.         }

  378.         return beanInfo.getPropertyDescriptors();
  379.     }

  380.     /**
  381.      * Convert a {@code ResultSet} row into a JavaBean.  This
  382.      * implementation uses reflection and {@code BeanInfo} classes to
  383.      * match column names to bean property names.  Properties are matched to
  384.      * columns based on several factors:
  385.      * &lt;br/&gt;
  386.      * &lt;ol&gt;
  387.      *     &lt;li&gt;
  388.      *     The class has a writable property with the same name as a column.
  389.      *     The name comparison is case insensitive.
  390.      *     &lt;/li&gt;
  391.      *
  392.      *     &lt;li&gt;
  393.      *     The column type can be converted to the property's set method
  394.      *     parameter type with a ResultSet.get* method.  If the conversion fails
  395.      *     (ie. the property was an int and the column was a Timestamp) an
  396.      *     SQLException is thrown.
  397.      *     &lt;/li&gt;
  398.      * &lt;/ol&gt;
  399.      *
  400.      * &lt;p&gt;
  401.      * Primitive bean properties are set to their defaults when SQL NULL is
  402.      * returned from the {@code ResultSet}.  Numeric fields are set to 0
  403.      * and booleans are set to false.  Object bean properties are set to
  404.      * {@code null} when SQL NULL is returned.  This is the same behavior
  405.      * as the {@code ResultSet} get* methods.
  406.      * &lt;/p&gt;
  407.      * @param <T> The type of bean to create
  408.      * @param rs ResultSet that supplies the bean data
  409.      * @param type Class from which to create the bean instance
  410.      * @throws SQLException if a database access error occurs
  411.      * @return the newly created bean
  412.      */
  413.     public <T> T toBean(final ResultSet rs, final Class<? extends T> type) throws SQLException {
  414.         final T bean = this.newInstance(type);
  415.         return this.populateBean(rs, bean);
  416.     }

  417.     /**
  418.      * Convert a {@code ResultSet} into a {@code List} of JavaBeans.
  419.      * This implementation uses reflection and {@code BeanInfo} classes to
  420.      * match column names to bean property names. Properties are matched to
  421.      * columns based on several factors:
  422.      * &lt;br/&gt;
  423.      * &lt;ol&gt;
  424.      *     &lt;li&gt;
  425.      *     The class has a writable property with the same name as a column.
  426.      *     The name comparison is case insensitive.
  427.      *     &lt;/li&gt;
  428.      *
  429.      *     &lt;li&gt;
  430.      *     The column type can be converted to the property's set method
  431.      *     parameter type with a ResultSet.get* method.  If the conversion fails
  432.      *     (ie. the property was an int and the column was a Timestamp) an
  433.      *     SQLException is thrown.
  434.      *     &lt;/li&gt;
  435.      * &lt;/ol&gt;
  436.      *
  437.      * <p>
  438.      * Primitive bean properties are set to their defaults when SQL NULL is
  439.      * returned from the {@code ResultSet}.  Numeric fields are set to 0
  440.      * and booleans are set to false.  Object bean properties are set to
  441.      * {@code null} when SQL NULL is returned.  This is the same behavior
  442.      * as the {@code ResultSet} get* methods.
  443.      * &lt;/p&gt;
  444.      * @param <T> The type of bean to create
  445.      * @param resultSet ResultSet that supplies the bean data
  446.      * @param type Class from which to create the bean instance
  447.      * @throws SQLException if a database access error occurs
  448.      * @return the newly created List of beans
  449.      */
  450.     public <T> List<T> toBeanList(final ResultSet resultSet, final Class<? extends T> type) throws SQLException {
  451.         final List<T> results = new ArrayList<>();

  452.         if (!resultSet.next()) {
  453.             return results;
  454.         }

  455.         final PropertyDescriptor[] props = this.propertyDescriptors(type);
  456.         final ResultSetMetaData rsmd = resultSet.getMetaData();
  457.         final int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);

  458.         do {
  459.             results.add(this.createBean(resultSet, type, props, columnToProperty));
  460.         } while (resultSet.next());

  461.         return results;
  462.     }

  463. }