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   * @author Norm Deane
    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(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                 log.error("ERROR: " + errorMsg, e);
    174                 throw e;
    175             }
    176         } catch (IllegalAccessException e) {
    177             final String errorMsg = "Unable to access the property provided.";
    178             log.error(errorMsg, e);
    179             throw new IllegalArgumentException(errorMsg);
    180         } catch (InvocationTargetException e) {
    181             final String errorMsg = "Exception occurred in property's getter";
    182             log.error(errorMsg, e);
    183             throw new IllegalArgumentException(errorMsg);
    184         } catch (NoSuchMethodException e) {
    185             final String errorMsg = "Property not found";
    186             log.error(errorMsg, e);
    187             throw new IllegalArgumentException(errorMsg);
    188         }
    189     }
    190
    191     /***
    192      * Returns the name of the property which will be updated when this <code>Closure</code> executes.
    193      *
    194      * @return The name of the property which will be updated when this <code>Closure</code> executes.
    195      */
    196     public String getPropertyName() {
    197         return propertyName;
    198     }
    199
    200     /***
    201      * Returns the value that the property specified by <code>propertyName</code>
    202      * will be updated to when this <code>Closure</code> executes.
    203      *
    204      * @return The value that the property specified by <code>propertyName</code>
    205      * will be updated to when this <code>Closure</code> executes.
    206      */
    207     public Object getPropertyValue() {
    208         return propertyValue;
    209     }
    210
    211     /***
    212      * Returns the flag that determines whether <code>null</code> objects in the property path will
    213      * genenerate an <code>IllegalArgumentException</code> or not. If set to <code>true</code> then
    214      * if any objects in the property path leading up to the target property evaluate to
    215      * <code>null</code> then the <code>IllegalArgumentException</code> throw by
    216      * <code>PropertyUtils</code> will be logged but not rethrown.  If set to <code>false</code> then
    217      * if any objects in the property path leading up to the target property evaluate to
    218      * <code>null</code> then the <code>IllegalArgumentException</code> throw by
    219      * <code>PropertyUtils</code> will be logged and rethrown.
    220      *
    221      * @return The flag that determines whether <code>null</code> objects in the property path will
    222      * genenerate an <code>IllegalArgumentException</code> or not.
    223      */
    224     public boolean isIgnoreNull() {
    225         return ignoreNull;
    226     }
    227 }