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 *      https://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}