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