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 java.lang.reflect.InvocationTargetException;
021
022import org.apache.commons.collections.Predicate;
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025
026/**
027 * <p><code>Predicate</code> that evaluates a property value against a specified value.</p>
028 * <p>
029 * An implementation of <code>org.apache.commons.collections.Predicate</code> that evaluates a
030 * property value on the object provided against a specified value and returns <code>true</code>
031 * if equal; <code>false</code> otherwise.
032 * The <code>BeanPropertyValueEqualsPredicate</code> constructor takes two parameters which
033 * determine what property will be evaluated on the target object and what its expected value should
034 * be.
035 * </p>
036 * <dl>
037 *    <dt>
038 *       <strong><code>
039 *           public BeanPropertyValueEqualsPredicate(String propertyName, Object propertyValue)
040 *       </code></strong>
041 *    </dt>
042 *    <dd>
043 *       Will create a <code>Predicate</code> that will evaluate the target object and return
044 *       <code>true</code> if the property specified by <code>propertyName</code> has a value which
045 *       is equal to the the value specified by <code>propertyValue</code>. Or return
046 *       <code>false</code> otherwise.
047 *    </dd>
048 * </dl>
049 * <p>
050 * <strong>Note:</strong> Property names can be a simple, nested, indexed, or mapped property as defined by
051 * <code>org.apache.commons.beanutils.PropertyUtils</code>.  If any object in the property path
052 * specified by <code>propertyName</code> is <code>null</code> then the outcome is based on the
053 * value of the <code>ignoreNull</code> attribute.
054 * </p>
055 * <p>
056 * A typical usage might look like:
057 * </p>
058 * <pre>
059 * // create the closure
060 * BeanPropertyValueEqualsPredicate predicate =
061 *    new BeanPropertyValueEqualsPredicate( "activeEmployee", Boolean.FALSE );
062 *
063 * // filter the Collection
064 * CollectionUtils.filter( peopleCollection, predicate );
065 * </pre>
066 * <p>
067 * This would take a <code>Collection</code> of person objects and filter out any people whose
068 * <code>activeEmployee</code> property is <code>false</code>. Assuming...
069 * </p>
070 * <ul>
071 *    <li>
072 *       The top level object in the <code>peeopleCollection</code> is an object which represents a
073 *       person.
074 *    </li>
075 *    <li>
076 *       The person object has a <code>getActiveEmployee()</code> method which returns
077 *       the boolean value for the object's <code>activeEmployee</code> property.
078 *    </li>
079 * </ul>
080 * <p>
081 * Another typical usage might look like:
082 * </p>
083 * <pre>
084 * // create the closure
085 * BeanPropertyValueEqualsPredicate predicate =
086 *    new BeanPropertyValueEqualsPredicate( "personId", "456-12-1234" );
087 *
088 * // search the Collection
089 * CollectionUtils.find( peopleCollection, predicate );
090 * </pre>
091 * <p>
092 * This would search a <code>Collection</code> of person objects and return the first object whose
093 * </p>
094 * <code>personId</code> property value equals <code>456-12-1234</code>. Assuming...
095 * <ul>
096 *    <li>
097 *       The top level object in the <code>peeopleCollection</code> is an object which represents a
098 *       person.
099 *    </li>
100 *    <li>
101 *       The person object has a <code>getPersonId()</code> method which returns
102 *       the value for the object's <code>personId</code> property.
103 *    </li>
104 * </ul>
105 *
106 * @see org.apache.commons.beanutils.PropertyUtils
107 * @see org.apache.commons.collections.Predicate
108 */
109public class BeanPropertyValueEqualsPredicate implements Predicate {
110
111    /** For logging. */
112    private final Log log = LogFactory.getLog(this.getClass());
113
114    /**
115     * The name of the property which will be evaluated when this <code>Predicate</code> is executed.
116     */
117    private final String propertyName;
118
119    /**
120     * The value that the property specified by <code>propertyName</code>
121     * will be compared to when this <code>Predicate</code> executes.
122     */
123    private final Object propertyValue;
124
125    /**
126     * <p>Should <code>null</code> objects in the property path be ignored?</p>
127     * <p>
128     * Determines whether <code>null</code> objects in the property path will genenerate an
129     * <code>IllegalArgumentException</code> or not. If set to <code>true</code> then if any objects
130     * in the property path evaluate to <code>null</code> then the
131     * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged but
132     * not rethrown and <code>false</code> will be returned.  If set to <code>false</code> then if
133     * any objects in the property path evaluate to <code>null</code> then the
134     * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged and
135     * rethrown.
136     * </p>
137     */
138    private final boolean ignoreNull;
139
140    /**
141     * Constructor which takes the name of the property, its expected value to be used in evaluation,
142     * and assumes <code>ignoreNull</code> to be <code>false</code>.
143     *
144     * @param propertyName The name of the property that will be evaluated against the expected value.
145     * @param propertyValue The value to use in object evaluation.
146     * @throws IllegalArgumentException If the property name provided is null or empty.
147     */
148    public BeanPropertyValueEqualsPredicate(final String propertyName, final Object propertyValue) {
149        this(propertyName, propertyValue, false);
150    }
151
152    /**
153     * Constructor which takes the name of the property, its expected value
154     * to be used in evaluation, and a boolean which determines whether <code>null</code> objects in
155     * the property path will genenerate an <code>IllegalArgumentException</code> or not.
156     *
157     * @param propertyName The name of the property that will be evaluated against the expected value.
158     * @param propertyValue The value to use in object evaluation.
159     * @param ignoreNull Determines whether <code>null</code> objects in the property path will
160     * genenerate an <code>IllegalArgumentException</code> or not.
161     * @throws IllegalArgumentException If the property name provided is null or empty.
162     */
163    public BeanPropertyValueEqualsPredicate(final String propertyName, final Object propertyValue, final boolean ignoreNull) {
164        if (propertyName == null || propertyName.length() <= 0) {
165            throw new IllegalArgumentException("propertyName cannot be null or empty");
166        }
167        this.propertyName = propertyName;
168        this.propertyValue = propertyValue;
169        this.ignoreNull = ignoreNull;
170    }
171
172    /**
173     * Evaulates the object provided against the criteria specified when this
174     * <code>BeanPropertyValueEqualsPredicate</code> was constructed.  Equality is based on
175     * either reference or logical equality as defined by the property object's equals method. If
176     * any object in the property path leading up to the target property is <code>null</code> then
177     * the outcome will be based on the value of the <code>ignoreNull</code> attribute. By default,
178     * <code>ignoreNull</code> is <code>false</code> and would result in an
179     * <code>IllegalArgumentException</code> if an object in the property path leading up to the
180     * target property is <code>null</code>.
181     *
182     * @param object The object to be evaluated.
183     * @return True if the object provided meets all the criteria for this <code>Predicate</code>;
184     * false otherwise.
185     * @throws IllegalArgumentException If an IllegalAccessException, InvocationTargetException, or
186     * NoSuchMethodException is thrown when trying to access the property specified on the object
187     * provided. Or if an object in the property path provided is <code>null</code> and
188     * <code>ignoreNull</code> is set to <code>false</code>.
189     */
190    @Override
191    public boolean evaluate(final Object object) {
192
193        boolean evaluation = false;
194
195        try {
196            evaluation = evaluateValue(propertyValue,
197                    PropertyUtils.getProperty(object, propertyName));
198        } catch (final IllegalArgumentException e) {
199            final String errorMsg = "Problem during evaluation. Null value encountered in property path...";
200
201            if (!ignoreNull) {
202                throw new IllegalArgumentException(errorMsg, e);
203            }
204            log.warn("WARNING: " + errorMsg + e);
205        } catch (final IllegalAccessException e) {
206            final String errorMsg = "Unable to access the property provided.";
207            throw new IllegalArgumentException(errorMsg, e);
208        } catch (final InvocationTargetException e) {
209            final String errorMsg = "Exception occurred in property's getter";
210            throw new IllegalArgumentException(errorMsg, e);
211        } catch (final NoSuchMethodException e) {
212            final String errorMsg = "Property not found.";
213            throw new IllegalArgumentException(errorMsg, e);
214        }
215
216        return evaluation;
217    }
218
219    /**
220     * Utility method which evaluates whether the actual property value equals the expected property
221     * value.
222     *
223     * @param expected The expected value.
224     * @param actual The actual value.
225     * @return True if they are equal; false otherwise.
226     */
227    protected boolean evaluateValue(final Object expected, final Object actual) {
228        return expected == actual || expected != null && expected.equals(actual);
229    }
230
231    /**
232     * Returns the name of the property which will be evaluated when this <code>Predicate</code> is
233     * executed.
234     *
235     * @return The name of the property which will be evaluated when this <code>Predicate</code> is
236     * executed.
237     */
238    public String getPropertyName() {
239        return propertyName;
240    }
241
242    /**
243     * Returns the value that the property specified by <code>propertyName</code> will be compared to
244     * when this <code>Predicate</code> executes.
245     *
246     * @return The value that the property specified by <code>propertyName</code> will be compared to
247     * when this <code>Predicate</code> executes.
248     */
249    public Object getPropertyValue() {
250        return propertyValue;
251    }
252
253    /**
254     * Returns the flag which determines whether <code>null</code> objects in the property path will
255     * genenerate an <code>IllegalArgumentException</code> or not. If set to <code>true</code> then
256     * if any objects in the property path evaluate to <code>null</code> then the
257     * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged but
258     * not rethrown and <code>false</code> will be returned.  If set to <code>false</code> then if
259     * any objects in the property path evaluate to <code>null</code> then the
260     * <code>IllegalArgumentException</code> throw by <code>PropertyUtils</code> will be logged and
261     * rethrown.
262     *
263     * @return The flag which determines whether <code>null</code> objects in the property path will
264     * genenerate an <code>IllegalArgumentException</code> or not.
265     */
266    public boolean isIgnoreNull() {
267        return ignoreNull;
268    }
269}