Question

I'm using BeanutilsBean.describe() method to grab data for an audit trail. It works just fine - that's not the question!

However there are certain attributes which do not need to be audited. These are recorded as strings in a list. So for example if you had an attribute DomainObject.myValue the list would contain "myValue" so that the result of the call made to DomainObject.getMyValue() is not included in the audit trail.

Currently the code gets all the attributes from BeanutilsBean.describe() and then iterates through them to discard the unwanted ones.

What I want to be able to do is configure the BeanUtilsBean instance with that list of attribute names to exclude so that it doesn't ever call those methods. So in my example, DomainObject.getMyValue() is never called at all.

I can't work out if that is possible from looking through the API or code.

Was it helpful?

Solution 2

No, filtering as you're doing is the simplest way. The source of that method makes no provision for any kind of configurable filtering. There are really only two options that I can see while using BeanUtilsBean:

  • Call describe() and then filter the results (what you're doing now). You could wrap this up in a static method with a signature like public static Map<String, String> describeBean(Object bean, String... excludedProperties)
  • Roll your own utility method (signature like the above) that copies the implementation of describe() but does the filtering during the initial iteration. This would be more performant as it wouldn't require an additional pass through the Map.

OTHER TIPS

This is the code I used to solve this.

It's a slightly amended copy of BeanUtilsBean.describe() which doesn't call the excluded attribute getters; it's the "Roll your own" option from ach's answer (the first option has been in use in live code for a couple of years but it's never sat right with me!).

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.MethodUtils;

public class BeanUtilsBeanExtensions {

    private static final BeanUtilsBean BEAN_UTILS_BEAN = BeanUtilsBean
                    .getInstance();

    public BeanUtilsBeanExtensions() {
    }

    /**
     * Extends BeanUtilsBean.describe() so that it can be given a list of
     * attributes to exclude. This avoids calling methods which might derive
     * data which don't happen to be populated when the describe() call is made
     * (and therefore could throw exceptions) as well as being more efficient
     * than describing everything then discarding attributes which aren't
     * required.
     *
     * @param bean
     *            See BeanUtilsBean.describe()
     * @param excludedAttributeNames
     *            the attribute names which should not be described.
     * @return See BeanUtilsBean.describe()
     */
    public Map<String, String> describe(Object bean,
                    Set<String> excludedAttributeNames)
                    throws IllegalAccessException,
                    InvocationTargetException, NoSuchMethodException {

        // This method is mostly just a copy/paste from BeanUtilsBean.describe()
        // The only changes are:
        // - Removal of reference to the (private) logger
        // - Addition of Reference to a BeanUtilsBean instance
        // - Addition of calls to excludedAttributeNames.contains(name)
        // - Use of generics on the Collections
        // - Calling of a copy of PropertyUtilsBean.getReadMethod()

        if (bean == null) {
            return (new java.util.HashMap<String, String>());
        }

        Map<String, String> description = new HashMap<String, String>();
        if (bean instanceof DynaBean) {
            DynaProperty[] descriptors = ((DynaBean) bean).getDynaClass()
                            .getDynaProperties();
            for (int i = 0; i < descriptors.length; i++) {
                String name = descriptors[i].getName();
                if (!excludedAttributeNames.contains(name)) {
                    description.put(name,
                                    BEAN_UTILS_BEAN.getProperty(bean, name));
                }
            }
        }
        else {
            PropertyDescriptor[] descriptors = BEAN_UTILS_BEAN
                            .getPropertyUtils().getPropertyDescriptors(bean);
            Class<? extends Object> clazz = bean.getClass();
            for (int i = 0; i < descriptors.length; i++) {
                String name = descriptors[i].getName();
                if (!excludedAttributeNames.contains(name)
                                && getReadMethod(clazz, descriptors[i]) != null) {
                    description.put(name,
                                    BEAN_UTILS_BEAN.getProperty(bean, name));
                }
            }
        }
        return description;
    }

    /*
     * Copy of PropertyUtilsBean.getReadMethod() since that is package-private.
     */
    private Method getReadMethod(Class<? extends Object> clazz,
                    PropertyDescriptor descriptor) {
        return MethodUtils.getAccessibleMethod(clazz,
                        descriptor.getReadMethod());
    }

}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top