From a726f858e7ea8d5b45c00a1045a2ab995ee1d6d5 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Sun, 14 Apr 2024 22:27:16 +0200 Subject: [PATCH] BEANUTILS-541 - FluentPropertyBeanIntrospector caches corrupted writeMethod (parallel) (#234) * BEANUTILS-541 - FluentPropertyBeanIntrospector caches corrupted writeMethod (parallel) * Fix import order (Checkstyle) --------- Co-authored-by: Gary Gregory --- .../FluentPropertyBeanIntrospector.java | 17 ++++------ .../beanutils2/bugs/Jira541TestCase.java | 31 ++++++++++++++++++- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java index 21eb417d1..e8bb593c8 100644 --- a/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java +++ b/src/main/java/org/apache/commons/beanutils2/FluentPropertyBeanIntrospector.java @@ -154,11 +154,13 @@ public void introspect(final IntrospectionContext icontext) icontext.addPropertyDescriptor(createFluentPropertyDescritor( m, propertyName)); } else if (pd.getWriteMethod() == null) { - // We change statically cached PropertyDescriptor, it may affect - // other subclasses of targetClass supertype. + // We should not change statically cached PropertyDescriptor as it can be from super-type, + // it may affect other subclasses of targetClass supertype. // See BEANUTILS-541 for more details. - clearDescriptorsCacheHierarchy(icontext.getTargetClass().getSuperclass()); - pd.setWriteMethod(m); + PropertyDescriptor fluentPropertyDescriptor = new PropertyDescriptor( + pd.getName(), pd.getReadMethod(), m); + // replace existing (possibly inherited from super-class) to one specific to current class + icontext.addPropertyDescriptor(fluentPropertyDescriptor); } } catch (final IntrospectionException e) { if (log.isDebugEnabled()) { @@ -170,13 +172,6 @@ public void introspect(final IntrospectionContext icontext) } } - private static void clearDescriptorsCacheHierarchy(Class cls) { - if (cls != null && cls != Object.class) { - Introspector.flushFromCaches(cls); - clearDescriptorsCacheHierarchy(cls.getSuperclass()); - } - } - /** * Derives the name of a property from the given set method. * diff --git a/src/test/java/org/apache/commons/beanutils2/bugs/Jira541TestCase.java b/src/test/java/org/apache/commons/beanutils2/bugs/Jira541TestCase.java index a8fac5d3b..16edece09 100644 --- a/src/test/java/org/apache/commons/beanutils2/bugs/Jira541TestCase.java +++ b/src/test/java/org/apache/commons/beanutils2/bugs/Jira541TestCase.java @@ -16,7 +16,13 @@ */ package org.apache.commons.beanutils2.bugs; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import org.apache.commons.beanutils2.FluentPropertyBeanIntrospector; import org.apache.commons.beanutils2.PropertyUtilsBean; @@ -34,6 +40,29 @@ public class Jira541TestCase { @Test public void testFluentBeanIntrospectorOnOverriddenSetter() throws Exception { + testImpl(); + } + + @Test + public void testFluentBeanIntrospectorOnOverriddenSetterConcurrent() throws Exception { + ExecutorService executionService = Executors.newFixedThreadPool(256); + try { + List> futures = new ArrayList<>(); + for (int i = 0; i < 10000; i++) { + futures.add(executionService.submit(() -> { + testImpl(); + return null; + })); + } + for (Future future : futures) { + future.get(); + } + } finally { + executionService.shutdown(); + } + } + + private static void testImpl() throws ReflectiveOperationException { PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); propertyUtilsBean.addBeanIntrospector(new FluentPropertyBeanIntrospector());