View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.beanutils;
19  
20  import org.apache.commons.collections.Closure;
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  
24  import java.lang.reflect.InvocationTargetException;
25  
26  
27  /**
28   * <p><code>Closure</code> that sets a property.</p>
29   * <p>
30   * An implementation of <code>org.apache.commons.collections.Closure</code> that updates
31   * a specified property on the object provided with a specified value.
32   * The <code>BeanPropertyValueChangeClosure</code> constructor takes two parameters which determine
33   * what property will be updated and with what value.
34   * <dl>
35   *    <dt>
36   *       <b><code>
37   *           <pre>public BeanPropertyValueChangeClosure( String propertyName, Object propertyValue )</pre>
38   *       </code></b>
39   *    </dt>
40   *    <dd>
41   *       Will create a <code>Closure</code> that will update an object by setting the property
42   *       specified by <code>propertyName</code> to the value specified by <code>propertyValue</code>.
43   *    </dd>
44   * </dl>
45   *
46   * <p/>
47   * <strong>Note:</strong> Property names can be a simple, nested, indexed, or mapped property as defined by
48   * <code>org.apache.commons.beanutils.PropertyUtils</code>.  If any object in the property path
49   * specified by <code>propertyName</code> is <code>null</code> then the outcome is based on the
50   * value of the <code>ignoreNull</code> attribute.
51   *
52   * <p/>
53   * A typical usage might look like:
54   * <code><pre>
55   * // create the closure
56   * BeanPropertyValueChangeClosure closure =
57   *    new BeanPropertyValueChangeClosure( "activeEmployee", Boolean.TRUE );
58   *
59   * // update the Collection
60   * CollectionUtils.forAllDo( peopleCollection, closure );
61   * </pre></code>
62   * <p/>
63   *
64   * This would take a <code>Collection</code> of person objects and update the
65   * <code>activeEmployee</code> property of each object in the <code>Collection</code> to
66   * <code>true</code>. Assuming...
67   * <ul>
68   *    <li>
69   *       The top level object in the <code>peopleCollection</code> is an object which represents a
70   *       person.
71   *    </li>
72   *    <li>
73   *       The person object has a <code>setActiveEmployee( boolean )</code> method which updates
74   *       the value for the object's <code>activeEmployee</code> property.
75   *    </li>
76   * </ul>
77   *
78   * @version $Id$
79   * @see org.apache.commons.beanutils.PropertyUtils
80   * @see org.apache.commons.collections.Closure
81   */
82  public class BeanPropertyValueChangeClosure implements Closure {
83  
84      /** For logging. */
85      private final Log log = LogFactory.getLog(this.getClass());
86  
87      /**
88       * The name of the property which will be updated when this <code>Closure</code> executes.
89       */
90      private String propertyName;
91  
92      /**
93       * The value that the property specified by <code>propertyName</code>
94       * will be updated to when this <code>Closure</code> executes.
95       */
96      private Object propertyValue;
97  
98      /**
99       * 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(final String propertyName, final 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(final String propertyName, final Object propertyValue, final 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(final Object object) {
164 
165         try {
166             PropertyUtils.setProperty(object, propertyName, propertyValue);
167         } catch (final 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                 final IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
174                 if (!BeanUtils.initCause(iae, e)) {
175                     log.error(errorMsg, e);
176                 }
177                 throw iae;
178             }
179         } catch (final IllegalAccessException e) {
180             final String errorMsg = "Unable to access the property provided.";
181             final IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
182             if (!BeanUtils.initCause(iae, e)) {
183                 log.error(errorMsg, e);
184             }
185             throw iae;
186         } catch (final InvocationTargetException e) {
187             final String errorMsg = "Exception occurred in property's getter";
188             final IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
189             if (!BeanUtils.initCause(iae, e)) {
190                 log.error(errorMsg, e);
191             }
192             throw iae;
193         } catch (final NoSuchMethodException e) {
194             final String errorMsg = "Property not found";
195             final 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 }