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 * https://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.beanutils2;
19
20 import java.lang.reflect.InvocationTargetException;
21 import java.util.function.Consumer;
22
23 import org.apache.commons.logging.Log;
24 import org.apache.commons.logging.LogFactory;
25
26 /**
27 * <p>
28 * {@code Closure} that sets a property.
29 * </p>
30 * <p>
31 * An implementation of {@link java.util.function.Consumer} that updates a specified property on the object provided with a specified value. The
32 * {@code BeanPropertyValueChangeClosure} constructor takes two parameters which determine what property will be updated and with what value.
33 * <dl>
34 * <dt>{@code public BeanPropertyValueChangeClosure( String propertyName, Object propertyValue )}</dt>
35 * <dd>Will create a {@code Closure} that will update an object by setting the property specified by
36 * {@code propertyName</code> to the value specified by <code>propertyValue}.
37 * </dd>
38 * </dl>
39 *
40 * <p>
41 * <strong>Note:</strong> Property names can be a simple, nested, indexed, or mapped property as defined by
42 * {@code org.apache.commons.beanutils2.PropertyUtils}. If any object in the property path
43 * specified by {@code propertyName</code> is <code>null} then the outcome is based on the
44 * value of the {@code ignoreNull} attribute.
45 * </p>
46 * <p>
47 * A typical usage might look like:
48 * </p>
49 * <pre>{@code
50 * // create the closure
51 * BeanPropertyValueChangeClosure closure =
52 * new BeanPropertyValueChangeClosure( "activeEmployee", Boolean.TRUE );
53 *
54 * // update the Collection
55 * CollectionUtils.forAllDo( peopleCollection, closure );
56 * }</pre>
57 *
58 * This would take a {@code Collection} of person objects and update the
59 * {@code activeEmployee</code> property of each object in the <code>Collection} to {@code true}. Assuming...
60 * <ul>
61 * <li>The top level object in the {@code peopleCollection} is an object which represents a person.</li>
62 * <li>The person object has a {@code setActiveEmployee( boolean )} method which updates the value for the object's {@code activeEmployee} property.</li>
63 * </ul>
64 *
65 * @param <T> The type of the input to the operation
66 * @param <V> The property value type.
67 * @see org.apache.commons.beanutils2.PropertyUtils
68 * @see java.util.function.Consumer
69 */
70 public class BeanPropertyValueChangeConsumer<T, V> implements Consumer<T> {
71
72 /** For logging. Each subclass gets its own log instance. */
73 private final Log log = LogFactory.getLog(this.getClass());
74
75 /**
76 * The name of the property which will be updated when this {@code Closure} executes.
77 */
78 private final String propertyName;
79
80 /**
81 * The value that the property specified by {@code propertyName} will be updated to when this {@code Closure} executes.
82 */
83 private final V propertyValue;
84
85 /**
86 * Determines whether {@code null} objects in the property path will generate an
87 * {@code IllegalArgumentException</code> or not. If set to <code>true} then if any objects
88 * in the property path leading up to the target property evaluate to {@code null} then the
89 * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged but
90 * not re-thrown. If set to {@code false} then if any objects in the property path leading
91 * up to the target property evaluate to {@code null} then the
92 * {@code IllegalArgumentException</code> throw by <code>PropertyUtils} will be logged and re-thrown.
93 */
94 private final boolean ignoreNull;
95
96 /**
97 * Constructor which takes the name of the property to be changed, the new value to set the property to, and assumes
98 * {@code ignoreNull</code> to be <code>false}.
99 *
100 * @param propertyName The name of the property that will be updated with the value specified by {@code propertyValue}.
101 * @param propertyValue The value that {@code propertyName} will be set to on the target object.
102 * @throws IllegalArgumentException If the propertyName provided is null or empty.
103 */
104 public BeanPropertyValueChangeConsumer(final String propertyName, final V propertyValue) {
105 this(propertyName, propertyValue, false);
106 }
107
108 /**
109 * Constructor which takes the name of the property to be changed, the new value to set the property to and a boolean which determines whether {@code null}
110 * objects in the property path will generate an {@code IllegalArgumentException} or not.
111 *
112 * @param propertyName The name of the property that will be updated with the value specified by {@code propertyValue}.
113 * @param propertyValue The value that {@code propertyName} will be set to on the target object.
114 * @param ignoreNull Determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
115 * @throws IllegalArgumentException If the propertyName provided is null or empty.
116 */
117 public BeanPropertyValueChangeConsumer(final String propertyName, final V propertyValue, final boolean ignoreNull) {
118 if (propertyName == null || propertyName.isEmpty()) {
119 throw new IllegalArgumentException("propertyName cannot be null or empty");
120 }
121 this.propertyName = propertyName;
122 this.propertyValue = propertyValue;
123 this.ignoreNull = ignoreNull;
124 }
125
126 /**
127 * Updates the target object provided using the property update criteria provided when this {@code BeanPropertyValueChangeClosure} was constructed. If any
128 * object in the property path leading up to the target property is {@code null} then the outcome will be based on the value of the
129 * {@code ignoreNull</code> attribute. By default, <code>ignoreNull} is
130 * {@code false</code> and would result in an <code>IllegalArgumentException} if an object
131 * in the property path leading up to the target property is {@code null}.
132 *
133 * @param object The object to be updated.
134 * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
135 * NoSuchMethodException is thrown when trying to access the property specified on the object
136 * provided. Or if an object in the property path provided is {@code null} and
137 * {@code ignoreNull</code> is set to <code>false}.
138 */
139 @Override
140 public void accept(final Object object) {
141
142 try {
143 PropertyUtils.setProperty(object, propertyName, propertyValue);
144 } catch (final IllegalArgumentException e) {
145 final String errorMsg = "Unable to execute Closure. Null value encountered in property path...";
146
147 if (!ignoreNull) {
148 throw new IllegalArgumentException(errorMsg, e);
149 }
150 log.warn(errorMsg, e);
151 } catch (final IllegalAccessException e) {
152 final String errorMsg = "Unable to access the property provided.";
153 throw new IllegalArgumentException(errorMsg, e);
154 } catch (final InvocationTargetException e) {
155 final String errorMsg = "Exception occurred in property's getter";
156 throw new IllegalArgumentException(errorMsg, e);
157 } catch (final NoSuchMethodException e) {
158 final String errorMsg = "Property not found";
159 throw new IllegalArgumentException(errorMsg, e);
160 }
161 }
162
163 /**
164 * Returns the name of the property which will be updated when this {@code Closure} executes.
165 *
166 * @return The name of the property which will be updated when this {@code Closure} executes.
167 */
168 public String getPropertyName() {
169 return propertyName;
170 }
171
172 /**
173 * Returns the value that the property specified by {@code propertyName} will be updated to when this {@code Closure} executes.
174 *
175 * @return The value that the property specified by {@code propertyName} will be updated to when this {@code Closure} executes.
176 */
177 public V getPropertyValue() {
178 return propertyValue;
179 }
180
181 /**
182 * Returns the flag that determines whether {@code null} objects in the property path will generate an
183 * {@code IllegalArgumentException</code> or not. If set to <code>true} then
184 * if any objects in the property path leading up to the target property evaluate to
185 * {@code null</code> then the <code>IllegalArgumentException} throw by
186 * {@code PropertyUtils</code> will be logged but not re-thrown. If set to <code>false} then
187 * if any objects in the property path leading up to the target property evaluate to
188 * {@code null</code> then the <code>IllegalArgumentException} throw by {@code PropertyUtils} will be logged and re-thrown.
189 *
190 * @return The flag that determines whether {@code null} objects in the property path will generate an {@code IllegalArgumentException} or not.
191 */
192 public boolean isIgnoreNull() {
193 return ignoreNull;
194 }
195 }