This is an automated email from the ASF dual-hosted git repository. thiagohp pushed a commit to branch 5.9.x-javax in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
commit bffc3c8539a4964e0b2fed5e7589a5fd15232843 Author: Thiago H. de Paula Figueiredo <[email protected]> AuthorDate: Sat Oct 4 17:51:07 2025 -0300 TAP5-2813: NPE when @Cached applied to method returning a generic type in multiple classloader mode --- .../internal/GuavaGenericsResolver.java | 2 +- .../services/ComponentModelSourceImpl.java | 10 ++++++++-- .../tapestry5/internal/transform/CachedWorker.java | 8 +++++++- tapestry-core/src/test/app1/CachedGenericsDemo.tml | 3 +++ .../tapestry5/integration/app1/CacheTests.java | 7 +++++++ .../tapestry5/integration/app1/GenericsClass.java | 6 ++++++ .../tapestry5/integration/app1/GenericsEntity.java | 23 ++++++++++++++++++++++ .../app1/base/AbstractCachedGenerics.java | 20 +++++++++++++++++++ .../app1/components/CachedGenerics.java | 18 +++++++++++++++++ .../integration/app1/pages/CachedGenericsDemo.java | 5 +++++ .../tapestry5/integration/app1/pages/Index.java | 4 +++- .../app1/base/AbstractCachedGenerics.tml | 11 +++++++++++ .../groovy/ioc/specs/ChainBuilderImplSpec.groovy | 2 +- 13 files changed, 113 insertions(+), 6 deletions(-) diff --git a/genericsresolver-guava/src/main/java/org/apache/tapestry5/genericsresolverguava/internal/GuavaGenericsResolver.java b/genericsresolver-guava/src/main/java/org/apache/tapestry5/genericsresolverguava/internal/GuavaGenericsResolver.java index 65338b23e..b5cf116e3 100644 --- a/genericsresolver-guava/src/main/java/org/apache/tapestry5/genericsresolverguava/internal/GuavaGenericsResolver.java +++ b/genericsresolver-guava/src/main/java/org/apache/tapestry5/genericsresolverguava/internal/GuavaGenericsResolver.java @@ -22,7 +22,7 @@ import org.apache.tapestry5.genericsresolverguava.internal.GuavaGenericsResolver import com.google.common.reflect.TypeToken; /** - * {@link GuavaGenericsResolver} implementation using Guava. + * {@link GenericsResolver} implementation using Guava. */ public class GuavaGenericsResolver implements GenericsResolver { diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java index dcbfbc47e..bec14d236 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentModelSourceImpl.java @@ -14,6 +14,7 @@ package org.apache.tapestry5.internal.services; +import java.util.HashSet; import java.util.Set; import org.apache.tapestry5.SymbolConstants; @@ -33,6 +34,9 @@ public class ComponentModelSourceImpl implements ComponentModelSource private final PageSource pageSource; private final boolean multipleClassLoaders; + + private final static ThreadLocal<Set<String>> CALL_STACK = + ThreadLocal.withInitial(HashSet::new); public ComponentModelSourceImpl(ComponentClassResolver resolver, ComponentInstantiatorSource source, ComponentDependencyRegistry componentDependencyRegistry, @@ -51,15 +55,16 @@ public class ComponentModelSourceImpl implements ComponentModelSource { if (multipleClassLoaders && isPage(componentClassName)) { - final Set<String> superclasses = componentDependencyRegistry.getDependencies( componentClassName, DependencyType.SUPERCLASS); if (!superclasses.isEmpty()) { final String superclass = superclasses.iterator().next(); - if (isPage(superclass)) + final Set<String> callStack = CALL_STACK.get(); + if (!callStack.contains(superclass) && isPage(superclass)) { + callStack.add(superclass); getModel(superclass); try { @@ -73,6 +78,7 @@ public class ComponentModelSourceImpl implements ComponentModelSource // so the objective of the line above is already // fulfilled and we can safely ignore the exception } + callStack.remove(superclass); } } } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java index 045188d09..643abdd63 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java @@ -274,11 +274,17 @@ public class CachedWorker implements ComponentClassTransformWorker2 private static JSONObject toJSONObject(PlasticMethod method) { final MethodDescription description = method.getDescription(); + + // TAP5-2813 + final String genericSignature = description.genericSignature != null ? + description.genericSignature.replaceAll("<[^>]+>", "") : null; + + return new JSONObject( MODIFIERS, description.modifiers, RETURN_TYPE, description.returnType, NAME, description.methodName, - GENERIC_SIGNATURE, description.genericSignature, + GENERIC_SIGNATURE, genericSignature, ARGUMENT_TYPES, new JSONArray(description.argumentTypes), CHECKED_EXCEPTION_TYPES, new JSONArray(description.checkedExceptionTypes), WATCH, method.getAnnotation(Cached.class).watch()); diff --git a/tapestry-core/src/test/app1/CachedGenericsDemo.tml b/tapestry-core/src/test/app1/CachedGenericsDemo.tml new file mode 100644 index 000000000..9f5b46817 --- /dev/null +++ b/tapestry-core/src/test/app1/CachedGenericsDemo.tml @@ -0,0 +1,3 @@ +<html t:type="Border" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"> + <t:cachedGenerics/> +</html> \ No newline at end of file diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CacheTests.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CacheTests.java index 6dccd8301..e7c2381af 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CacheTests.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/CacheTests.java @@ -66,4 +66,11 @@ public class CacheTests extends App1TestCase "Method org.apache.tapestry5.integration.app1.pages.ParamsMethodWithCached.invalidMethod(java.lang.String)", "may not be used with @Cached because it has parameters."); } + + @Test + public void at_cached_at_method_returning_generic_types() + { + // Fails without the fix + openLinks("@Cached on method returning type with generics"); + } } diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/GenericsClass.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/GenericsClass.java new file mode 100644 index 000000000..dd004b952 --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/GenericsClass.java @@ -0,0 +1,6 @@ +package org.apache.tapestry5.integration.app1; + + +public class GenericsClass<T, H> { + +} diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/GenericsEntity.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/GenericsEntity.java new file mode 100644 index 000000000..32123c66e --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/GenericsEntity.java @@ -0,0 +1,23 @@ +package org.apache.tapestry5.integration.app1; + +public class GenericsEntity<T> { + + private final String id_; + + private final String label_; + + public GenericsEntity(String id, String label) { + id_ = id; + label_ = label; + } + + @SuppressWarnings("unused") + private String getId() { + return id_; + } + + @SuppressWarnings("unused") + private String getLabel() { + return label_; + } +} diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/base/AbstractCachedGenerics.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/base/AbstractCachedGenerics.java new file mode 100644 index 000000000..053279e6d --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/base/AbstractCachedGenerics.java @@ -0,0 +1,20 @@ +package org.apache.tapestry5.integration.app1.base; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.tapestry5.annotations.Cached; +import org.apache.tapestry5.integration.app1.GenericsClass; + +public abstract class AbstractCachedGenerics<T, H> +{ + + protected abstract GenericsClass<T, H> createTable(List<T> itemsInCurrentPage); + + @Cached + public GenericsClass<T, H> getEmptyTable() + { + return createTable(new ArrayList<>()); + } + +} diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/CachedGenerics.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/CachedGenerics.java new file mode 100644 index 000000000..8e0fef614 --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/CachedGenerics.java @@ -0,0 +1,18 @@ +package org.apache.tapestry5.integration.app1.components; + +import java.util.List; + +import org.apache.tapestry5.integration.app1.GenericsClass; +import org.apache.tapestry5.integration.app1.GenericsEntity; +import org.apache.tapestry5.integration.app1.base.AbstractCachedGenerics; + +public class CachedGenerics extends AbstractCachedGenerics<GenericsEntity, String> +{ + + @Override + protected GenericsClass<GenericsEntity, String> createTable(List<GenericsEntity> itemsInCurrentPage) + { + return new GenericsClass<>(); + } + +} diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/CachedGenericsDemo.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/CachedGenericsDemo.java new file mode 100644 index 000000000..1e4cfa4b0 --- /dev/null +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/CachedGenericsDemo.java @@ -0,0 +1,5 @@ +package org.apache.tapestry5.integration.app1.pages; + +public class CachedGenericsDemo +{ +} diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java index 025b7eb07..b9ddb8d32 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java @@ -630,7 +630,9 @@ public class Index new Item("RecursiveDemo","Recursive Demo","Recursive component example"), - new Item("SelfRecursiveDemo", "Self-Recursive Demo", "check for handling of self-recursive components") + new Item("SelfRecursiveDemo", "Self-Recursive Demo", "check for handling of self-recursive components"), + + new Item("CachedGenericsDemo", "@Cached on method returning type with generics", "tests fix for TAP5-2813") ); static diff --git a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/base/AbstractCachedGenerics.tml b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/base/AbstractCachedGenerics.tml new file mode 100644 index 000000000..87b8639d0 --- /dev/null +++ b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/base/AbstractCachedGenerics.tml @@ -0,0 +1,11 @@ +<div xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" + xmlns:p="tapestry:parameter"> + + <p> + ${EmptyTable} + </p> + + <p> + ${EmptyTable} + </p> +</div> \ No newline at end of file diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/ChainBuilderImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/ChainBuilderImplSpec.groovy index 12ff3a7dc..4277cd0e9 100644 --- a/tapestry-ioc/src/test/groovy/ioc/specs/ChainBuilderImplSpec.groovy +++ b/tapestry-ioc/src/test/groovy/ioc/specs/ChainBuilderImplSpec.groovy @@ -118,7 +118,7 @@ class ChainBuilderImplSpec extends AbstractSharedRegistrySpecification { chain.toString() == "<Command chain of ioc.specs.ChainCommand>" } - final private static class InterfaceWithStaticMethodImpl extends InterfaceWithStaticMethod + final private static class InterfaceWithStaticMethodImpl implements InterfaceWithStaticMethod { public int something() { return 2; } }
