    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.Closure;
    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>Closure</code> that sets a property.</p>
    029     * <p>
    030     * An implementation of <code>org.apache.commons.collections.Closure</code> that updates
    031     * a specified property on the object provided with a specified value.
    032     * The <code>BeanPropertyValueChangeClosure</code> constructor takes two parameters which determine
    033     * what property will be updated and with what value.
    034     * <dl>
    035     *    <dt>
    036     *       <b><code>
    037     *           <pre>public BeanPropertyValueChangeClosure( String propertyName, Object propertyValue )</pre>
    038     *       </code></b>
    039     *    </dt>
    040     *    <dd>
    041     *       Will create a <code>Closure</code> that will update an object by setting the property
    042     *       specified by <code>propertyName</code> to the value specified by <code>propertyValue</code>.
    043     *    </dd>
    044     * </dl>
    045     *
    046     * <p/>
    047     * <strong>Note:</strong> Property names can be a simple, nested, indexed, or mapped property as defined by
    048     * <code>org.apache.commons.beanutils.PropertyUtils</code>.  If any object in the property path
    049     * specified by <code>propertyName</code> is <code>null</code> then the outcome is based on the
    050     * value of the <code>ignoreNull</code> attribute.
    051     *
    052     * <p/>
    053     * A typical usage might look like:
    054     * <code><pre>
    055     * // create the closure
    056     * BeanPropertyValueChangeClosure closure =
    057     *    new BeanPropertyValueChangeClosure( "activeEmployee", Boolean.TRUE );
    058     *
    059     * // update the Collection
    060     * CollectionUtils.forAllDo( peopleCollection, closure );
    061     * </pre></code>
    062     * <p/>
    063     *
    064     * This would take a <code>Collection</code> of person objects and update the
    065     * <code>activeEmployee</code> property of each object in the <code>Collection</code> to
    066     * <code>true</code>. Assuming...
    067     * <ul>
    068     *    <li>
    069     *       The top level object in the <code>peopleCollection</code> is an object which represents a
    070     *       person.
    071     *    </li>
    072     *    <li>
    073     *       The person object has a <code>setActiveEmployee( boolean )</code> method which updates
    074     *       the value for the object's <code>activeEmployee</code> property.
    075     *    </li>
    076     * </ul>
    077     *
    078     * @author Norm Deane
    079     * @see org.apache.commons.beanutils.PropertyUtils
    080     * @see org.apache.commons.collections.Closure
    081     */
    082    public class BeanPropertyValueChangeClosure implements Closure {
    083
    084        /** For logging. */
    085        private final Log log = LogFactory.getLog(this.getClass());
    086
    087        /**
    088         * The name of the property which will be updated when this <code>Closure</code> executes.
    089         */
    090        private String propertyName;
    091
    092        /**
    093         * The value that the property specified by <code>propertyName</code>
    094         * will be updated to when this <code>Closure</code> executes.
    095         */
    096        private Object propertyValue;
    097
    098        /**
    099         * Determines whether <code>null</code> objects in the property path will genenerate an
    100         * <code>IllegalArgumentException</code> or not. If set to <code>true</code> then if any objects
    101         * in the property path leading up to the target property evaluate to <code>null</code> then the
    102         * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged but
    103         * not rethrown.  If set to <code>false</code> then if any objects in the property path leading
    104         * up to the target property evaluate to <code>null</code> then the
    105         * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged and
    106         * rethrown.
    107         */
    108        private boolean ignoreNull;
    109
    110        /**
    111         * Constructor which takes the name of the property to be changed, the new value to set
    112         * the property to, and assumes <code>ignoreNull</code> to be <code>false</code>.
    113         *
    114         * @param propertyName The name of the property that will be updated with the value specified by
    115         * <code>propertyValue</code>.
    116         * @param propertyValue The value that <code>propertyName</code> will be set to on the target
    117         * object.
    118         * @throws IllegalArgumentException If the propertyName provided is null or empty.
    119         */
    120        public BeanPropertyValueChangeClosure(String propertyName, Object propertyValue) {
    121            this(propertyName, propertyValue, false);
    122        }
    123
    124        /**
    125         * Constructor which takes the name of the property to be changed, the new value to set
    126         * the property to and a boolean which determines whether <code>null</code> objects in the
    127         * property path will genenerate an <code>IllegalArgumentException</code> or not.
    128         *
    129         * @param propertyName The name of the property that will be updated with the value specified by
    130         * <code>propertyValue</code>.
    131         * @param propertyValue The value that <code>propertyName</code> will be set to on the target
    132         * object.
    133         * @param ignoreNull Determines whether <code>null</code> objects in the property path will
    134         * genenerate an <code>IllegalArgumentException</code> or not.
    135         * @throws IllegalArgumentException If the propertyName provided is null or empty.
    136         */
    137        public BeanPropertyValueChangeClosure(String propertyName, Object propertyValue, boolean ignoreNull) {
    138            super();
    139
    140            if ((propertyName != null) && (propertyName.length() > 0)) {
    141                this.propertyName = propertyName;
    142                this.propertyValue = propertyValue;
    143                this.ignoreNull = ignoreNull;
    144            } else {
    145                throw new IllegalArgumentException("propertyName cannot be null or empty");
    146            }
    147        }
    148
    149        /**
    150         * Updates the target object provided using the property update criteria provided when this
    151         * <code>BeanPropertyValueChangeClosure</code> was constructed.  If any object in the property
    152         * path leading up to the target property is <code>null</code> then the outcome will be based on
    153         * the value of the <code>ignoreNull</code> attribute. By default, <code>ignoreNull</code> is
    154         * <code>false</code> and would result in an <code>IllegalArgumentException</code> if an object
    155         * in the property path leading up to the target property is <code>null</code>.
    156         *
    157         * @param object The object to be updated.
    158         * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
    159         * NoSuchMethodException is thrown when trying to access the property specified on the object
    160         * provided. Or if an object in the property path provided is <code>null</code> and
    161         * <code>ignoreNull</code> is set to <code>false</code>.
    162         */
    163        public void execute(Object object) {
    164
    165            try {
    166                PropertyUtils.setProperty(object, propertyName, propertyValue);
    167            } catch (IllegalArgumentException e) {
    168                final String errorMsg = "Unable to execute Closure. Null value encountered in property path...";
    169
    170                if (ignoreNull) {
    171                    log.warn("WARNING: " + errorMsg + e);
    172                } else {
    173                    IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
    174                    if (!BeanUtils.initCause(iae, e)) {
    175                        log.error(errorMsg, e);
    176                    }
    177                    throw iae;
    178                }
    179            } catch (IllegalAccessException e) {
    180                final String errorMsg = "Unable to access the property provided.";
    181                IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
    182                if (!BeanUtils.initCause(iae, e)) {
    183                    log.error(errorMsg, e);
    184                }
    185                throw iae;
    186            } catch (InvocationTargetException e) {
    187                final String errorMsg = "Exception occurred in property's getter";
    188                IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
    189                if (!BeanUtils.initCause(iae, e)) {
    190                    log.error(errorMsg, e);
    191                }
    192                throw iae;
    193            } catch (NoSuchMethodException e) {
    194                final String errorMsg = "Property not found";
    195                IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
    196                if (!BeanUtils.initCause(iae, e)) {
    197                    log.error(errorMsg, e);
    198                }
    199                throw iae;
    200            }
    201        }
    202
    203        /**
    204         * Returns the name of the property which will be updated when this <code>Closure</code> executes.
    205         *
    206         * @return The name of the property which will be updated when this <code>Closure</code> executes.
    207         */
    208        public String getPropertyName() {
    209            return propertyName;
    210        }
    211
    212        /**
    213         * Returns the value that the property specified by <code>propertyName</code>
    214         * will be updated to when this <code>Closure</code> executes.
    215         *
    216         * @return The value that the property specified by <code>propertyName</code>
    217         * will be updated to when this <code>Closure</code> executes.
    218         */
    219        public Object getPropertyValue() {
    220            return propertyValue;
    221        }
    222
    223        /**
    224         * Returns the flag that determines whether <code>null</code> objects in the property path will
    225         * genenerate an <code>IllegalArgumentException</code> or not. If set to <code>true</code> then
    226         * if any objects in the property path leading up to the target property evaluate to
    227         * <code>null</code> then the <code>IllegalArgumentException</code> throw by
    228         * <code>PropertyUtils</code> will be logged but not rethrown.  If set to <code>false</code> then
    229         * if any objects in the property path leading up to the target property evaluate to
    230         * <code>null</code> then the <code>IllegalArgumentException</code> throw by
    231         * <code>PropertyUtils</code> will be logged and rethrown.
    232         *
    233         * @return The flag that determines whether <code>null</code> objects in the property path will
    234         * genenerate an <code>IllegalArgumentException</code> or not.
    235         */
    236        public boolean isIgnoreNull() {
    237            return ignoreNull;
    238        }
    239    }