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     
018    package org.apache.commons.beanutils;
019    
020    import org.apache.commons.collections.Transformer;
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    
024    import java.lang.reflect.InvocationTargetException;
025    
026    
027    /**
028     * <p><code>Transformer</code> that outputs a property value.</p>
029     *
030     * <p>An implementation of <code>org.apache.commons.collections.Transformer</code> that transforms
031     * the object provided by returning the value of a specified property of the object.  The
032     * constructor for <code>BeanToPropertyValueTransformer</code> requires the name of the property
033     * that will be used in the transformation.  The property can be a simple, nested, indexed, or
034     * mapped property as defined by <code>org.apache.commons.beanutils.PropertyUtils</code>. If any
035     * object in the property path specified by <code>propertyName</code> is <code>null</code> then the
036     * outcome is based on the value of the <code>ignoreNull</code> attribute.
037     * </p>
038     *
039     * <p>
040     * A typical usage might look like:
041     * <code><pre>
042     * // create the transformer
043     * BeanToPropertyValueTransformer transformer = new BeanToPropertyValueTransformer( "person.address.city" );
044     *
045     * // transform the Collection
046     * Collection peoplesCities = CollectionUtils.collect( peopleCollection, transformer );
047     * </pre></code>
048     * </p>
049     *
050     * <p>
051     * This would take a <code>Collection</code> of person objects and return a <code>Collection</code>
052     * of objects which represents the cities in which each person lived. Assuming...
053     * <ul>
054     *    <li>
055     *       The top level object in the <code>peeopleCollection</code> is an object which represents a
056     *       person.
057     *    </li>
058     *    <li>
059     *       The person object has a <code>getAddress()</code> method which returns an object which
060     *       represents a person's address.
061     *    </li>
062     *    <li>
063     *       The address object has a <code>getCity()</code> method which returns an object which
064     *       represents the city in which a person lives.
065     *    </li>
066     * </ul>
067     *
068     * @author Norm Deane
069     * @see org.apache.commons.beanutils.PropertyUtils
070     * @see org.apache.commons.collections.Transformer
071     */
072    public class BeanToPropertyValueTransformer implements Transformer {
073       
074        /** For logging. */
075        private final Log log = LogFactory.getLog(this.getClass());
076    
077        /** The name of the property that will be used in the transformation of the object. */
078        private String propertyName;
079    
080        /**
081         * <p>Should null objects on the property path throw an <code>IllegalArgumentException</code>?</p>
082         * <p>
083         * Determines whether <code>null</code> objects in the property path will genenerate an
084         * <code>IllegalArgumentException</code> or not. If set to <code>true</code> then if any objects
085         * in the property path evaluate to <code>null</code> then the
086         * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged but
087         * not rethrown and <code>null</code> will be returned.  If set to <code>false</code> then if any
088         * objects in the property path evaluate to <code>null</code> then the
089         * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged and
090         * rethrown.
091         * </p>
092         */
093        private boolean ignoreNull;
094    
095        /**
096         * Constructs a Transformer which does not ignore nulls.
097         * Constructor which takes the name of the property that will be used in the transformation and
098         * assumes <code>ignoreNull</code> to be <code>false</code>.
099         *
100         * @param propertyName The name of the property that will be used in the transformation.
101         * @throws IllegalArgumentException If the <code>propertyName</code> is <code>null</code> or
102         * empty.
103         */
104        public BeanToPropertyValueTransformer(String propertyName) {
105            this(propertyName, false);
106        }
107    
108        /**
109         * Constructs a Transformer and sets ignoreNull.
110         * Constructor which takes the name of the property that will be used in the transformation and
111         * a boolean which determines whether <code>null</code> objects in the property path will
112         * genenerate an <code>IllegalArgumentException</code> or not.
113         *
114         * @param propertyName The name of the property that will be used in the transformation.
115         * @param ignoreNull Determines whether <code>null</code> objects in the property path will
116         * genenerate an <code>IllegalArgumentException</code> or not.
117         * @throws IllegalArgumentException If the <code>propertyName</code> is <code>null</code> or
118         * empty.
119         */
120        public BeanToPropertyValueTransformer(String propertyName, boolean ignoreNull) {
121            super();
122    
123            if ((propertyName != null) && (propertyName.length() > 0)) {
124                this.propertyName = propertyName;
125                this.ignoreNull = ignoreNull;
126            } else {
127                throw new IllegalArgumentException(
128                    "propertyName cannot be null or empty");
129            }
130        }
131    
132        /**
133         * Returns the value of the property named in the transformer's constructor for
134         * the object provided. If any object in the property path leading up to the target property is
135         * <code>null</code> then the outcome will be based on the value of the <code>ignoreNull</code>
136         * attribute. By default, <code>ignoreNull</code> is <code>false</code> and would result in an
137         * <code>IllegalArgumentException</code> if an object in the property path leading up to the
138         * target property is <code>null</code>.
139         *
140         * @param object The object to be transformed.
141         * @return The value of the property named in the transformer's constructor for the object
142         * provided.
143         * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
144         * NoSuchMethodException is thrown when trying to access the property specified on the object
145         * provided. Or if an object in the property path provided is <code>null</code> and
146         * <code>ignoreNull</code> is set to <code>false</code>.
147         */
148        public Object transform(Object object) {
149           
150            Object propertyValue = null;
151    
152            try {
153                propertyValue = PropertyUtils.getProperty(object, propertyName);
154            } catch (IllegalArgumentException e) {
155                final String errorMsg = "Problem during transformation. Null value encountered in property path...";
156    
157                if (ignoreNull) {
158                    log.warn("WARNING: " + errorMsg + e);
159                } else {
160                    IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
161                    if (!BeanUtils.initCause(iae, e)) {
162                        log.error(errorMsg, e);
163                    }
164                    throw iae;
165                }
166            } catch (IllegalAccessException e) {
167                final String errorMsg = "Unable to access the property provided.";
168                IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
169                if (!BeanUtils.initCause(iae, e)) {
170                    log.error(errorMsg, e);
171                }
172                throw iae;
173            } catch (InvocationTargetException e) {
174                final String errorMsg = "Exception occurred in property's getter";
175                IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
176                if (!BeanUtils.initCause(iae, e)) {
177                    log.error(errorMsg, e);
178                }
179                throw iae;
180            } catch (NoSuchMethodException e) {
181                final String errorMsg = "No property found for name [" +
182                    propertyName + "]";
183                IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
184                if (!BeanUtils.initCause(iae, e)) {
185                    log.error(errorMsg, e);
186                }
187                throw iae;
188            }
189    
190            return propertyValue;
191        }
192    
193        /**
194         * Returns the name of the property that will be used in the transformation of the bean.
195         *
196         * @return The name of the property that will be used in the transformation of the bean.
197         */
198        public String getPropertyName() {
199            return propertyName;
200        }
201    
202        /**
203         * Returns the flag which determines whether <code>null</code> objects in the property path will
204         * genenerate an <code>IllegalArgumentException</code> or not. If set to <code>true</code> then
205         * if any objects in the property path evaluate to <code>null</code> then the
206         * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged but
207         * not rethrown and <code>null</code> will be returned.  If set to <code>false</code> then if any
208         * objects in the property path evaluate to <code>null</code> then the
209         * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged and
210         * rethrown.
211         *
212         * @return The flag which determines whether <code>null</code> objects in the property path will
213         * genenerate an <code>IllegalArgumentException</code> or not.
214         */
215        public boolean isIgnoreNull() {
216            return ignoreNull;
217        }
218    }