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    }