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}