fc09ee
From fd220124926ff0c6e760d5331eb62e09b20b46a9 Mon Sep 17 00:00:00 2001
fc09ee
From: Marian Koncek <mkoncek@redhat.com>
fc09ee
Date: Wed, 18 Dec 2019 16:01:16 +0100
fc09ee
Subject: [PATCH] CVE-2014-0114
fc09ee
fc09ee
---
fc09ee
 .../beanutils/BeanIntrospectionData.java      | 154 +++++++++++++
fc09ee
 .../commons/beanutils/BeanIntrospector.java   |  53 +++++
fc09ee
 .../beanutils/DefaultBeanIntrospector.java    | 181 +++++++++++++++
fc09ee
 .../DefaultIntrospectionContext.java          | 108 +++++++++
fc09ee
 .../beanutils/IntrospectionContext.java       |  99 ++++++++
fc09ee
 .../commons/beanutils/PropertyUtils.java      |  15 ++
fc09ee
 .../commons/beanutils/PropertyUtilsBean.java  | 213 +++++++++---------
fc09ee
 .../SuppressPropertiesBeanIntrospector.java   |  75 ++++++
fc09ee
 ...essPropertiesBeanIntrospectorTestCase.java | 127 +++++++++++
fc09ee
 .../beanutils/bugs/Jira463TestCase.java       |  48 ++++
fc09ee
 .../beanutils/bugs/Jira520TestCase.java       |  39 ++++
fc09ee
 11 files changed, 1000 insertions(+), 112 deletions(-)
fc09ee
 create mode 100644 src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java
fc09ee
 create mode 100644 src/main/java/org/apache/commons/beanutils/BeanIntrospector.java
fc09ee
 create mode 100644 src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java
fc09ee
 create mode 100644 src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java
fc09ee
 create mode 100644 src/main/java/org/apache/commons/beanutils/IntrospectionContext.java
fc09ee
 create mode 100644 src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
fc09ee
 create mode 100644 src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java
fc09ee
 create mode 100644 src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java
fc09ee
 create mode 100644 src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java
