BeanToPropertyValueTransformer.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.beanutils2;

  18. import java.lang.reflect.InvocationTargetException;
  19. import java.util.function.Function;

  20. import org.apache.commons.logging.Log;
  21. import org.apache.commons.logging.LogFactory;

  22. /**
  23.  * <p>
  24.  * {@code Transformer} that outputs a property value.
  25.  * </p>
  26.  *
  27.  * <p>
  28.  * An implementation of {@link java.util.function.Function} that transforms the object provided by returning the value of a specified property of the object.
  29.  * The constructor for {@code BeanToPropertyValueTransformer} requires the name of the property that will be used in the transformation. The property can be a
  30.  * simple, nested, indexed, or mapped property as defined by {@code org.apache.commons.beanutils2.PropertyUtils}. If any object in the property path specified
  31.  * by {@code propertyName</code> is <code>null} then the
  32.  * outcome is based on the value of the {@code ignoreNull} attribute.
  33.  * </p>
  34.  *
  35.  * <p>
  36.  * A typical usage might look like:
  37.  * </p>
  38.  * <pre>{@code
  39.  * // create the transformer
  40.  * BeanToPropertyValueTransformer transformer = new BeanToPropertyValueTransformer( "person.address.city" );
  41.  *
  42.  * // transform the Collection
  43.  * Collection peoplesCities = CollectionUtils.collect( peopleCollection, transformer );
  44.  * }</pre>
  45.  *
  46.  * <p>
  47.  * This would take a {@code Collection</code> of person objects and return a <code>Collection} of objects which represents the cities in which each person
  48.  * lived. Assuming...
  49.  * <ul>
  50.  * <li>The top level object in the {@code peopleCollection} is an object which represents a person.</li>
  51.  * <li>The person object has a {@code getAddress()} method which returns an object which represents a person's address.</li>
  52.  * <li>The address object has a {@code getCity()} method which returns an object which represents the city in which a person lives.</li>
  53.  * </ul>
  54.  *
  55.  * @param <T> the type of the input to the function
  56.  * @param <R> the type of the result of the function
  57.  * @see org.apache.commons.beanutils2.PropertyUtils
  58.  * @see java.util.function.Function
  59.  */
  60. public class BeanToPropertyValueTransformer<T, R> implements Function<T, R> {

  61.     /** For logging. Each subclass gets its own log instance. */
  62.     private final Log log = LogFactory.getLog(this.getClass());

  63.     /** The name of the property that will be used in the transformation of the object. */
  64.     private final String propertyName;

  65.     /**
  66.      * <p>
  67.      * Should null objects on the property path throw an {@code IllegalArgumentException}?
  68.      * </p>
  69.      * <p>
  70.      * Determines whether {@code null} objects in the property path will generate an
  71.      * {@code IllegalArgumentException</code> or not. If set to <code>true} then if any objects
  72.      * in the property path evaluate to {@code null} then the
  73.      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
  74.      * not re-thrown and {@code null</code> will be returned.  If set to <code>false} then if any
  75.      * objects in the property path evaluate to {@code null} then the
  76.      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and re-thrown.
  77.      * </p>
  78.      */
  79.     private final boolean ignoreNull;

  80.     /**
  81.      * Constructs a Transformer which does not ignore nulls. Constructor which takes the name of the property that will be used in the transformation and
  82.      * assumes {@code ignoreNull</code> to be <code>false}.
  83.      *
  84.      * @param propertyName The name of the property that will be used in the transformation.
  85.      * @throws IllegalArgumentException If the {@code propertyName</code> is <code>null} or empty.
  86.      */
  87.     public BeanToPropertyValueTransformer(final String propertyName) {
  88.         this(propertyName, false);
  89.     }

  90.     /**
  91.      * Constructs a Transformer and sets ignoreNull. Constructor which takes the name of the property that will be used in the transformation and a boolean
  92.      * which determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
  93.      *
  94.      * @param propertyName The name of the property that will be used in the transformation.
  95.      * @param ignoreNull   Determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
  96.      * @throws IllegalArgumentException If the {@code propertyName</code> is <code>null} or empty.
  97.      */
  98.     public BeanToPropertyValueTransformer(final String propertyName, final boolean ignoreNull) {
  99.         if (propertyName == null || propertyName.isEmpty()) {
  100.             throw new IllegalArgumentException("propertyName cannot be null or empty");
  101.         }
  102.         this.propertyName = propertyName;
  103.         this.ignoreNull = ignoreNull;
  104.     }

  105.     /**
  106.      * Returns the value of the property named in the transformer's constructor for the object provided. If any object in the property path leading up to the
  107.      * target property is {@code null</code> then the outcome will be based on the value of the <code>ignoreNull}
  108.      * attribute. By default, {@code ignoreNull</code> is <code>false} and would result in an
  109.      * {@code IllegalArgumentException} if an object in the property path leading up to the
  110.      * target property is {@code null}.
  111.      *
  112.      * &#64;param object The object to be transformed.
  113.      * &#64;return The value of the property named in the transformer's constructor for the object
  114.      * provided.
  115.      * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
  116.      * NoSuchMethodException is thrown when trying to access the property specified on the object
  117.      * provided. Or if an object in the property path provided is {@code null} and
  118.      * {@code ignoreNull</code> is set to <code>false}.
  119.      */
  120.     @Override
  121.     public R apply(final T object) {
  122.         R propertyValue = null;
  123.         try {
  124.             propertyValue = (R) PropertyUtils.getProperty(object, propertyName);
  125.         } catch (final IllegalArgumentException e) {
  126.             final String errorMsg = "Problem during transformation. Null value encountered in property path...";
  127.             if (!ignoreNull) {
  128.                 throw new IllegalArgumentException(errorMsg, e);
  129.             }
  130.             log.warn(errorMsg, e);
  131.         } catch (final IllegalAccessException e) {
  132.             final String errorMsg = "Unable to access the property provided.";
  133.             throw new IllegalArgumentException(errorMsg, e);
  134.         } catch (final InvocationTargetException e) {
  135.             final String errorMsg = "Exception occurred in property's getter";
  136.             throw new IllegalArgumentException(errorMsg, e);
  137.         } catch (final NoSuchMethodException e) {
  138.             final String errorMsg = "No property found for name [" + propertyName + "]";
  139.             throw new IllegalArgumentException(errorMsg, e);
  140.         }

  141.         return propertyValue;
  142.     }

  143.     /**
  144.      * Returns the name of the property that will be used in the transformation of the bean.
  145.      *
  146.      * @return The name of the property that will be used in the transformation of the bean.
  147.      */
  148.     public String getPropertyName() {
  149.         return propertyName;
  150.     }

  151.     /**
  152.      * Returns the flag which determines whether {@code null} objects in the property path will generate an
  153.      * {@code IllegalArgumentException</code> or not. If set to <code>true} then
  154.      * if any objects in the property path evaluate to {@code null} then the
  155.      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
  156.      * not re-thrown and {@code null</code> will be returned.  If set to <code>false} then if any
  157.      * objects in the property path evaluate to {@code null} then the
  158.      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and re-thrown.
  159.      *
  160.      * @return The flag which determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
  161.      */
  162.     public boolean isIgnoreNull() {
  163.         return ignoreNull;
  164.     }
  165. }