View Javadoc
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  
18  package org.apache.commons.beanutils2;
19  
20  import java.lang.reflect.InvocationTargetException;
21  import java.util.function.Function;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  /**
27   * <p>
28   * {@code Transformer} that outputs a property value.
29   * </p>
30   *
31   * <p>
32   * An implementation of {@link java.util.function.Function} that transforms the object provided by returning the value of a specified property of the object.
33   * The constructor for {@code BeanToPropertyValueTransformer} requires the name of the property that will be used in the transformation. The property can be a
34   * simple, nested, indexed, or mapped property as defined by {@code org.apache.commons.beanutils2.PropertyUtils}. If any object in the property path specified
35   * by {@code propertyName</code> is <code>null} then the
36   * outcome is based on the value of the {@code ignoreNull} attribute.
37   * </p>
38   *
39   * <p>
40   * A typical usage might look like:
41   * </p>
42   * <pre>{@code
43   * // create the transformer
44   * BeanToPropertyValueTransformer transformer = new BeanToPropertyValueTransformer( "person.address.city" );
45   *
46   * // transform the Collection
47   * Collection peoplesCities = CollectionUtils.collect( peopleCollection, transformer );
48   * }</pre>
49   *
50   * <p>
51   * 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
52   * lived. Assuming...
53   * <ul>
54   * <li>The top level object in the {@code peopleCollection} is an object which represents a person.</li>
55   * <li>The person object has a {@code getAddress()} method which returns an object which represents a person's address.</li>
56   * <li>The address object has a {@code getCity()} method which returns an object which represents the city in which a person lives.</li>
57   * </ul>
58   *
59   * @param <T> the type of the input to the function
60   * @param <R> the type of the result of the function
61   * @see org.apache.commons.beanutils2.PropertyUtils
62   * @see java.util.function.Function
63   */
64  public class BeanToPropertyValueTransformer<T, R> implements Function<T, R> {
65  
66      /** For logging. Each subclass gets its own log instance. */
67      private final Log log = LogFactory.getLog(this.getClass());
68  
69      /** The name of the property that will be used in the transformation of the object. */
70      private final String propertyName;
71  
72      /**
73       * <p>
74       * Should null objects on the property path throw an {@code IllegalArgumentException}?
75       * </p>
76       * <p>
77       * Determines whether {@code null} objects in the property path will generate an
78       * {@code IllegalArgumentException</code> or not. If set to <code>true} then if any objects
79       * in the property path evaluate to {@code null} then the
80       * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
81       * not re-thrown and {@code null</code> will be returned.  If set to <code>false} then if any
82       * objects in the property path evaluate to {@code null} then the
83       * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and re-thrown.
84       * </p>
85       */
86      private final boolean ignoreNull;
87  
88      /**
89       * Constructs a Transformer which does not ignore nulls. Constructor which takes the name of the property that will be used in the transformation and
90       * assumes {@code ignoreNull</code> to be <code>false}.
91       *
92       * @param propertyName The name of the property that will be used in the transformation.
93       * @throws IllegalArgumentException If the {@code propertyName</code> is <code>null} or empty.
94       */
95      public BeanToPropertyValueTransformer(final String propertyName) {
96          this(propertyName, false);
97      }
98  
99      /**
100      * Constructs a Transformer and sets ignoreNull. Constructor which takes the name of the property that will be used in the transformation and a boolean
101      * which determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
102      *
103      * @param propertyName The name of the property that will be used in the transformation.
104      * @param ignoreNull   Determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
105      * @throws IllegalArgumentException If the {@code propertyName</code> is <code>null} or empty.
106      */
107     public BeanToPropertyValueTransformer(final String propertyName, final boolean ignoreNull) {
108         if (propertyName == null || propertyName.isEmpty()) {
109             throw new IllegalArgumentException("propertyName cannot be null or empty");
110         }
111         this.propertyName = propertyName;
112         this.ignoreNull = ignoreNull;
113     }
114 
115     /**
116      * 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
117      * target property is {@code null</code> then the outcome will be based on the value of the <code>ignoreNull}
118      * attribute. By default, {@code ignoreNull</code> is <code>false} and would result in an
119      * {@code IllegalArgumentException} if an object in the property path leading up to the
120      * target property is {@code null}.
121      *
122      * &#64;param object The object to be transformed.
123      * &#64;return The value of the property named in the transformer's constructor for the object
124      * provided.
125      * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
126      * NoSuchMethodException is thrown when trying to access the property specified on the object
127      * provided. Or if an object in the property path provided is {@code null} and
128      * {@code ignoreNull</code> is set to <code>false}.
129      */
130     @Override
131     public R apply(final T object) {
132         R propertyValue = null;
133         try {
134             propertyValue = (R) PropertyUtils.getProperty(object, propertyName);
135         } catch (final IllegalArgumentException e) {
136             final String errorMsg = "Problem during transformation. Null value encountered in property path...";
137             if (!ignoreNull) {
138                 throw new IllegalArgumentException(errorMsg, e);
139             }
140             log.warn(errorMsg, e);
141         } catch (final IllegalAccessException e) {
142             final String errorMsg = "Unable to access the property provided.";
143             throw new IllegalArgumentException(errorMsg, e);
144         } catch (final InvocationTargetException e) {
145             final String errorMsg = "Exception occurred in property's getter";
146             throw new IllegalArgumentException(errorMsg, e);
147         } catch (final NoSuchMethodException e) {
148             final String errorMsg = "No property found for name [" + propertyName + "]";
149             throw new IllegalArgumentException(errorMsg, e);
150         }
151 
152         return propertyValue;
153     }
154 
155     /**
156      * Returns the name of the property that will be used in the transformation of the bean.
157      *
158      * @return The name of the property that will be used in the transformation of the bean.
159      */
160     public String getPropertyName() {
161         return propertyName;
162     }
163 
164     /**
165      * Returns the flag which determines whether {@code null} objects in the property path will generate an
166      * {@code IllegalArgumentException</code> or not. If set to <code>true} then
167      * if any objects in the property path evaluate to {@code null} then the
168      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
169      * not re-thrown and {@code null</code> will be returned.  If set to <code>false} then if any
170      * objects in the property path evaluate to {@code null} then the
171      * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and re-thrown.
172      *
173      * @return The flag which determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
174      */
175     public boolean isIgnoreNull() {
176         return ignoreNull;
177     }
178 }