    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}