You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2026/03/29 19:53:43 UTC
(tapestry-5) 03/03: TAP5-2813: NPE when @Cached applied to method returning a generic type
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 <th...@arsmachina.com.br>
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; }
}