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 org.apache.commons.collections.Closure; 021import org.apache.commons.logging.Log; 022import org.apache.commons.logging.LogFactory; 023 024import 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 * <code>public BeanPropertyValueChangeClosure( String propertyName, Object propertyValue )</code> 037 * </dt> 038 * <dd> 039 * Will create a <code>Closure</code> that will update an object by setting the property 040 * specified by <code>propertyName</code> to the value specified by <code>propertyValue</code>. 041 * </dd> 042 * </dl> 043 * 044 * <p> 045 * <strong>Note:</strong> Property names can be a simple, nested, indexed, or mapped property as defined by 046 * <code>org.apache.commons.beanutils.PropertyUtils</code>. If any object in the property path 047 * specified by <code>propertyName</code> is <code>null</code> then the outcome is based on the 048 * value of the <code>ignoreNull</code> attribute. 049 * </p> 050 * <p> 051 * A typical usage might look like: 052 * </p> 053 * <pre><code> 054 * // create the closure 055 * BeanPropertyValueChangeClosure closure = 056 * new BeanPropertyValueChangeClosure( "activeEmployee", Boolean.TRUE ); 057 * 058 * // update the Collection 059 * CollectionUtils.forAllDo( peopleCollection, closure ); 060 * </code></pre> 061 * 062 * This would take a <code>Collection</code> of person objects and update the 063 * <code>activeEmployee</code> property of each object in the <code>Collection</code> to 064 * <code>true</code>. Assuming... 065 * <ul> 066 * <li> 067 * The top level object in the <code>peopleCollection</code> is an object which represents a 068 * person. 069 * </li> 070 * <li> 071 * The person object has a <code>setActiveEmployee( boolean )</code> method which updates 072 * the value for the object's <code>activeEmployee</code> property. 073 * </li> 074 * </ul> 075 * 076 * @version $Id$ 077 * @see org.apache.commons.beanutils.PropertyUtils 078 * @see org.apache.commons.collections.Closure 079 */ 080public class BeanPropertyValueChangeClosure implements Closure { 081 082 /** For logging. */ 083 private final Log log = LogFactory.getLog(this.getClass()); 084 085 /** 086 * The name of the property which will be updated when this <code>Closure</code> executes. 087 */ 088 private String propertyName; 089 090 /** 091 * The value that the property specified by <code>propertyName</code> 092 * will be updated to when this <code>Closure</code> executes. 093 */ 094 private Object propertyValue; 095 096 /** 097 * Determines whether <code>null</code> objects in the property path will genenerate an 098 * <code>IllegalArgumentException</code> or not. If set to <code>true</code> then if any objects 099 * in the property path leading up to the target property evaluate to <code>null</code> then the 100 * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged but 101 * not rethrown. If set to <code>false</code> then if any objects in the property path leading 102 * up to the target property evaluate to <code>null</code> then the 103 * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged and 104 * rethrown. 105 */ 106 private boolean ignoreNull; 107 108 /** 109 * Constructor which takes the name of the property to be changed, the new value to set 110 * the property to, and assumes <code>ignoreNull</code> to be <code>false</code>. 111 * 112 * @param propertyName The name of the property that will be updated with the value specified by 113 * <code>propertyValue</code>. 114 * @param propertyValue The value that <code>propertyName</code> will be set to on the target 115 * object. 116 * @throws IllegalArgumentException If the propertyName provided is null or empty. 117 */ 118 public BeanPropertyValueChangeClosure(final String propertyName, final Object propertyValue) { 119 this(propertyName, propertyValue, false); 120 } 121 122 /** 123 * Constructor which takes the name of the property to be changed, the new value to set 124 * the property to and a boolean which determines whether <code>null</code> objects in the 125 * property path will genenerate an <code>IllegalArgumentException</code> or not. 126 * 127 * @param propertyName The name of the property that will be updated with the value specified by 128 * <code>propertyValue</code>. 129 * @param propertyValue The value that <code>propertyName</code> will be set to on the target 130 * object. 131 * @param ignoreNull Determines whether <code>null</code> objects in the property path will 132 * genenerate an <code>IllegalArgumentException</code> or not. 133 * @throws IllegalArgumentException If the propertyName provided is null or empty. 134 */ 135 public BeanPropertyValueChangeClosure(final String propertyName, final Object propertyValue, final boolean ignoreNull) { 136 super(); 137 138 if ((propertyName != null) && (propertyName.length() > 0)) { 139 this.propertyName = propertyName; 140 this.propertyValue = propertyValue; 141 this.ignoreNull = ignoreNull; 142 } else { 143 throw new IllegalArgumentException("propertyName cannot be null or empty"); 144 } 145 } 146 147 /** 148 * Updates the target object provided using the property update criteria provided when this 149 * <code>BeanPropertyValueChangeClosure</code> was constructed. If any object in the property 150 * path leading up to the target property is <code>null</code> then the outcome will be based on 151 * the value of the <code>ignoreNull</code> attribute. By default, <code>ignoreNull</code> is 152 * <code>false</code> and would result in an <code>IllegalArgumentException</code> if an object 153 * in the property path leading up to the target property is <code>null</code>. 154 * 155 * @param object The object to be updated. 156 * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or 157 * NoSuchMethodException is thrown when trying to access the property specified on the object 158 * provided. Or if an object in the property path provided is <code>null</code> and 159 * <code>ignoreNull</code> is set to <code>false</code>. 160 */ 161 public void execute(final Object object) { 162 163 try { 164 PropertyUtils.setProperty(object, propertyName, propertyValue); 165 } catch (final IllegalArgumentException e) { 166 final String errorMsg = "Unable to execute Closure. Null value encountered in property path..."; 167 168 if (ignoreNull) { 169 log.warn("WARNING: " + errorMsg + e); 170 } else { 171 final IllegalArgumentException iae = new IllegalArgumentException(errorMsg); 172 if (!BeanUtils.initCause(iae, e)) { 173 log.error(errorMsg, e); 174 } 175 throw iae; 176 } 177 } catch (final IllegalAccessException e) { 178 final String errorMsg = "Unable to access the property provided."; 179 final IllegalArgumentException iae = new IllegalArgumentException(errorMsg); 180 if (!BeanUtils.initCause(iae, e)) { 181 log.error(errorMsg, e); 182 } 183 throw iae; 184 } catch (final InvocationTargetException e) { 185 final String errorMsg = "Exception occurred in property's getter"; 186 final IllegalArgumentException iae = new IllegalArgumentException(errorMsg); 187 if (!BeanUtils.initCause(iae, e)) { 188 log.error(errorMsg, e); 189 } 190 throw iae; 191 } catch (final NoSuchMethodException e) { 192 final String errorMsg = "Property not found"; 193 final IllegalArgumentException iae = new IllegalArgumentException(errorMsg); 194 if (!BeanUtils.initCause(iae, e)) { 195 log.error(errorMsg, e); 196 } 197 throw iae; 198 } 199 } 200 201 /** 202 * Returns the name of the property which will be updated when this <code>Closure</code> executes. 203 * 204 * @return The name of the property which will be updated when this <code>Closure</code> executes. 205 */ 206 public String getPropertyName() { 207 return propertyName; 208 } 209 210 /** 211 * Returns the value that the property specified by <code>propertyName</code> 212 * will be updated to when this <code>Closure</code> executes. 213 * 214 * @return The value that the property specified by <code>propertyName</code> 215 * will be updated to when this <code>Closure</code> executes. 216 */ 217 public Object getPropertyValue() { 218 return propertyValue; 219 } 220 221 /** 222 * Returns the flag that determines whether <code>null</code> objects in the property path will 223 * genenerate an <code>IllegalArgumentException</code> or not. If set to <code>true</code> then 224 * if any objects in the property path leading up to the target property evaluate to 225 * <code>null</code> then the <code>IllegalArgumentException</code> throw by 226 * <code>PropertyUtils</code> will be logged but not rethrown. If set to <code>false</code> then 227 * if any objects in the property path leading up to the target property evaluate to 228 * <code>null</code> then the <code>IllegalArgumentException</code> throw by 229 * <code>PropertyUtils</code> will be logged and rethrown. 230 * 231 * @return The flag that determines whether <code>null</code> objects in the property path will 232 * genenerate an <code>IllegalArgumentException</code> or not. 233 */ 234 public boolean isIgnoreNull() { 235 return ignoreNull; 236 } 237}