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: * <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><code>
036: * <pre>public BeanPropertyValueChangeClosure( String propertyName, Object propertyValue )</pre>
037: * </code></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: *
051: * <p/>
052: * A typical usage might look like:
053: * <code><pre>
054: * // create the closure
055: * BeanPropertyValueChangeClosure closure =
056: * new BeanPropertyValueChangeClosure( "activeEmployee", Boolean.TRUE );
057: *
058: * // update the Collection
059: * CollectionUtils.forAllDo( peopleCollection, closure );
060: * </pre></code>
061: * <p/>
062: *
063: * This would take a <code>Collection</code> of person objects and update the
064: * <code>activeEmployee</code> property of each object in the <code>Collection</code> to
065: * <code>true</code>. Assuming...
066: * <ul>
067: * <li>
068: * The top level object in the <code>peopleCollection</code> is an object which represents a
069: * person.
070: * </li>
071: * <li>
072: * The person object has a <code>setActiveEmployee( boolean )</code> method which updates
073: * the value for the object's <code>activeEmployee</code> property.
074: * </li>
075: * </ul>
076: *
077: * @author Norm Deane
078: * @see org.apache.commons.beanutils.PropertyUtils
079: * @see org.apache.commons.collections.Closure
080: */
081: public 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 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 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 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(String propertyName,
120: 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,
138: Object propertyValue, boolean ignoreNull) {
139: super ();
140:
141: if ((propertyName != null) && (propertyName.length() > 0)) {
142: this .propertyName = propertyName;
143: this .propertyValue = propertyValue;
144: this .ignoreNull = ignoreNull;
145: } else {
146: throw new IllegalArgumentException(
147: "propertyName cannot be null or empty");
148: }
149: }
150:
151: /**
152: * Updates the target object provided using the property update criteria provided when this
153: * <code>BeanPropertyValueChangeClosure</code> was constructed. If any object in the property
154: * path leading up to the target property is <code>null</code> then the outcome will be based on
155: * the value of the <code>ignoreNull</code> attribute. By default, <code>ignoreNull</code> is
156: * <code>false</code> and would result in an <code>IllegalArgumentException</code> if an object
157: * in the property path leading up to the target property is <code>null</code>.
158: *
159: * @param object The object to be updated.
160: * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
161: * NoSuchMethodException is thrown when trying to access the property specified on the object
162: * provided. Or if an object in the property path provided is <code>null</code> and
163: * <code>ignoreNull</code> is set to <code>false</code>.
164: */
165: public void execute(Object object) {
166:
167: try {
168: PropertyUtils.setProperty(object, propertyName,
169: propertyValue);
170: } catch (IllegalArgumentException e) {
171: final String errorMsg = "Unable to execute Closure. Null value encountered in property path...";
172:
173: if (ignoreNull) {
174: log.warn("WARNING: " + errorMsg, e);
175: } else {
176: log.error("ERROR: " + errorMsg, e);
177: throw e;
178: }
179: } catch (IllegalAccessException e) {
180: final String errorMsg = "Unable to access the property provided.";
181: log.error(errorMsg, e);
182: throw new IllegalArgumentException(errorMsg);
183: } catch (InvocationTargetException e) {
184: final String errorMsg = "Exception occurred in property's getter";
185: log.error(errorMsg, e);
186: throw new IllegalArgumentException(errorMsg);
187: } catch (NoSuchMethodException e) {
188: final String errorMsg = "Property not found";
189: log.error(errorMsg, e);
190: throw new IllegalArgumentException(errorMsg);
191: }
192: }
193:
194: /**
195: * Returns the name of the property which will be updated when this <code>Closure</code> executes.
196: *
197: * @return The name of the property which will be updated when this <code>Closure</code> executes.
198: */
199: public String getPropertyName() {
200: return propertyName;
201: }
202:
203: /**
204: * Returns the value that the property specified by <code>propertyName</code>
205: * will be updated to when this <code>Closure</code> executes.
206: *
207: * @return The value that the property specified by <code>propertyName</code>
208: * will be updated to when this <code>Closure</code> executes.
209: */
210: public Object getPropertyValue() {
211: return propertyValue;
212: }
213:
214: /**
215: * Returns the flag that determines whether <code>null</code> objects in the property path will
216: * genenerate an <code>IllegalArgumentException</code> or not. If set to <code>true</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 but not rethrown. If set to <code>false</code> then
220: * if any objects in the property path leading up to the target property evaluate to
221: * <code>null</code> then the <code>IllegalArgumentException</code> throw by
222: * <code>PropertyUtils</code> will be logged and rethrown.
223: *
224: * @return The flag that determines whether <code>null</code> objects in the property path will
225: * genenerate an <code>IllegalArgumentException</code> or not.
226: */
227: public boolean isIgnoreNull() {
228: return ignoreNull;
229: }
230: }
|