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