fc09ee
fc09ee
diff --git a/src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java b/src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java
fc09ee
new file mode 100644
fc09ee
index 0000000..1ab1d1e
fc09ee
--- /dev/null
fc09ee
+++ b/src/main/java/org/apache/commons/beanutils/BeanIntrospectionData.java
fc09ee
@@ -0,0 +1,154 @@
fc09ee
+/*
fc09ee
+ * Licensed to the Apache Software Foundation (ASF) under one or more
fc09ee
+ * contributor license agreements.  See the NOTICE file distributed with
fc09ee
+ * this work for additional information regarding copyright ownership.
fc09ee
+ * The ASF licenses this file to You under the Apache License, Version 2.0
fc09ee
+ * (the "License"); you may not use this file except in compliance with
fc09ee
+ * the License.  You may obtain a copy of the License at
fc09ee
+ *
fc09ee
+ *      http://www.apache.org/licenses/LICENSE-2.0
fc09ee
+ *
fc09ee
+ * Unless required by applicable law or agreed to in writing, software
fc09ee
+ * distributed under the License is distributed on an "AS IS" BASIS,
fc09ee
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
fc09ee
+ * See the License for the specific language governing permissions and
fc09ee
+ * limitations under the License.
fc09ee
+ */
fc09ee
+package org.apache.commons.beanutils;
fc09ee
+
fc09ee
+import java.beans.IntrospectionException;
fc09ee
+import java.beans.PropertyDescriptor;
fc09ee
+import java.lang.reflect.Method;
fc09ee
+import java.util.HashMap;
fc09ee
+import java.util.Map;
fc09ee
+
fc09ee
+/**
fc09ee
+ * 

fc09ee
+ * An internally used helper class for storing introspection information about a bean
fc09ee
+ * class.
fc09ee
+ * 

fc09ee
+ * 

fc09ee
+ * This class is used by {@link PropertyUtilsBean}. When accessing bean properties via
fc09ee
+ * reflection information about the properties available and their types and access
fc09ee
+ * methods must be present. {@code PropertyUtilsBean} stores this information in a cache
fc09ee
+ * so that it can be accessed quickly. The cache stores instances of this class.
fc09ee
+ * 

fc09ee
+ * 

fc09ee
+ * This class mainly stores information about the properties of a bean class. Per default,
fc09ee
+ * this is contained in {@code PropertyDescriptor} objects. Some additional information
fc09ee
+ * required by the {@code BeanUtils} library is also stored here.
fc09ee
+ * 

fc09ee
+ *
fc09ee
+ * @version $Id$
fc09ee
+ * @since 1.9.1
fc09ee
+ */
fc09ee
+class BeanIntrospectionData {
fc09ee
+    /** An array with property descriptors for the managed bean class. */
fc09ee
+    private final PropertyDescriptor[] descriptors;
fc09ee
+
fc09ee
+    /** A map for remembering the write method names for properties. */
fc09ee
+    private final Map writeMethodNames;
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Creates a new instance of {@code BeanIntrospectionData} and initializes its
fc09ee
+     * completely.
fc09ee
+     *
fc09ee
+     * @param descs the array with the descriptors of the available properties
fc09ee
+     */
fc09ee
+    public BeanIntrospectionData(final PropertyDescriptor[] descs) {
fc09ee
+        this(descs, setUpWriteMethodNames(descs));
fc09ee
+    }
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Creates a new instance of {@code BeanIntrospectionData} and allows setting the map
fc09ee
+     * with write method names. This constructor is mainly used for testing purposes.
fc09ee
+     *
fc09ee
+     * @param descs the array with the descriptors of the available properties
fc09ee
+     * @param writeMethNames the map with the names of write methods
fc09ee
+     */
fc09ee
+    BeanIntrospectionData(final PropertyDescriptor[] descs, final Map writeMethNames) {
fc09ee
+        descriptors = descs;
fc09ee
+        writeMethodNames = writeMethNames;
fc09ee
+    }
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Returns the array with property descriptors.
fc09ee
+     *
fc09ee
+     * @return the property descriptors for the associated bean class
fc09ee
+     */
fc09ee
+    public PropertyDescriptor[] getDescriptors() {
fc09ee
+        return descriptors;
fc09ee
+    }
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Returns the {@code PropertyDescriptor} for the property with the specified name. If
fc09ee
+     * this property is unknown, result is null.
fc09ee
+     *
fc09ee
+     * @param name the name of the property in question
fc09ee
+     * @return the {@code PropertyDescriptor} for this property or null
fc09ee
+     */
fc09ee
+    public PropertyDescriptor getDescriptor(final String name) {
fc09ee
+    	PropertyDescriptor[] descriptors = getDescriptors();
fc09ee
+        for (int i = 0; i < descriptors.length; ++i) {
fc09ee
+        	PropertyDescriptor pd = descriptors[i];
fc09ee
+            if (name.equals(pd.getName())) {
fc09ee
+                return pd;
fc09ee
+            }
fc09ee
+        }
fc09ee
+        return null;
fc09ee
+    }
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Returns the write method for the property determined by the given
fc09ee
+     * {@code PropertyDescriptor}. This information is normally available in the
fc09ee
+     * descriptor object itself. However, at least by the ORACLE implementation, the
fc09ee
+     * method is stored as a {@code SoftReference}. If this reference has been freed by
fc09ee
+     * the GC, it may be the case that the method cannot be obtained again. Then,
fc09ee
+     * additional information stored in this object is necessary to obtain the method
fc09ee
+     * again.
fc09ee
+     *
fc09ee
+     * @param beanCls the class of the affected bean
fc09ee
+     * @param desc the {@code PropertyDescriptor} of the desired property
fc09ee
+     * @return the write method for this property or null if there is none
fc09ee
+     */
fc09ee
+    public Method getWriteMethod(final Class beanCls, final PropertyDescriptor desc) {
fc09ee
+        Method method = desc.getWriteMethod();
fc09ee
+        if (method == null) {
fc09ee
+            final String methodName = (String) writeMethodNames.get(desc.getName());
fc09ee
+            if (methodName != null) {
fc09ee
+                method = MethodUtils.getAccessibleMethod(beanCls, methodName,
fc09ee
+                        desc.getPropertyType());
fc09ee
+                if (method != null) {
fc09ee
+                    try {
fc09ee
+                        desc.setWriteMethod(method);
fc09ee
+                    } catch (final IntrospectionException e) {
fc09ee
+                        // ignore, in this case the method is not cached
fc09ee
+                    }
fc09ee
+                }
fc09ee
+            }
fc09ee
+        }
fc09ee
+
fc09ee
+        return method;
fc09ee
+    }
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Initializes the map with the names of the write methods for the supported
fc09ee
+     * properties. The method names - if defined - need to be stored separately because
fc09ee
+     * they may get lost when the GC claims soft references used by the
fc09ee
+     * {@code PropertyDescriptor} objects.
fc09ee
+     *
fc09ee
+     * @param descs the array with the descriptors of the available properties
fc09ee
+     * @return the map with the names of write methods for properties
fc09ee
+     */
fc09ee
+    private static Map setUpWriteMethodNames(final PropertyDescriptor[] descs) {
fc09ee
+        final Map methods = new HashMap();
fc09ee
+        for (int i = 0; i < descs.length; ++i) {
fc09ee
+        	PropertyDescriptor pd = descs[i];
fc09ee
+            final Method method = pd.getWriteMethod();
fc09ee
+            if (method != null) {
fc09ee
+                methods.put(pd.getName(), method.getName());
fc09ee
+            }
fc09ee
+        }
fc09ee
+        return methods;
fc09ee
+    }
fc09ee
+}
fc09ee
diff --git a/src/main/java/org/apache/commons/beanutils/BeanIntrospector.java b/src/main/java/org/apache/commons/beanutils/BeanIntrospector.java
fc09ee
new file mode 100644
fc09ee
index 0000000..3f5545e
fc09ee
--- /dev/null
fc09ee
+++ b/src/main/java/org/apache/commons/beanutils/BeanIntrospector.java
fc09ee
@@ -0,0 +1,53 @@
fc09ee
+/*
fc09ee
+ * Licensed to the Apache Software Foundation (ASF) under one or more
fc09ee
+ * contributor license agreements.  See the NOTICE file distributed with
fc09ee
+ * this work for additional information regarding copyright ownership.
fc09ee
+ * The ASF licenses this file to You under the Apache License, Version 2.0
fc09ee
+ * (the "License"); you may not use this file except in compliance with
fc09ee
+ * the License.  You may obtain a copy of the License at
fc09ee
+ *
fc09ee
+ *      http://www.apache.org/licenses/LICENSE-2.0
fc09ee
+ *
fc09ee
+ * Unless required by applicable law or agreed to in writing, software
fc09ee
+ * distributed under the License is distributed on an "AS IS" BASIS,
fc09ee
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
fc09ee
+ * See the License for the specific language governing permissions and
fc09ee
+ * limitations under the License.
fc09ee
+ */
fc09ee
+package org.apache.commons.beanutils;
fc09ee
+
fc09ee
+import java.beans.IntrospectionException;
fc09ee
+
fc09ee
+/**
fc09ee
+ * 

fc09ee
+ * Definition of an interface for components that can perform introspection on
fc09ee
+ * bean classes.
fc09ee
+ * 

fc09ee
+ * 

fc09ee
+ * Before {@link PropertyUtils} can be used for interaction with a specific Java
fc09ee
+ * class, the class's properties have to be determined. This is called
fc09ee
+ * introspection and is initiated automatically on demand.
fc09ee
+ * PropertyUtils does not perform introspection on its own, but
fc09ee
+ * delegates this task to one or more objects implementing this interface. This
fc09ee
+ * makes it possible to customize introspection which may be useful for certain
fc09ee
+ * code bases using non-standard conventions for accessing properties.
fc09ee
+ * 

fc09ee
+ *
fc09ee
+ * @version $Id: BeanIntrospector.java 1540359 2013-11-09 18:10:52Z oheger $
fc09ee
+ * @since 1.9
fc09ee
+ */
fc09ee
+public interface BeanIntrospector {
fc09ee
+    /**
fc09ee
+     * Performs introspection on a Java class. The current class to be inspected
fc09ee
+     * can be queried from the passed in IntrospectionContext
fc09ee
+     * object. A typical implementation has to obtain this class, determine its
fc09ee
+     * properties according to the rules it implements, and add them to the
fc09ee
+     * passed in context object.
fc09ee
+     *
fc09ee
+     * @param icontext the context object for interaction with the initiator of
fc09ee
+     *        the introspection request
fc09ee
+     * @throws IntrospectionException if an error occurs during introspection
fc09ee
+     */
fc09ee
+    void introspect(IntrospectionContext icontext)
fc09ee
+            throws IntrospectionException;
fc09ee
+}
fc09ee
diff --git a/src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java
fc09ee
new file mode 100644
fc09ee
index 0000000..60657d0
fc09ee
--- /dev/null
fc09ee
+++ b/src/main/java/org/apache/commons/beanutils/DefaultBeanIntrospector.java
fc09ee
@@ -0,0 +1,181 @@
fc09ee
+/*
fc09ee
+ * Licensed to the Apache Software Foundation (ASF) under one or more
fc09ee
+ * contributor license agreements.  See the NOTICE file distributed with
fc09ee
+ * this work for additional information regarding copyright ownership.
fc09ee
+ * The ASF licenses this file to You under the Apache License, Version 2.0
fc09ee
+ * (the "License"); you may not use this file except in compliance with
fc09ee
+ * the License.  You may obtain a copy of the License at
fc09ee
+ *
fc09ee
+ *      http://www.apache.org/licenses/LICENSE-2.0
fc09ee
+ *
fc09ee
+ * Unless required by applicable law or agreed to in writing, software
fc09ee
+ * distributed under the License is distributed on an "AS IS" BASIS,
fc09ee
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
fc09ee
+ * See the License for the specific language governing permissions and
fc09ee
+ * limitations under the License.
fc09ee
+ */
fc09ee
+package org.apache.commons.beanutils;
fc09ee
+
fc09ee
+import java.beans.BeanInfo;
fc09ee
+import java.beans.IndexedPropertyDescriptor;
fc09ee
+import java.beans.IntrospectionException;
fc09ee
+import java.beans.Introspector;
fc09ee
+import java.beans.PropertyDescriptor;
fc09ee
+import java.lang.reflect.Method;
fc09ee
+import java.util.List;
fc09ee
+
fc09ee
+import org.apache.commons.logging.Log;
fc09ee
+import org.apache.commons.logging.LogFactory;
fc09ee
+
fc09ee
+/**
fc09ee
+ * 

fc09ee
+ * The default {@link BeanIntrospector} implementation.
fc09ee
+ * 

fc09ee
+ * 

fc09ee
+ * This class implements a default bean introspection algorithm based on the JDK
fc09ee
+ * classes in the java.beans package. It discovers properties
fc09ee
+ * conforming to the Java Beans specification.
fc09ee
+ * 

fc09ee
+ * 

fc09ee
+ * This class is a singleton. The single instance can be obtained using the
fc09ee
+ * {@code INSTANCE} field. It does not define any state and thus can be
fc09ee
+ * shared by arbitrary clients. {@link PropertyUtils} per default uses this
fc09ee
+ * instance as its only {@code BeanIntrospector} object.
fc09ee
+ * 

fc09ee
+ *
fc09ee
+ * @version $Id$
fc09ee
+ * @since 1.9
fc09ee
+ */
fc09ee
+public class DefaultBeanIntrospector implements BeanIntrospector {
fc09ee
+    /** The singleton instance of this class. */
fc09ee
+    public static final BeanIntrospector INSTANCE = new DefaultBeanIntrospector();
fc09ee
+
fc09ee
+    /** Constant for argument types of a method that expects no arguments. */
fc09ee
+    private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
fc09ee
+
fc09ee
+    /** Constant for arguments types of a method that expects a list argument. */
fc09ee
+    private static final Class[] LIST_CLASS_PARAMETER = new Class[] { java.util.List.class };
fc09ee
+
fc09ee
+    /** Log instance */
fc09ee
+    private final Log log = LogFactory.getLog(getClass());
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Private constructor so that no instances can be created.
fc09ee
+     */
fc09ee
+    private DefaultBeanIntrospector() {
fc09ee
+    }
fc09ee
+
fc09ee
+    /**
fc09ee
+     * Performs introspection of a specific Java class. This implementation uses
fc09ee
+     * the {@code java.beans.Introspector.getBeanInfo()} method to obtain
fc09ee
+     * all property descriptors for the current class and adds them to the
fc09ee
+     * passed in introspection context.
fc09ee
+     *
fc09ee
+     * @param icontext the introspection context
fc09ee
+     */
fc09ee
+    public void introspect(final IntrospectionContext icontext) {
fc09ee
+        BeanInfo beanInfo = null;
fc09ee
+        try {
fc09ee
+            beanInfo = Introspector.getBeanInfo(icontext.getTargetClass());
fc09ee
+        } catch (final IntrospectionException e) {
fc09ee
+            // no descriptors are added to the context
fc09ee
+            log.error(
fc09ee
+                    "Error when inspecting class " + icontext.getTargetClass(),
fc09ee
+                    e);
fc09ee
+            return;
fc09ee
+        }
fc09ee
+
fc09ee
+        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
fc09ee
+        if (descriptors == null) {
fc09ee
+            descriptors = new PropertyDescriptor[0];
fc09ee
+        }
fc09ee
+
fc09ee
+        handleIndexedPropertyDescriptors(icontext.getTargetClass(),
fc09ee
+                descriptors);
fc09ee
+        icontext.addPropertyDescriptors(descriptors);
fc09ee
+    }
fc09ee
+
fc09ee
+    /**
fc09ee
+     * This method fixes an issue where IndexedPropertyDescriptor behaves
fc09ee
+     * differently in different versions of the JDK for 'indexed' properties
fc09ee
+     * which use java.util.List (rather than an array). It implements a
fc09ee
+     * workaround for Bug 28358. If you have a Bean with the following
fc09ee
+     * getters/setters for an indexed property:
fc09ee
+     *
fc09ee
+     * 
fc09ee
+     * public List getFoo()
fc09ee
+     * public Object getFoo(int index)
fc09ee
+     * public void setFoo(List foo)
fc09ee
+     * public void setFoo(int index, Object foo)
fc09ee
+     * 
fc09ee
+     *
fc09ee
+     * then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod()
fc09ee
+     * behave as follows:
fc09ee
+     * 
    fc09ee
    +     * 
  • JDK 1.3.1_04: returns valid Method objects from these methods.
  • fc09ee
    +     * 
  • JDK 1.4.2_05: returns null from these methods.
  • fc09ee
    +     * 
    fc09ee
    +     *
    fc09ee
    +     * @param beanClass the current class to be inspected
    fc09ee
    +     * @param descriptors the array with property descriptors
    fc09ee
    +     */
    fc09ee
    +    private void handleIndexedPropertyDescriptors(final Class beanClass,
    fc09ee
    +            final PropertyDescriptor[] descriptors) {
    fc09ee
    +        for (final PropertyDescriptor pd : descriptors) {
    fc09ee
    +            if (pd instanceof IndexedPropertyDescriptor) {
    fc09ee
    +                final IndexedPropertyDescriptor descriptor = (IndexedPropertyDescriptor) pd;
    fc09ee
    +                final String propName = descriptor.getName().substring(0, 1)
    fc09ee
    +                        .toUpperCase()
    fc09ee
    +                        + descriptor.getName().substring(1);
    fc09ee
    +
    fc09ee
    +                if (descriptor.getReadMethod() == null) {
    fc09ee
    +                    final String methodName = descriptor.getIndexedReadMethod() != null ? descriptor
    fc09ee
    +                            .getIndexedReadMethod().getName() : "get"
    fc09ee
    +                            + propName;
    fc09ee
    +                    final Method readMethod = MethodUtils
    fc09ee
    +                            .getMatchingAccessibleMethod(beanClass, methodName,
    fc09ee
    +                                    EMPTY_CLASS_PARAMETERS);
    fc09ee
    +                    if (readMethod != null) {
    fc09ee
    +                        try {
    fc09ee
    +                            descriptor.setReadMethod(readMethod);
    fc09ee
    +                        } catch (final Exception e) {
    fc09ee
    +                            log.error(
    fc09ee
    +                                    "Error setting indexed property read method",
    fc09ee
    +                                    e);
    fc09ee
    +                        }
    fc09ee
    +                    }
    fc09ee
    +                }
    fc09ee
    +                if (descriptor.getWriteMethod() == null) {
    fc09ee
    +                    final String methodName = descriptor.getIndexedWriteMethod() != null ? descriptor
    fc09ee
    +                            .getIndexedWriteMethod().getName() : "set"
    fc09ee
    +                            + propName;
    fc09ee
    +                    Method writeMethod = MethodUtils
    fc09ee
    +                            .getMatchingAccessibleMethod(beanClass, methodName,
    fc09ee
    +                                    LIST_CLASS_PARAMETER);
    fc09ee
    +                    if (writeMethod == null) {
    fc09ee
    +                        for (final Method m : beanClass.getMethods()) {
    fc09ee
    +                            if (m.getName().equals(methodName)) {
    fc09ee
    +                                final Class[] parameterTypes = m.getParameterTypes();
    fc09ee
    +                                if (parameterTypes.length == 1
    fc09ee
    +                                        && List.class
    fc09ee
    +                                                .isAssignableFrom(parameterTypes[0])) {
    fc09ee
    +                                    writeMethod = m;
    fc09ee
    +                                    break;
    fc09ee
    +                                }
    fc09ee
    +                            }
    fc09ee
    +                        }
    fc09ee
    +                    }
    fc09ee
    +                    if (writeMethod != null) {
    fc09ee
    +                        try {
    fc09ee
    +                            descriptor.setWriteMethod(writeMethod);
    fc09ee
    +                        } catch (final Exception e) {
    fc09ee
    +                            log.error(
    fc09ee
    +                                    "Error setting indexed property write method",
    fc09ee
    +                                    e);
    fc09ee
    +                        }
    fc09ee
    +                    }
    fc09ee
    +                }
    fc09ee
    +            }
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +}
    fc09ee
    diff --git a/src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java b/src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java
    fc09ee
    new file mode 100644
    fc09ee
    index 0000000..9171198
    fc09ee
    --- /dev/null
    fc09ee
    +++ b/src/main/java/org/apache/commons/beanutils/DefaultIntrospectionContext.java
    fc09ee
    @@ -0,0 +1,108 @@
    fc09ee
    +/*
    fc09ee
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    fc09ee
    + * contributor license agreements.  See the NOTICE file distributed with
    fc09ee
    + * this work for additional information regarding copyright ownership.
    fc09ee
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    fc09ee
    + * (the "License"); you may not use this file except in compliance with
    fc09ee
    + * the License.  You may obtain a copy of the License at
    fc09ee
    + *
    fc09ee
    + *      http://www.apache.org/licenses/LICENSE-2.0
    fc09ee
    + *
    fc09ee
    + * Unless required by applicable law or agreed to in writing, software
    fc09ee
    + * distributed under the License is distributed on an "AS IS" BASIS,
    fc09ee
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    fc09ee
    + * See the License for the specific language governing permissions and
    fc09ee
    + * limitations under the License.
    fc09ee
    + */
    fc09ee
    +package org.apache.commons.beanutils;
    fc09ee
    +
    fc09ee
    +import java.beans.PropertyDescriptor;
    fc09ee
    +import java.util.HashMap;
    fc09ee
    +import java.util.Map;
    fc09ee
    +import java.util.Set;
    fc09ee
    +
    fc09ee
    +/**
    fc09ee
    + * 

    fc09ee
    + * An implementation of the {@code IntrospectionContext} interface used by
    fc09ee
    + * {@link PropertyUtilsBean} when doing introspection of a bean class.
    fc09ee
    + * 

    fc09ee
    + * 

    fc09ee
    + * This class implements the methods required by the
    fc09ee
    + * {@code IntrospectionContext} interface in a straight-forward manner
    fc09ee
    + * based on a map. It is used internally only. It is not thread-safe.
    fc09ee
    + * 

    fc09ee
    + *
    fc09ee
    + * @version $Id$
    fc09ee
    + * @since 1.9
    fc09ee
    + */
    fc09ee
    +class DefaultIntrospectionContext implements IntrospectionContext {
    fc09ee
    +    /** Constant for an empty array of property descriptors. */
    fc09ee
    +    private static final PropertyDescriptor[] EMPTY_DESCRIPTORS = new PropertyDescriptor[0];
    fc09ee
    +
    fc09ee
    +    /** The current class for introspection. */
    fc09ee
    +    private final Class currentClass;
    fc09ee
    +
    fc09ee
    +    /** A map for storing the already added property descriptors. */
    fc09ee
    +    private final Map descriptors;
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     *
    fc09ee
    +     * Creates a new instance of DefaultIntrospectionContext and sets
    fc09ee
    +     * the current class for introspection.
    fc09ee
    +     *
    fc09ee
    +     * @param cls the current class
    fc09ee
    +     */
    fc09ee
    +    public DefaultIntrospectionContext(final Class cls) {
    fc09ee
    +        currentClass = cls;
    fc09ee
    +        descriptors = new HashMap();
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    public Class getTargetClass() {
    fc09ee
    +        return currentClass;
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    public void addPropertyDescriptor(final PropertyDescriptor desc) {
    fc09ee
    +        if (desc == null) {
    fc09ee
    +            throw new IllegalArgumentException(
    fc09ee
    +                    "Property descriptor must not be null!");
    fc09ee
    +        }
    fc09ee
    +        descriptors.put(desc.getName(), desc);
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    public void addPropertyDescriptors(final PropertyDescriptor[] descs) {
    fc09ee
    +        if (descs == null) {
    fc09ee
    +            throw new IllegalArgumentException(
    fc09ee
    +                    "Array with descriptors must not be null!");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        for (int i = 0; i < descs.length; ++i) {
    fc09ee
    +            addPropertyDescriptor(descs[i]);
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    public boolean hasProperty(final String name) {
    fc09ee
    +        return descriptors.containsKey(name);
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    public PropertyDescriptor getPropertyDescriptor(final String name) {
    fc09ee
    +        return (PropertyDescriptor) descriptors.get(name);
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    public void removePropertyDescriptor(final String name) {
    fc09ee
    +        descriptors.remove(name);
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    public Set propertyNames() {
    fc09ee
    +        return descriptors.keySet();
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Returns an array with all descriptors added to this context. This method
    fc09ee
    +     * is used to obtain the results of introspection.
    fc09ee
    +     *
    fc09ee
    +     * @return an array with all known property descriptors
    fc09ee
    +     */
    fc09ee
    +    public PropertyDescriptor[] getPropertyDescriptors() {
    fc09ee
    +        return (PropertyDescriptor[]) descriptors.values().toArray(EMPTY_DESCRIPTORS);
    fc09ee
    +    }
    fc09ee
    +}
    fc09ee
    diff --git a/src/main/java/org/apache/commons/beanutils/IntrospectionContext.java b/src/main/java/org/apache/commons/beanutils/IntrospectionContext.java
    fc09ee
    new file mode 100644
    fc09ee
    index 0000000..c48f186
    fc09ee
    --- /dev/null
    fc09ee
    +++ b/src/main/java/org/apache/commons/beanutils/IntrospectionContext.java
    fc09ee
    @@ -0,0 +1,99 @@
    fc09ee
    +/*
    fc09ee
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    fc09ee
    + * contributor license agreements.  See the NOTICE file distributed with
    fc09ee
    + * this work for additional information regarding copyright ownership.
    fc09ee
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    fc09ee
    + * (the "License"); you may not use this file except in compliance with
    fc09ee
    + * the License.  You may obtain a copy of the License at
    fc09ee
    + *
    fc09ee
    + *      http://www.apache.org/licenses/LICENSE-2.0
    fc09ee
    + *
    fc09ee
    + * Unless required by applicable law or agreed to in writing, software
    fc09ee
    + * distributed under the License is distributed on an "AS IS" BASIS,
    fc09ee
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    fc09ee
    + * See the License for the specific language governing permissions and
    fc09ee
    + * limitations under the License.
    fc09ee
    + */
    fc09ee
    +package org.apache.commons.beanutils;
    fc09ee
    +
    fc09ee
    +import java.beans.PropertyDescriptor;
    fc09ee
    +import java.util.Set;
    fc09ee
    +
    fc09ee
    +/**
    fc09ee
    + * 

    fc09ee
    + * A context interface used during introspection for querying and setting
    fc09ee
    + * property descriptors.
    fc09ee
    + * 

    fc09ee
    + * 

    fc09ee
    + * An implementation of this interface is passed to {@link BeanIntrospector}
    fc09ee
    + * objects during processing of a bean class. It allows the
    fc09ee
    + * {@code BeanIntrospector} to deliver descriptors for properties it has
    fc09ee
    + * detected. It is also possible to find out which properties have already been
    fc09ee
    + * found by another {@code BeanIntrospector}; this allows multiple
    fc09ee
    + * {@code BeanIntrospector} instances to collaborate.
    fc09ee
    + * 

    fc09ee
    + *
    fc09ee
    + * @version $Id: IntrospectionContext.java 1540359 2013-11-09 18:10:52Z oheger $
    fc09ee
    + * @since 1.9
    fc09ee
    + */
    fc09ee
    +public interface IntrospectionContext {
    fc09ee
    +    /**
    fc09ee
    +     * Returns the class that is subject of introspection.
    fc09ee
    +     *
    fc09ee
    +     * @return the current class
    fc09ee
    +     */
    fc09ee
    +    Class getTargetClass();
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Adds the given property descriptor to this context. This method is called
    fc09ee
    +     * by a {@code BeanIntrospector} during introspection for each detected
    fc09ee
    +     * property. If this context already contains a descriptor for the affected
    fc09ee
    +     * property, it is overridden.
    fc09ee
    +     *
    fc09ee
    +     * @param desc the property descriptor
    fc09ee
    +     */
    fc09ee
    +    void addPropertyDescriptor(PropertyDescriptor desc);
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Adds an array of property descriptors to this context. Using this method
    fc09ee
    +     * multiple descriptors can be added at once.
    fc09ee
    +     *
    fc09ee
    +     * @param descriptors the array of descriptors to be added
    fc09ee
    +     */
    fc09ee
    +    void addPropertyDescriptors(PropertyDescriptor[] descriptors);
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Tests whether a descriptor for the property with the given name is
    fc09ee
    +     * already contained in this context. This method can be used for instance
    fc09ee
    +     * to prevent that an already existing property descriptor is overridden.
    fc09ee
    +     *
    fc09ee
    +     * @param name the name of the property in question
    fc09ee
    +     * @return true if a descriptor for this property has already been
    fc09ee
    +     *         added, false otherwise
    fc09ee
    +     */
    fc09ee
    +    boolean hasProperty(String name);
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Returns the descriptor for the property with the given name or
    fc09ee
    +     * null if this property is unknown.
    fc09ee
    +     *
    fc09ee
    +     * @param name the name of the property in question
    fc09ee
    +     * @return the descriptor for this property or null if this property
    fc09ee
    +     *         is unknown
    fc09ee
    +     */
    fc09ee
    +    PropertyDescriptor getPropertyDescriptor(String name);
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Removes the descriptor for the property with the given name.
    fc09ee
    +     *
    fc09ee
    +     * @param name the name of the affected property
    fc09ee
    +     */
    fc09ee
    +    void removePropertyDescriptor(String name);
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Returns a set with the names of all properties known to this context.
    fc09ee
    +     *
    fc09ee
    +     * @return a set with the known property names
    fc09ee
    +     */
    fc09ee
    +    Set propertyNames();
    fc09ee
    +}
    fc09ee
    diff --git a/src/main/java/org/apache/commons/beanutils/PropertyUtils.java b/src/main/java/org/apache/commons/beanutils/PropertyUtils.java
    fc09ee
    index 6344b4f..b3ce0e6 100644
    fc09ee
    --- a/src/main/java/org/apache/commons/beanutils/PropertyUtils.java
    fc09ee
    +++ b/src/main/java/org/apache/commons/beanutils/PropertyUtils.java
    fc09ee
    @@ -890,5 +890,20 @@ public class PropertyUtils {
    fc09ee
             PropertyUtilsBean.getInstance().setSimpleProperty(bean, name, value);
    fc09ee
         }
    fc09ee
     
    fc09ee
    +    public static void addBeanIntrospector(final BeanIntrospector introspector) {
    fc09ee
    +        PropertyUtilsBean.getInstance().addBeanIntrospector(introspector);
    fc09ee
    +    }
    fc09ee
     
    fc09ee
    +    /**
    fc09ee
    +     * Removes the specified BeanIntrospector.
    fc09ee
    +     *
    fc09ee
    +     * @param introspector the BeanIntrospector to be removed
    fc09ee
    +     * @return true if the BeanIntrospector existed and
    fc09ee
    +     *         could be removed, false otherwise
    fc09ee
    +     * @since 1.9
    fc09ee
    +     */
    fc09ee
    +    public static boolean removeBeanIntrospector(final BeanIntrospector introspector) {
    fc09ee
    +        return PropertyUtilsBean.getInstance().removeBeanIntrospector(
    fc09ee
    +                introspector);
    fc09ee
    +    }
    fc09ee
     }
    fc09ee
    diff --git a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
    fc09ee
    index 1894f21..c7817cd 100644
    fc09ee
    --- a/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
    fc09ee
    +++ b/src/main/java/org/apache/commons/beanutils/PropertyUtilsBean.java
    fc09ee
    @@ -37,6 +37,7 @@ import org.apache.commons.collections.FastHashMap;
    fc09ee
     import org.apache.commons.logging.Log;
    fc09ee
     import org.apache.commons.logging.LogFactory;
    fc09ee
     
    fc09ee
    +import java.util.concurrent.CopyOnWriteArrayList;
    fc09ee
     
    fc09ee
     /**
    fc09ee
      * Utility methods for using Java Reflection APIs to facilitate generic
    fc09ee
    @@ -130,7 +131,9 @@ public class PropertyUtilsBean {
    fc09ee
     
    fc09ee
         /** Log instance */
    fc09ee
         private Log log = LogFactory.getLog(PropertyUtils.class);
    fc09ee
    -    
    fc09ee
    +
    fc09ee
    +    /** The list with BeanIntrospector objects. */
    fc09ee
    +    private final List introspectors;
    fc09ee
         // ---------------------------------------------------------- Constructors
    fc09ee
         
    fc09ee
         /** Base constructor */
    fc09ee
    @@ -139,6 +142,8 @@ public class PropertyUtilsBean {
    fc09ee
             descriptorsCache.setFast(true);
    fc09ee
             mappedDescriptorsCache = new WeakFastHashMap();
    fc09ee
             mappedDescriptorsCache.setFast(true);
    fc09ee
    +        introspectors = new CopyOnWriteArrayList();
    fc09ee
    +        resetBeanIntrospectors();
    fc09ee
         }
    fc09ee
     
    fc09ee
     
    fc09ee
    @@ -183,6 +188,47 @@ public class PropertyUtilsBean {
    fc09ee
             }
    fc09ee
         }
    fc09ee
     
    fc09ee
    +    /**
    fc09ee
    +     * Resets the {@link BeanIntrospector} objects registered at this instance. After this
    fc09ee
    +     * method was called, only the default {@code BeanIntrospector} is registered.
    fc09ee
    +     *
    fc09ee
    +     * @since 1.9
    fc09ee
    +     */
    fc09ee
    +    public final void resetBeanIntrospectors() {
    fc09ee
    +        introspectors.clear();
    fc09ee
    +        introspectors.add(DefaultBeanIntrospector.INSTANCE);
    fc09ee
    +        introspectors.add(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Adds a BeanIntrospector. This object is invoked when the
    fc09ee
    +     * property descriptors of a class need to be obtained.
    fc09ee
    +     *
    fc09ee
    +     * @param introspector the BeanIntrospector to be added (must
    fc09ee
    +     *        not be null
    fc09ee
    +     * @throws IllegalArgumentException if the argument is null
    fc09ee
    +     * @since 1.9
    fc09ee
    +     */
    fc09ee
    +    public void addBeanIntrospector(final BeanIntrospector introspector) {
    fc09ee
    +        if (introspector == null) {
    fc09ee
    +            throw new IllegalArgumentException(
    fc09ee
    +                    "BeanIntrospector must not be null!");
    fc09ee
    +        }
    fc09ee
    +        introspectors.add(introspector);
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Removes the specified BeanIntrospector.
    fc09ee
    +     *
    fc09ee
    +     * @param introspector the BeanIntrospector to be removed
    fc09ee
    +     * @return true if the BeanIntrospector existed and
    fc09ee
    +     *         could be removed, false otherwise
    fc09ee
    +     * @since 1.9
    fc09ee
    +     */
    fc09ee
    +    public boolean removeBeanIntrospector(final BeanIntrospector introspector) {
    fc09ee
    +        return introspectors.remove(introspector);
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
         /**
    fc09ee
          * Clear any cached property descriptors information for all classes
    fc09ee
          * loaded by any class loaders.  This is useful in cases where class
    fc09ee
    @@ -909,17 +955,12 @@ public class PropertyUtilsBean {
    fc09ee
                 return (null);
    fc09ee
             }
    fc09ee
             
    fc09ee
    -        PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
    fc09ee
    -        if (descriptors != null) {
    fc09ee
    -            
    fc09ee
    -            for (int i = 0; i < descriptors.length; i++) {
    fc09ee
    -                if (name.equals(descriptors[i].getName())) {
    fc09ee
    -                    return (descriptors[i]);
    fc09ee
    -                }
    fc09ee
    -            }
    fc09ee
    +        final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
    fc09ee
    +        PropertyDescriptor result = data.getDescriptor(name);
    fc09ee
    +        if (result != null) {
    fc09ee
    +            return result;
    fc09ee
             }
    fc09ee
     
    fc09ee
    -        PropertyDescriptor result = null;
    fc09ee
             FastHashMap mappedDescriptors =
    fc09ee
                     getMappedPropertyDescriptors(bean);
    fc09ee
             if (mappedDescriptors == null) {
    fc09ee
    @@ -960,110 +1001,10 @@ public class PropertyUtilsBean {
    fc09ee
          * @exception IllegalArgumentException if beanClass is null
    fc09ee
          */
    fc09ee
         public PropertyDescriptor[]
    fc09ee
    -            getPropertyDescriptors(Class beanClass) {
    fc09ee
    -
    fc09ee
    -        if (beanClass == null) {
    fc09ee
    -            throw new IllegalArgumentException("No bean class specified");
    fc09ee
    -        }
    fc09ee
    -
    fc09ee
    -        // Look up any cached descriptors for this bean class
    fc09ee
    -        PropertyDescriptor[] descriptors = null;
    fc09ee
    -        descriptors =
    fc09ee
    -                (PropertyDescriptor[]) descriptorsCache.get(beanClass);
    fc09ee
    -        if (descriptors != null) {
    fc09ee
    -            return (descriptors);
    fc09ee
    -        }
    fc09ee
    -
    fc09ee
    -        // Introspect the bean and cache the generated descriptors
    fc09ee
    -        BeanInfo beanInfo = null;
    fc09ee
    -        try {
    fc09ee
    -            beanInfo = Introspector.getBeanInfo(beanClass);
    fc09ee
    -        } catch (IntrospectionException e) {
    fc09ee
    -            return (new PropertyDescriptor[0]);
    fc09ee
    -        }
    fc09ee
    -        descriptors = beanInfo.getPropertyDescriptors();
    fc09ee
    -        if (descriptors == null) {
    fc09ee
    -            descriptors = new PropertyDescriptor[0];
    fc09ee
    -        }
    fc09ee
    -
    fc09ee
    -        // ----------------- Workaround for Bug 28358 --------- START ------------------
    fc09ee
    -        //
    fc09ee
    -        // The following code fixes an issue where IndexedPropertyDescriptor behaves
    fc09ee
    -        // Differently in different versions of the JDK for 'indexed' properties which
    fc09ee
    -        // use java.util.List (rather than an array).
    fc09ee
    -        //
    fc09ee
    -        // If you have a Bean with the following getters/setters for an indexed property:
    fc09ee
    -        //
    fc09ee
    -        //     public List getFoo()
    fc09ee
    -        //     public Object getFoo(int index)
    fc09ee
    -        //     public void setFoo(List foo)
    fc09ee
    -        //     public void setFoo(int index, Object foo)
    fc09ee
    -        //
    fc09ee
    -        // then the IndexedPropertyDescriptor's getReadMethod() and getWriteMethod()
    fc09ee
    -        // behave as follows:
    fc09ee
    -        //
    fc09ee
    -        //     JDK 1.3.1_04: returns valid Method objects from these methods.
    fc09ee
    -        //     JDK 1.4.2_05: returns null from these methods.
    fc09ee
    -        //
    fc09ee
    -        for (int i = 0; i < descriptors.length; i++) {
    fc09ee
    -            if (descriptors[i] instanceof IndexedPropertyDescriptor) {
    fc09ee
    -                IndexedPropertyDescriptor descriptor =  (IndexedPropertyDescriptor)descriptors[i];
    fc09ee
    -                String propName = descriptor.getName().substring(0, 1).toUpperCase() +
    fc09ee
    -                                  descriptor.getName().substring(1);
    fc09ee
    -
    fc09ee
    -                if (descriptor.getReadMethod() == null) {
    fc09ee
    -                    String methodName = descriptor.getIndexedReadMethod() != null
    fc09ee
    -                                        ? descriptor.getIndexedReadMethod().getName()
    fc09ee
    -                                        : "get" + propName;
    fc09ee
    -                    Method readMethod = MethodUtils.getMatchingAccessibleMethod(beanClass,
    fc09ee
    -                                                            methodName,
    fc09ee
    -                                                            EMPTY_CLASS_PARAMETERS);
    fc09ee
    -                    if (readMethod != null) {
    fc09ee
    -                        try {
    fc09ee
    -                            descriptor.setReadMethod(readMethod);
    fc09ee
    -                        } catch(Exception e) {
    fc09ee
    -                            log.error("Error setting indexed property read method", e);
    fc09ee
    -                        }
    fc09ee
    -                    }
    fc09ee
    -                }
    fc09ee
    -                if (descriptor.getWriteMethod() == null) {
    fc09ee
    -                    String methodName = descriptor.getIndexedWriteMethod() != null
    fc09ee
    -                                      ? descriptor.getIndexedWriteMethod().getName()
    fc09ee
    -                                      : "set" + propName;
    fc09ee
    -                    Method writeMethod = MethodUtils.getMatchingAccessibleMethod(beanClass,
    fc09ee
    -                                                            methodName,
    fc09ee
    -                                                            LIST_CLASS_PARAMETER);
    fc09ee
    -                    if (writeMethod == null) {
    fc09ee
    -                        Method[] methods = beanClass.getMethods();
    fc09ee
    -                        for (int j = 0; j < methods.length; j++) {
    fc09ee
    -                            if (methods[j].getName().equals(methodName)) {
    fc09ee
    -                                Class[] parameterTypes = methods[j].getParameterTypes();
    fc09ee
    -                                if (parameterTypes.length == 1 &&
    fc09ee
    -                                    List.class.isAssignableFrom(parameterTypes[0])) {
    fc09ee
    -                                    writeMethod = methods[j];
    fc09ee
    -                                    break; 
    fc09ee
    -                                }
    fc09ee
    -                            }
    fc09ee
    -                        }
    fc09ee
    -                    }
    fc09ee
    -                    if (writeMethod != null) {
    fc09ee
    -                        try {
    fc09ee
    -                            descriptor.setWriteMethod(writeMethod);
    fc09ee
    -                        } catch(Exception e) {
    fc09ee
    -                            log.error("Error setting indexed property write method", e);
    fc09ee
    -                        }
    fc09ee
    -                    }
    fc09ee
    -                }
    fc09ee
    -            }
    fc09ee
    -        }
    fc09ee
    -        // ----------------- Workaround for Bug 28358 ---------- END -------------------
    fc09ee
    -
    fc09ee
    -        descriptorsCache.put(beanClass, descriptors);
    fc09ee
    -        return (descriptors);
    fc09ee
    -
    fc09ee
    +                getPropertyDescriptors(Class beanClass) {
    fc09ee
    +        return getIntrospectionData(beanClass).getDescriptors();
    fc09ee
         }
    fc09ee
     
    fc09ee
    -
    fc09ee
         /**
    fc09ee
          * 

    Retrieve the property descriptors for the specified bean,

    fc09ee
          * introspecting and caching them the first time a particular bean class
    fc09ee
    @@ -2248,4 +2189,52 @@ public class PropertyUtilsBean {
    fc09ee
                 
    fc09ee
             }
    fc09ee
         }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Obtains the {@code BeanIntrospectionData} object describing the specified bean
    fc09ee
    +     * class. This object is looked up in the internal cache. If necessary, introspection
    fc09ee
    +     * is performed now on the affected bean class, and the results object is created.
    fc09ee
    +     *
    fc09ee
    +     * @param beanClass the bean class in question
    fc09ee
    +     * @return the {@code BeanIntrospectionData} object for this class
    fc09ee
    +     * @throws IllegalArgumentException if the bean class is null
    fc09ee
    +     */
    fc09ee
    +    private BeanIntrospectionData getIntrospectionData(final Class beanClass) {
    fc09ee
    +        if (beanClass == null) {
    fc09ee
    +            throw new IllegalArgumentException("No bean class specified");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        // Look up any cached information for this bean class
    fc09ee
    +        BeanIntrospectionData data = (BeanIntrospectionData) descriptorsCache.get(beanClass);
    fc09ee
    +        if (data == null) {
    fc09ee
    +            data = fetchIntrospectionData(beanClass);
    fc09ee
    +            descriptorsCache.put(beanClass, data);
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        return data;
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Performs introspection on the specified class. This method invokes all {@code BeanIntrospector} objects that were
    fc09ee
    +     * added to this instance.
    fc09ee
    +     *
    fc09ee
    +     * @param beanClass the class to be inspected
    fc09ee
    +     * @return a data object with the results of introspection
    fc09ee
    +     */
    fc09ee
    +    private BeanIntrospectionData fetchIntrospectionData(final Class beanClass) {
    fc09ee
    +        final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);
    fc09ee
    +
    fc09ee
    +    	Iterator it = introspectors.iterator();
    fc09ee
    +    	while (it.hasNext()) {
    fc09ee
    +    		final BeanIntrospector bi = (BeanIntrospector) it.next();
    fc09ee
    +    		
    fc09ee
    +            try {
    fc09ee
    +                bi.introspect(ictx);
    fc09ee
    +            } catch (final IntrospectionException iex) {
    fc09ee
    +                log.error("Exception during introspection", iex);
    fc09ee
    +            }
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        return new BeanIntrospectionData(ictx.getPropertyDescriptors());
    fc09ee
    +    }
    fc09ee
     }
    fc09ee
    diff --git a/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
    fc09ee
    new file mode 100644
    fc09ee
    index 0000000..9d4e3b3
    fc09ee
    --- /dev/null
    fc09ee
    +++ b/src/main/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospector.java
    fc09ee
    @@ -0,0 +1,75 @@
    fc09ee
    +package org.apache.commons.beanutils;
    fc09ee
    +
    fc09ee
    +import java.beans.IntrospectionException;
    fc09ee
    +import java.util.Collection;
    fc09ee
    +import java.util.Collections;
    fc09ee
    +import java.util.HashSet;
    fc09ee
    +import java.util.Set;
    fc09ee
    +
    fc09ee
    +/**
    fc09ee
    + * 

    fc09ee
    + * A specialized {@code BeanIntrospector} implementation which suppresses some properties.
    fc09ee
    + * 

    fc09ee
    + * 

    fc09ee
    + * An instance of this class is passed a set with the names of the properties it should
    fc09ee
    + * process. During introspection of a bean class it removes all these properties from the
    fc09ee
    + * {@link IntrospectionContext}. So effectively, properties added by a different
    fc09ee
    + * {@code BeanIntrospector} are removed again.
    fc09ee
    + * 

    fc09ee
    + *
    fc09ee
    + * @version $Id$
    fc09ee
    + * @since 1.9.2
    fc09ee
    + */
    fc09ee
    +public class SuppressPropertiesBeanIntrospector implements BeanIntrospector {
    fc09ee
    +    /**
    fc09ee
    +     * A specialized instance which is configured to suppress the special {@code class}
    fc09ee
    +     * properties of Java beans. Unintended access to the property {@code class} (which is
    fc09ee
    +     * common to all Java objects) can be a security risk because it also allows access to
    fc09ee
    +     * the class loader. Adding this instance as {@code BeanIntrospector} to an instance
    fc09ee
    +     * of {@code PropertyUtilsBean} suppresses the {@code class} property; it can then no
    fc09ee
    +     * longer be accessed.
    fc09ee
    +     */
    fc09ee
    +    public static final SuppressPropertiesBeanIntrospector SUPPRESS_CLASS =
    fc09ee
    +            new SuppressPropertiesBeanIntrospector(Collections.singleton("class"));
    fc09ee
    +
    fc09ee
    +    /** A set with the names of the properties to be suppressed. */
    fc09ee
    +    private final Set<String> propertyNames;
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Creates a new instance of {@code SuppressPropertiesBeanIntrospector} and sets the
    fc09ee
    +     * names of the properties to be suppressed.
    fc09ee
    +     *
    fc09ee
    +     * @param propertiesToSuppress the names of the properties to be suppressed (must not
    fc09ee
    +     * be null)
    fc09ee
    +     * @throws IllegalArgumentException if the collection with property names is
    fc09ee
    +     * null
    fc09ee
    +     */
    fc09ee
    +    public SuppressPropertiesBeanIntrospector(Collection<String> propertiesToSuppress) {
    fc09ee
    +        if (propertiesToSuppress == null) {
    fc09ee
    +            throw new IllegalArgumentException("Property names must not be null!");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        propertyNames = Collections.unmodifiableSet(new HashSet<String>(
    fc09ee
    +                propertiesToSuppress));
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Returns a (unmodifiable) set with the names of the properties which are suppressed
    fc09ee
    +     * by this {@code BeanIntrospector}.
    fc09ee
    +     *
    fc09ee
    +     * @return a set with the names of the suppressed properties
    fc09ee
    +     */
    fc09ee
    +    public Set<String> getSuppressedProperties() {
    fc09ee
    +        return propertyNames;
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * {@inheritDoc} This implementation removes all properties from the given context it
    fc09ee
    +     * is configured for.
    fc09ee
    +     */
    fc09ee
    +    public void introspect(IntrospectionContext icontext) throws IntrospectionException {
    fc09ee
    +        for (String property : getSuppressedProperties()) {
    fc09ee
    +            icontext.removePropertyDescriptor(property);
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +}
    fc09ee
    diff --git a/src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java b/src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java
    fc09ee
    new file mode 100644
    fc09ee
    index 0000000..ed93e70
    fc09ee
    --- /dev/null
    fc09ee
    +++ b/src/test/java/org/apache/commons/beanutils/SuppressPropertiesBeanIntrospectorTestCase.java
    fc09ee
    @@ -0,0 +1,127 @@
    fc09ee
    +package org.apache.commons.beanutils;
    fc09ee
    +
    fc09ee
    +import java.beans.IntrospectionException;
    fc09ee
    +import java.beans.PropertyDescriptor;
    fc09ee
    +import java.util.Arrays;
    fc09ee
    +import java.util.Collection;
    fc09ee
    +import java.util.HashSet;
    fc09ee
    +import java.util.Set;
    fc09ee
    +
    fc09ee
    +import junit.framework.TestCase;
    fc09ee
    +
    fc09ee
    +/**
    fc09ee
    + * Test class for {@code SuppressPropertiesBeanIntrospector}.
    fc09ee
    + *
    fc09ee
    + * @version $Id$
    fc09ee
    + */
    fc09ee
    +public class SuppressPropertiesBeanIntrospectorTestCase extends TestCase {
    fc09ee
    +    /**
    fc09ee
    +     * Tries to create an instance without properties.
    fc09ee
    +     */
    fc09ee
    +    public void testInitNoPropertyNames() {
    fc09ee
    +        try {
    fc09ee
    +            new SuppressPropertiesBeanIntrospector(null);
    fc09ee
    +            fail("Missing properties not detected!");
    fc09ee
    +        } catch (IllegalArgumentException iaex) {
    fc09ee
    +            // ok
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Tests whether the expected properties have been removed during introspection.
    fc09ee
    +     */
    fc09ee
    +    public void testRemovePropertiesDuringIntrospection() throws IntrospectionException {
    fc09ee
    +        String[] properties = { "test", "other", "oneMore" };
    fc09ee
    +        SuppressPropertiesBeanIntrospector introspector = new SuppressPropertiesBeanIntrospector(
    fc09ee
    +                Arrays.asList(properties));
    fc09ee
    +        IntrospectionContextTestImpl context = new IntrospectionContextTestImpl();
    fc09ee
    +
    fc09ee
    +        introspector.introspect(context);
    fc09ee
    +        assertEquals("Wrong number of removed properties", properties.length, context
    fc09ee
    +                .getRemovedProperties().size());
    fc09ee
    +        for (String property : properties) {
    fc09ee
    +            assertTrue("Property not removed: " + property, context
    fc09ee
    +                    .getRemovedProperties().contains(property));
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Tests that a defensive copy is created from the collection with properties to be
    fc09ee
    +     * removed.
    fc09ee
    +     */
    fc09ee
    +    public void testPropertyNamesDefensiveCopy() throws IntrospectionException {
    fc09ee
    +        Collection<String> properties = new HashSet<String>();
    fc09ee
    +        properties.add("prop1");
    fc09ee
    +        SuppressPropertiesBeanIntrospector introspector = new SuppressPropertiesBeanIntrospector(
    fc09ee
    +                properties);
    fc09ee
    +        properties.add("prop2");
    fc09ee
    +        IntrospectionContextTestImpl context = new IntrospectionContextTestImpl();
    fc09ee
    +
    fc09ee
    +        introspector.introspect(context);
    fc09ee
    +        assertEquals("Wrong number of removed properties", 1, context
    fc09ee
    +                .getRemovedProperties().size());
    fc09ee
    +        assertTrue("Wrong removed property",
    fc09ee
    +                context.getRemovedProperties().contains("prop1"));
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Tests that the set with properties to be removed cannot be modified.
    fc09ee
    +     */
    fc09ee
    +    public void testGetSuppressedPropertiesModify() {
    fc09ee
    +        SuppressPropertiesBeanIntrospector introspector = new SuppressPropertiesBeanIntrospector(
    fc09ee
    +                Arrays.asList("p1", "p2"));
    fc09ee
    +        Set<String> properties = introspector.getSuppressedProperties();
    fc09ee
    +        try {
    fc09ee
    +            properties.add("anotherProperty");
    fc09ee
    +            fail("Could modify properties");
    fc09ee
    +        } catch (UnsupportedOperationException uoex) {
    fc09ee
    +            // ok
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * A test implementation of IntrospectionContext which collects the properties which
    fc09ee
    +     * have been removed.
    fc09ee
    +     */
    fc09ee
    +    private static class IntrospectionContextTestImpl implements IntrospectionContext {
    fc09ee
    +        /** Stores the names of properties which have been removed. */
    fc09ee
    +        private final Set<String> removedProperties = new HashSet<String>();
    fc09ee
    +
    fc09ee
    +        /**
    fc09ee
    +         * Returns the names of properties which have been removed.
    fc09ee
    +         *
    fc09ee
    +         * @return the set with removed properties
    fc09ee
    +         */
    fc09ee
    +        public Set<String> getRemovedProperties() {
    fc09ee
    +            return removedProperties;
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        public Class getTargetClass() {
    fc09ee
    +            throw new UnsupportedOperationException("Unexpected method call!");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        public void addPropertyDescriptor(PropertyDescriptor desc) {
    fc09ee
    +            throw new UnsupportedOperationException("Unexpected method call!");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        public void addPropertyDescriptors(PropertyDescriptor[] descriptors) {
    fc09ee
    +            throw new UnsupportedOperationException("Unexpected method call!");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        public boolean hasProperty(String name) {
    fc09ee
    +            throw new UnsupportedOperationException("Unexpected method call!");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        public PropertyDescriptor getPropertyDescriptor(String name) {
    fc09ee
    +            throw new UnsupportedOperationException("Unexpected method call!");
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        public void removePropertyDescriptor(String name) {
    fc09ee
    +            removedProperties.add(name);
    fc09ee
    +        }
    fc09ee
    +
    fc09ee
    +        public Set<String> propertyNames() {
    fc09ee
    +            throw new UnsupportedOperationException("Unexpected method call!");
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +}
    fc09ee
    diff --git a/src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java b/src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java
    fc09ee
    new file mode 100644
    fc09ee
    index 0000000..31ac31c
    fc09ee
    --- /dev/null
    fc09ee
    +++ b/src/test/java/org/apache/commons/beanutils/bugs/Jira463TestCase.java
    fc09ee
    @@ -0,0 +1,48 @@
    fc09ee
    +/*
    fc09ee
    + * Licensed to the Apache Software Foundation (ASF) under one or more
    fc09ee
    + * contributor license agreements.  See the NOTICE file distributed with
    fc09ee
    + * this work for additional information regarding copyright ownership.
    fc09ee
    + * The ASF licenses this file to You under the Apache License, Version 2.0
    fc09ee
    + * (the "License"); you may not use this file except in compliance with
    fc09ee
    + * the License.  You may obtain a copy of the License at
    fc09ee
    + *
    fc09ee
    + *      http://www.apache.org/licenses/LICENSE-2.0
    fc09ee
    + *
    fc09ee
    + * Unless required by applicable law or agreed to in writing, software
    fc09ee
    + * distributed under the License is distributed on an "AS IS" BASIS,
    fc09ee
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    fc09ee
    + * See the License for the specific language governing permissions and
    fc09ee
    + * limitations under the License.
    fc09ee
    + */
    fc09ee
    +package org.apache.commons.beanutils.bugs;
    fc09ee
    +
    fc09ee
    +import junit.framework.TestCase;
    fc09ee
    +
    fc09ee
    +import org.apache.commons.beanutils.AlphaBean;
    fc09ee
    +import org.apache.commons.beanutils.BeanUtilsBean;
    fc09ee
    +import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
    fc09ee
    +
    fc09ee
    +/**
    fc09ee
    + * Class loader vulnerability in DefaultResolver
    fc09ee
    + *
    fc09ee
    + * @version $Id$
    fc09ee
    + * @see https://issues.apache.org/jira/browse/BEANUTILS-463
    fc09ee
    + */
    fc09ee
    +public class Jira463TestCase extends TestCase {
    fc09ee
    +    /**
    fc09ee
    +     * Tests that with a specialized {@code BeanIntrospector} implementation the class
    fc09ee
    +     * property can be suppressed.
    fc09ee
    +     */
    fc09ee
    +    public void testSuppressClassProperty() throws Exception {
    fc09ee
    +        BeanUtilsBean bub = new BeanUtilsBean();
    fc09ee
    +        bub.getPropertyUtils().addBeanIntrospector(
    fc09ee
    +                SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
    fc09ee
    +        AlphaBean bean = new AlphaBean();
    fc09ee
    +        try {
    fc09ee
    +            bub.getProperty(bean, "class");
    fc09ee
    +            fail("Could access class property!");
    fc09ee
    +        } catch (NoSuchMethodException ex) {
    fc09ee
    +            // ok
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +}
    fc09ee
    diff --git a/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java b/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java
    fc09ee
    new file mode 100644
    fc09ee
    index 0000000..2a70811
    fc09ee
    --- /dev/null
    fc09ee
    +++ b/src/test/java/org/apache/commons/beanutils/bugs/Jira520TestCase.java
    fc09ee
    @@ -0,0 +1,39 @@
    fc09ee
    +package org.apache.commons.beanutils.bugs;
    fc09ee
    +
    fc09ee
    +import org.apache.commons.beanutils.AlphaBean;
    fc09ee
    +import org.apache.commons.beanutils.BeanUtilsBean;
    fc09ee
    +import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
    fc09ee
    +
    fc09ee
    +import junit.framework.TestCase;
    fc09ee
    +
    fc09ee
    +/**
    fc09ee
    + * Fix CVE: https://nvd.nist.gov/vuln/detail/CVE-2014-0114
    fc09ee
    + *
    fc09ee
    + * @see https://issues.apache.org/jira/browse/BEANUTILS-520
    fc09ee
    + */
    fc09ee
    +public class Jira520TestCase extends TestCase {
    fc09ee
    +    /**
    fc09ee
    +     * By default opt-in to security that does not allow access to "class".
    fc09ee
    +     */
    fc09ee
    +    public void testSuppressClassPropertyByDefault() throws Exception {
    fc09ee
    +        final BeanUtilsBean bub = new BeanUtilsBean();
    fc09ee
    +        final AlphaBean bean = new AlphaBean();
    fc09ee
    +        try {
    fc09ee
    +            bub.getProperty(bean, "class");
    fc09ee
    +            fail("Could access class property!");
    fc09ee
    +        } catch (final NoSuchMethodException ex) {
    fc09ee
    +            // ok
    fc09ee
    +        }
    fc09ee
    +    }
    fc09ee
    +
    fc09ee
    +    /**
    fc09ee
    +     * Allow opt-out to make your app less secure but allow access to "class".
    fc09ee
    +     */
    fc09ee
    +    public void testAllowAccessToClassProperty() throws Exception {
    fc09ee
    +        final BeanUtilsBean bub = new BeanUtilsBean();
    fc09ee
    +        bub.getPropertyUtils().removeBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
    fc09ee
    +        final AlphaBean bean = new AlphaBean();
    fc09ee
    +        String result = bub.getProperty(bean, "class");
    fc09ee
    +        assertEquals("Class property should have been accessed", "class org.apache.commons.beanutils.AlphaBean", result);
    fc09ee
    +    }
    fc09ee
    +}
    fc09ee
    \ No newline at end of file
    fc09ee
    -- 
    fc09ee
    2.21.0
    fc09ee