This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch javax
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/javax by this push:
new 70438950c TAP5-2779: multiple classloader fixes
70438950c is described below
commit 70438950cf5c4aee41dfc6cdc9e1e7851df18520
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Fri May 17 09:42:21 2024 -0300
TAP5-2779: multiple classloader fixes
---
.../org/apache/tapestry5/plastic/PlasticUtils.java | 81 ++++++++-
.../tapestry5/plastic/PropertyValueProvider.java | 38 +++-
.../apache/tapestry5/plastic/PlasticUtilsTest.java | 37 +++-
.../plastic/test/PlasticUtilsTestObject.java | 17 +-
.../test/PlasticUtilsTestObjectSuperclass.java | 2 +
.../Enumeration.java} | 27 +--
.../services/ComponentInstantiatorSourceImpl.java | 2 +-
.../internal/services/PageSourceImpl.java | 21 ++-
.../tapestry5/internal/transform/CachedWorker.java | 198 +++++++++++++++++++--
.../app1/components/SubclassWithImport.java | 20 ++-
.../app1/components/SuperclassWithImport.java | 33 +++-
.../app1/components/SubclassWithImport.tml | 3 +
12 files changed, 410 insertions(+), 69 deletions(-)
diff --git
a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
index 8dd3c76f1..d36993b1d 100644
--- a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
+++ b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
@@ -25,6 +25,10 @@ import org.apache.tapestry5.internal.plastic.PrimitiveType;
*/
public class PlasticUtils
{
+ private static final String SETTER_METHOD_NAME =
"__propertyValueProvider__set";
+
+ private static final String GETTER_METHOD_NAME =
"__propertyValueProvider__get";
+
/**
* The {@code toString()} method inherited from Object.
*/
@@ -37,14 +41,17 @@ public class PlasticUtils
private static final AtomicLong UID_GENERATOR = new
AtomicLong(System.nanoTime());
- private static final MethodDescription
PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION;
+ private static final MethodDescription
PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION;
+
+ private static final MethodDescription
PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION;
private static final MethodDescription
FIELD_VALUE_PROVIDER_METHOD_DESCRIPTION;
static
{
try {
- PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION = new
MethodDescription(PropertyValueProvider.class.getMethod("__propertyValueProvider__get",
String.class));
+ PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION = new
MethodDescription(PropertyValueProvider.class.getMethod(GETTER_METHOD_NAME,
String.class));
+ PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION = new
MethodDescription(PropertyValueProvider.class.getMethod(SETTER_METHOD_NAME,
String.class, Object.class));
FIELD_VALUE_PROVIDER_METHOD_DESCRIPTION = new
MethodDescription(FieldValueProvider.class.getMethod("__fieldValueProvider__get",
String.class));
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
@@ -250,7 +257,7 @@ public class PlasticUtils
final Set<PlasticMethod> methods =
plasticClass.introduceInterface(PropertyValueProvider.class);
- final InstructionBuilderCallback callback = (builder) -> {
+ final InstructionBuilderCallback getterCallback = (builder) -> {
for (FieldInfo field : fieldInfos)
{
@@ -273,6 +280,9 @@ public class PlasticUtils
}
+ // Field/property not found, so let's try the superclass in case
+ // it also implement
+
builder.loadThis();
builder.instanceOf(PropertyValueProvider.class);
@@ -281,31 +291,86 @@ public class PlasticUtils
builder.loadArgument(0);
ifBuilder.invokeSpecial(
plasticClass.getSuperClassName(),
- PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION);
+ PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION);
ifBuilder.returnResult();
});
+ // Giving up
+
+ builder.throwException(RuntimeException.class, "Property not found
or not supported");
+
+ };
+
+ final InstructionBuilderCallback setterCallback = (builder) -> {
+
+ for (FieldInfo field : fieldInfos)
+ {
+ builder.loadArgument(0);
+ builder.loadConstant(field.name);
+ builder.invokeVirtual(String.class.getName(), "boolean",
"equals", Object.class.getName());
+ builder.when(Condition.NON_ZERO, ifBuilder ->
+ {
+ final String methodName = "set" +
PlasticInternalUtils.capitalize(field.name);
+
+ ifBuilder.loadThis();
+ ifBuilder.loadArgument(1);
+ ifBuilder.castOrUnbox(field.type);
+ ifBuilder.invokeVirtual(
+ plasticClass.getClassName(),
+ void.class.getName(),
+ methodName,
+ field.type);
+ ifBuilder.returnResult();
+ });
+
+ }
+
// Field/property not found, so let's try the superclass in case
// it also implement
+ builder.loadThis();
+ builder.instanceOf(PropertyValueProvider.class);
+
+ builder.when(Condition.NON_ZERO, ifBuilder -> {
+ builder.loadThis();
+ builder.loadArgument(0);
+ builder.loadArgument(1);
+ ifBuilder.invokeSpecial(
+ plasticClass.getSuperClassName(),
+ PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION);
+ ifBuilder.returnResult();
+ });
+
+ // Giving up
+
builder.throwException(RuntimeException.class, "Property not found
or not supported");
};
- final PlasticMethod method;
+ final PlasticMethod getterMethod;
+ final PlasticMethod setterMethod;
// Superclass has already defined this method, so we need to override
it so
// it can also find the subclasses' declared fields/properties.
if (methods.isEmpty())
{
- method =
plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION ,
callback);
+ getterMethod =
plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION,
getterCallback);
+ setterMethod =
plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION,
setterCallback);
}
else
{
- method = methods.iterator().next();
+ getterMethod = methods.stream()
+ .filter(m ->
m.getDescription().methodName.equals(GETTER_METHOD_NAME))
+ .findFirst()
+ .get();
+ setterMethod = methods.stream()
+ .filter(m ->
m.getDescription().methodName.equals(SETTER_METHOD_NAME))
+ .findFirst()
+ .get();
}
- method.changeImplementation(callback);
+ getterMethod.changeImplementation(getterCallback);
+ setterMethod.changeImplementation(setterCallback);
}
diff --git
a/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
b/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
index 3f841b82d..a10ecfeb7 100644
---
a/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
+++
b/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
@@ -1,4 +1,4 @@
-// Copyright 2023 The Apache Software Foundation
+// Copyright 2023, 2024 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -34,7 +34,15 @@ public interface PropertyValueProvider
* @return the field value.
*/
Object __propertyValueProvider__get(String fieldName);
-
+
+ /**
+ * Sets the value of a given field.
+ * @param fieldName the field name.
+ * @param value the field value.
+ * @since 5.8.7
+ */
+ void __propertyValueProvider__set(String fieldName, Object value);
+
/**
* <p>
* Returns the value of a given field in a given object if it belongs to a
class
@@ -60,4 +68,30 @@ public interface PropertyValueProvider
}
}
+ /**
+ * <p>
+ * Sets the value of a given field in a given object if it belongs to a
class
+ * that implements {@linkplain PropertyValueProvider}. Otherwise, it
throws an exception.
+ * </p>
+ * <p>
+ * This is an utility method to avoid having to make casts very time you
need to call
+ * {@linkplain #__propertyValueProvider__set(String)}.
+ * </p>
+ * @param object an object.
+ * @param fieldName the field name.
+ * @param value the field value.
+ * @since 5.8.7
+ */
+ static void set(Object object, String fieldName, Object value)
+ {
+ if (object instanceof PropertyValueProvider)
+ {
+ ((PropertyValueProvider)
object).__propertyValueProvider__set(fieldName, value);
+ }
+ else
+ {
+ throw new RuntimeException("Class " + object.getClass().getName()
+ " doesn't implement " + PropertyValueProvider.class.getSimpleName());
+ }
+ }
+
}
diff --git
a/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
b/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
index 40faac5d2..399ac3960 100644
--- a/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
+++ b/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
@@ -22,8 +22,10 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
+import org.apache.tapestry5.internal.plastic.asm.util.ASMifier;
import org.apache.tapestry5.plastic.test.PlasticUtilsTestObject;
import org.apache.tapestry5.plastic.test.PlasticUtilsTestObjectSuperclass;
+import org.apache.tapestry5.plastic.test_.Enumeration;
import org.junit.jupiter.api.Test;
// [Thiago] This is only here because I couldn't get Groovy tests to run on
Eclipse
@@ -31,11 +33,12 @@ import org.junit.jupiter.api.Test;
public class PlasticUtilsTest
{
-// public static void main(String[] args) throws ClassNotFoundException {
-// final PlasticUtilsTest plasticUtilsTest = new PlasticUtilsTest();
-// plasticUtilsTest.implement_field_value_provider();
-// plasticUtilsTest.implement_property_value_provider();
-// }
+ public static void main(String[] args) throws Exception {
+// ASMifier.main(new String[] {PlasticUtilsTestObject.class.getName(),
"-nodebug"});
+ final PlasticUtilsTest plasticUtilsTest = new PlasticUtilsTest();
+ plasticUtilsTest.implement_field_value_provider();
+ plasticUtilsTest.implement_property_value_provider();
+ }
@Test
public void implement_field_value_provider() throws ClassNotFoundException
@@ -110,6 +113,30 @@ public class PlasticUtilsTest
assertEquals(PlasticUtilsTestObject.TRUE_OF_FALSE, (Boolean)
PropertyValueProvider.get(object, "trueOrFalse"));
assertEquals(PlasticUtilsTestObjectSuperclass.SUPER,
PropertyValueProvider.get(object, "superString"));
+ final String newStringValue = "something else";
+ final String newOtherStringValue = "what?";
+ final String newNullStringValue = "not null anymore";
+ final Enumeration newEnumerationValue = Enumeration.TRUE;
+ final int[] newIntArrayValue = new int[] { 3, 1, 4 };
+ final boolean newTrueOfFalseValue =
!PlasticUtilsTestObject.TRUE_OF_FALSE;
+ final String newSuperStringValue = "Batman";
+
+ PropertyValueProvider.set(object, "string", newStringValue);
+ PropertyValueProvider.set(object, "otherString", newOtherStringValue);
+ PropertyValueProvider.set(object, "nullString", newNullStringValue);
+ PropertyValueProvider.set(object, "enumeration", newEnumerationValue);
+ PropertyValueProvider.set(object, "intArray", newIntArrayValue);
+ PropertyValueProvider.set(object, "trueOrFalse", newTrueOfFalseValue);
+ PropertyValueProvider.set(object, "superString", newSuperStringValue);
+
+ assertEquals(newStringValue, PropertyValueProvider.get(object,
"string"));
+ assertEquals(newOtherStringValue, PropertyValueProvider.get(object,
"otherString"));
+ assertEquals(newNullStringValue, PropertyValueProvider.get(object,
"nullString"));
+ assertEquals(newEnumerationValue.toString(),
PropertyValueProvider.get(object, "enumeration").toString());
+ assertTrue(Arrays.equals(newIntArrayValue, (int[])
PropertyValueProvider.get(object, "intArray")));
+ assertEquals(newTrueOfFalseValue, (Boolean)
PropertyValueProvider.get(object, "trueOrFalse"));
+ assertEquals(newSuperStringValue, PropertyValueProvider.get(object,
"superString"));
+
}
}
diff --git
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
index bd34620ff..484d60ed2 100644
---
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
+++
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
@@ -14,22 +14,29 @@
package org.apache.tapestry5.plastic.test;
+import org.apache.tapestry5.plastic.test_.Enumeration;
+
public class PlasticUtilsTestObject extends PlasticUtilsTestObjectSuperclass
{
public static final String STRING = "A nice string";
public static final int[] INT_ARRAY = new int[] {1, 42};
public static final String OTHER_STRING = "Another nice string";
- public static final Enumeration ENUMERATION =
PlasticUtilsTestObject.Enumeration.FILE_NOT_FOUND;
+ public static final Enumeration ENUMERATION = Enumeration.FILE_NOT_FOUND;
public static final boolean TRUE_OF_FALSE = true;
- public static enum Enumeration
+ public PlasticUtilsTestObject()
{
- TRUE,
- FALSE,
- FILE_NOT_FOUND
}
+ public PlasticUtilsTestObject(String ignored, boolean b)
+ {
+ setString("?");
+ setString(ignored);
+ setTrueOrFalse(b);
+ }
+
+
private String string = STRING;
private String otherString = OTHER_STRING;
diff --git
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
index f82995f88..8a713b18c 100644
---
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
+++
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
@@ -20,6 +20,8 @@ public class PlasticUtilsTestObjectSuperclass
public static final String SUPER = "Super!!!";
private String superString = SUPER;
+
+ private String overridden = "from superclass";
public String getSuperString()
{
diff --git
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
b/plastic/src/test/java/org/apache/tapestry5/plastic/test_/Enumeration.java
similarity index 56%
copy from
plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
copy to
plastic/src/test/java/org/apache/tapestry5/plastic/test_/Enumeration.java
index f82995f88..660161c59 100644
---
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
+++ b/plastic/src/test/java/org/apache/tapestry5/plastic/test_/Enumeration.java
@@ -1,4 +1,4 @@
-// Copyright 2023 The Apache Software Foundation
+// Copyright 2024 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -11,24 +11,11 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+package org.apache.tapestry5.plastic.test_;
-package org.apache.tapestry5.plastic.test;
-
-public class PlasticUtilsTestObjectSuperclass
+public enum Enumeration
{
-
- public static final String SUPER = "Super!!!";
-
- private String superString = SUPER;
-
- public String getSuperString()
- {
- return superString;
- }
-
- public void setSuperString(String superString)
- {
- this.superString = superString;
- }
-
-}
+ TRUE,
+ FALSE,
+ FILE_NOT_FOUND
+}
\ No newline at end of file
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
index 3573b474b..8a5344c72 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
@@ -185,7 +185,7 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
this.tracker = tracker;
this.invalidationHub = invalidationHub;
this.productionMode = productionMode;
- this.multipleClassLoaders = multipleClassLoaders;
+ this.multipleClassLoaders = multipleClassLoaders && !productionMode;
this.resolver = resolver;
this.pageClassLoaderContextManager = pageClassLoaderContextManager;
this.componentDependencyRegistry = componentDependencyRegistry;
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
index 48a004fd8..6c67fe802 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
@@ -17,6 +17,7 @@ package org.apache.tapestry5.internal.services;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -121,7 +122,7 @@ public class PageSourceImpl implements PageSource
this.componentClassResolver = componentClassResolver;
this.productionMode = productionMode;
this.pageCachingReferenceTypeService = pageCachingReferenceTypeService;
- this.multipleClassLoaders = multipleClassLoaders;
+ this.multipleClassLoaders = multipleClassLoaders && !productionMode;
this.pageClassLoaderContextManager = pageClassLoaderContextManager;
this.logger = logger;
}
@@ -134,7 +135,9 @@ public class PageSourceImpl implements PageSource
}
try
{
- return getPage(canonicalPageName, true);
+ @SuppressWarnings("unchecked")
+ Set<String> alreadyProcessed = multipleClassLoaders ? new
HashSet<>() : Collections.EMPTY_SET;
+ return getPage(canonicalPageName, true, alreadyProcessed);
}
finally
{
@@ -145,7 +148,7 @@ public class PageSourceImpl implements PageSource
}
}
- public Page getPage(String canonicalPageName, boolean
invalidateUnknownContext)
+ public Page getPage(String canonicalPageName, boolean
invalidateUnknownContext, Set<String> alreadyProcessed)
{
ComponentResourceSelector selector =
selectorAnalyzer.buildSelectorForRequest();
@@ -178,7 +181,13 @@ public class PageSourceImpl implements PageSource
for (String pageClassName : pageDependencies)
{
- page =
getPage(componentClassResolver.resolvePageClassNameToPageName(pageClassName),
false);
+ // Avoiding infinite recursion caused by circular
dependencies
+ if (!alreadyProcessed.contains(pageClassName))
+ {
+ alreadyProcessed.add(pageClassName);
+ page =
getPage(componentClassResolver.resolvePageClassNameToPageName(pageClassName),
+ invalidateUnknownContext, alreadyProcessed);
+ }
}
}
@@ -203,7 +212,7 @@ public class PageSourceImpl implements PageSource
{
final ComponentPageElement rootElement = page.getRootElement();
componentDependencyRegistry.clear(rootElement);
- componentDependencyRegistry.register(rootElement);
+
componentDependencyRegistry.register(rootElement.getComponent().getClass());
PageClassLoaderContext context =
pageClassLoaderContextManager.get(className);
if (context.isUnknown() && multipleClassLoaders)
@@ -216,7 +225,7 @@ public class PageSourceImpl implements PageSource
}
context.getClassNames().clear();
// Avoiding bad invalidations
- return getPage(canonicalPageName, false);
+ return getPage(canonicalPageName, false, alreadyProcessed);
}
}
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 7a7fbe38f..e5848dcd3 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
@@ -24,6 +24,9 @@ import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.ioc.services.PerThreadValue;
import org.apache.tapestry5.ioc.services.PerthreadManager;
+import org.apache.tapestry5.json.JSONArray;
+import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.model.ComponentModel;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.plastic.*;
import org.apache.tapestry5.plastic.PlasticUtils.FieldInfo;
@@ -33,9 +36,15 @@ import org.apache.tapestry5.services.TransformConstants;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.TransformationSupport;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Caches method return values for methods annotated with {@link Cached}.
@@ -43,7 +52,25 @@ import java.util.Set;
@SuppressWarnings("all")
public class CachedWorker implements ComponentClassTransformWorker2
{
+ private static final String WATCH_BINDING_PREFIX = "cache$watchBinding$";
+
private static final String FIELD_PREFIX = "cache$";
+
+ private static final String META_PROPERTY = "cachedWorker";
+
+ private static final String MODIFIERS = "modifiers";
+
+ private static final String RETURN_TYPE = "returnType";
+
+ private static final String NAME = "name";
+
+ private static final String GENERIC_SIGNATURE = "genericSignature";
+
+ private static final String ARGUMENT_TYPES = "argumentTypes";
+
+ private static final String CHECKED_EXCEPTION_TYPES =
"checkedExceptionTypes";
+
+ private static final String WATCH = "watch";
private final BindingSource bindingSource;
@@ -143,14 +170,39 @@ public class CachedWorker implements
ComponentClassTransformWorker2
public void transform(PlasticClass plasticClass, TransformationSupport
support, MutableComponentModel model)
{
- List<PlasticMethod> methods =
plasticClass.getMethodsWithAnnotation(Cached.class);
- Set<PlasticUtils.FieldInfo> fieldInfos = multipleClassLoaders ? new
HashSet<>() : null;
+ final List<PlasticMethod> methods =
plasticClass.getMethodsWithAnnotation(Cached.class);
+ final Set<PlasticUtils.FieldInfo> fieldInfos = multipleClassLoaders ?
new HashSet<>() : null;
+ final Map<String, String> extraMethodCachedWatchMap =
multipleClassLoaders ? new HashMap<>() : null;
+
+ if (multipleClassLoaders)
+ {
+
+ // Store @Cache-annotated methods information so subclasses can
+ // know about them.
+
+ model.setMeta(META_PROPERTY,
toJSONArray(methods).toCompactString());
+
+ // Use the information from superclasses
+
+ ComponentModel parentModel = model.getParentModel();
+ Set<PlasticMethod> extraMethods = new HashSet<>();
+ while (parentModel != null)
+ {
+ extraMethods.addAll(
+ toPlasticMethodList(
+ parentModel.getMeta(META_PROPERTY),
plasticClass, extraMethodCachedWatchMap));
+ parentModel = parentModel.getParentModel();
+ }
+
+ methods.addAll(extraMethods);
+
+ }
for (PlasticMethod method : methods)
{
validateMethod(method);
- adviseMethod(plasticClass, method, fieldInfos);
+ adviseMethod(plasticClass, method, fieldInfos, model,
extraMethodCachedWatchMap);
}
if (multipleClassLoaders && !fieldInfos.isEmpty())
@@ -158,8 +210,70 @@ public class CachedWorker implements
ComponentClassTransformWorker2
this.propertyValueProviderWorker.add(plasticClass, fieldInfos);
}
}
+
+ private Collection<PlasticMethod> toPlasticMethodList(String meta,
PlasticClass plasticClass,
+ Map<String, String> extraMethodCachedWatchMap)
+ {
+ final JSONArray array = new JSONArray(meta);
+ List<PlasticMethod> methods = new ArrayList<>(array.size());
+ for (int i = 0; i < array.size(); i++)
+ {
+ final JSONObject jsonObject = array.getJSONObject(i);
+ methods.add(toPlasticMethod(jsonObject, plasticClass,
extraMethodCachedWatchMap));
+ }
+ return methods;
+ }
+
+
+ private static PlasticMethod toPlasticMethod(JSONObject jsonObject,
PlasticClass plasticClass,
+ Map<String, String> extraMethodCachedWatchMap)
+ {
+ final int modifiers = jsonObject.getInt(MODIFIERS);
+ final String returnType = jsonObject.getString(RETURN_TYPE);
+ final String methodName = jsonObject.getString(NAME);
+ final String genericSignature =
jsonObject.getStringOrDefault(GENERIC_SIGNATURE, null);
+ final JSONArray argumentTypesArray =
jsonObject.getJSONArray(ARGUMENT_TYPES);
+ final String[] argumentTypes = argumentTypesArray.stream()
+ .collect(Collectors.toList()).toArray(new
String[argumentTypesArray.size()]);
+ final JSONArray checkedExceptionTypesArray =
jsonObject.getJSONArray(CHECKED_EXCEPTION_TYPES);
+ final String[] checkedExceptionTypes =
checkedExceptionTypesArray.stream()
+ .collect(Collectors.toList()).toArray(new
String[checkedExceptionTypesArray.size()]);
+
+ if (!extraMethodCachedWatchMap.containsKey(methodName))
+ {
+ extraMethodCachedWatchMap.put(methodName,
jsonObject.getString(WATCH));
+ }
+
+ return plasticClass.introduceMethod(new MethodDescription(
+ modifiers, returnType, methodName, argumentTypes,
+ genericSignature, checkedExceptionTypes));
+ }
+
+ private static JSONArray toJSONArray(List<PlasticMethod> methods)
+ {
+ final JSONArray array = new JSONArray();
+ for (PlasticMethod method : methods)
+ {
+ array.add(toJSONObject(method));
+ }
+ return array;
+ }
+
+ private static JSONObject toJSONObject(PlasticMethod method)
+ {
+ final MethodDescription description = method.getDescription();
+ return new JSONObject(
+ MODIFIERS, description.modifiers,
+ RETURN_TYPE, description.returnType,
+ NAME, description.methodName,
+ GENERIC_SIGNATURE, description.genericSignature,
+ ARGUMENT_TYPES, new JSONArray(description.argumentTypes),
+ CHECKED_EXCEPTION_TYPES, new
JSONArray(description.checkedExceptionTypes),
+ WATCH, method.getAnnotation(Cached.class).watch());
+ }
- private void adviseMethod(PlasticClass plasticClass, PlasticMethod method,
Set<FieldInfo> fieldInfos)
+ private void adviseMethod(PlasticClass plasticClass, PlasticMethod method,
Set<FieldInfo> fieldInfos,
+ MutableComponentModel model, Map<String, String>
extraMethodCachedWatchMap)
{
// Every instance of the class requires its own per-thread value. This
handles the case of multiple
// pages containing the component, or the same page containing the
component multiple times.
@@ -184,7 +298,10 @@ public class CachedWorker implements
ComponentClassTransformWorker2
Cached annotation = method.getAnnotation(Cached.class);
- MethodResultCacheFactory factory = createFactory(plasticClass,
annotation.watch(), method);
+ final String expression = annotation != null ?
+ annotation.watch() :
+
extraMethodCachedWatchMap.get(method.getDescription().methodName);
+ MethodResultCacheFactory factory = createFactory(plasticClass,
expression, method, fieldInfos, model);
MethodAdvice advice = createAdvice(cacheField, factory);
@@ -192,17 +309,28 @@ public class CachedWorker implements
ComponentClassTransformWorker2
}
private String getFieldName(PlasticMethod method) {
- final StringBuilder builder = new StringBuilder(FIELD_PREFIX);
- builder.append(method.getDescription().methodName);
+ return getFieldName(method, FIELD_PREFIX);
+ }
+
+ private String getFieldName(PlasticMethod method, String prefix)
+ {
+ final String methodName = method.getDescription().methodName;
+ final String className = method.getPlasticClass().getClassName();
+ return getFieldName(prefix, methodName, className);
+ }
+
+ private String getFieldName(String prefix, final String methodName, final
String className)
+ {
+ final StringBuilder builder = new StringBuilder(prefix);
+ builder.append(methodName);
if (multipleClassLoaders)
{
builder.append("_");
-
builder.append(method.getPlasticClass().getClassName().replace('.', '_'));
+ builder.append(className.replace('.', '_'));
}
return builder.toString();
}
-
private MethodAdvice createAdvice(PlasticField cacheField,
final MethodResultCacheFactory factory)
{
@@ -255,7 +383,8 @@ public class CachedWorker implements
ComponentClassTransformWorker2
private MethodResultCacheFactory createFactory(PlasticClass plasticClass,
final String watch,
- PlasticMethod method)
+ PlasticMethod method,
Set<FieldInfo> fieldInfos,
+ MutableComponentModel model)
{
// When there's no watch, a shared factory that just returns a new
SimpleMethodResultCache
// will suffice.
@@ -266,8 +395,23 @@ public class CachedWorker implements
ComponentClassTransformWorker2
// Because of the watch, its necessary to create a factory for
instances of this component and method.
- final FieldHandle bindingFieldHandle =
plasticClass.introduceField(Binding.class, "cache$watchBinding$" +
method.getDescription().methodName).getHandle();
-
+ final String bindingFieldName = WATCH_BINDING_PREFIX +
method.getDescription().methodName;
+ final PlasticField bindingField =
plasticClass.introduceField(Binding.class, bindingFieldName);
+ final FieldHandle bindingFieldHandle = bindingField.getHandle();
+
+ if (multipleClassLoaders)
+ {
+ fieldInfos.add(PlasticUtils.toFieldInfo(bindingField));
+ try
+ {
+ bindingField.createAccessors(PropertyAccessType.READ_WRITE);
+ }
+ catch (IllegalArgumentException e)
+ {
+ // Method already implemented in superclass, so, given we only
+ // care the method exists, we ignore this exception
+ }
+ }
// Each component instance will get its own Binding instance. That
handles both different locales,
// and reuse of a component (with a cached method) within a page or
across pages. However, the binding can't be initialized
@@ -283,7 +427,15 @@ public class CachedWorker implements
ComponentClassTransformWorker2
Binding binding = bindingSource.newBinding("@Cached watch",
resources,
BindingConstants.PROP, watch);
- bindingFieldHandle.set(invocation.getInstance(), binding);
+ final Object instance = invocation.getInstance();
+ if (multipleClassLoaders)
+ {
+ PropertyValueProvider.set(instance, bindingFieldName,
binding);
+ }
+ else
+ {
+ bindingFieldHandle.set(instance, binding);
+ }
invocation.proceed();
}
@@ -293,10 +445,26 @@ public class CachedWorker implements
ComponentClassTransformWorker2
{
public MethodResultCache create(Object instance)
{
- Binding binding = (Binding) bindingFieldHandle.get(instance);
-
+ Binding binding = (Binding) (
+ multipleClassLoaders ?
+ PropertyValueProvider.get(instance, bindingFieldName) :
+ bindingFieldHandle.get(instance));
+
return new WatchedBindingMethodResultCache(binding);
}
+
+ private Object getCacheBinding(final String methodName, String
bindingFieldName, Object instance, ComponentModel model)
+ {
+ Object value = PropertyValueProvider.get(instance,
bindingFieldName);
+ while (value == null && model.getParentModel() != null)
+ {
+ model = model.getParentModel();
+ bindingFieldName = getFieldName(WATCH_BINDING_PREFIX,
+ methodName, model.getComponentClassName());
+ value = PropertyValueProvider.get(instance,
bindingFieldName);
+ }
+ return value;
+ }
};
}
diff --git
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
index 9b865bef8..286afb0d5 100644
---
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
+++
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
@@ -4,23 +4,37 @@ import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Cached;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.Persist;
+import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Zone;
@Import(stylesheet = "context:css/ie-only.css")
public class SubclassWithImport extends SuperclassWithImport {
+ @Property int getIntCallCount;
+
// Just to test a bug happening when a parameter is an array type
@Parameter
private Zone[][][][][] zones;
- @Cached
- public int getInt()
+ // Just to test CachedWorker with a watch expression
+ @Cached(watch = "counter")
+ public String getInt()
{
- return 2;
+ getIntCallCount++;
+ return getCounter() + " from subclass";
+ }
+
+ @Cached(watch = "counter")
+ public String getOther()
+ {
+ return getCounter() + " " + " from superclass";
}
public void setupRender(MarkupWriter writer) {
+ getIntCallCount = 0;
writer.element("p").text("Int: " + getInt() + " : " + getInt());
writer.end();
}
+
}
diff --git
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
index 2de9d1793..a03c1cde3 100644
---
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
+++
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
@@ -3,19 +3,44 @@ package org.apache.tapestry5.integration.app1.components;
import org.apache.tapestry5.MarkupWriter;
import org.apache.tapestry5.annotations.Cached;
import org.apache.tapestry5.annotations.Import;
+import org.apache.tapestry5.annotations.Persist;
@Import(stylesheet = "context:css/via-import.css")
public class SuperclassWithImport {
- @Cached
- public int getOther()
+ // Just to test CachedWorker with a watch expression
+ @Persist
+ private int counter;
+
+ @Persist
+ private int secondCounter;
+
+ @Cached(watch = "counter")
+ public String getOther()
+ {
+ return getCounter() + " " + " from superclass";
+ }
+
+ @Cached(watch = "secondCounter")
+ public String getNonOverriden()
{
- return 4;
+ return getCounter() + " " + " non-overriden";
}
public void cleanupRender(MarkupWriter writer) {
- writer.element("p").text("Other: " + getOther() + " : " + getOther());
+ writer.element("p").text("Other: " + getOther() + " : " + getOther() +
+ " nonOverriden " + getNonOverriden() + " nonOverriden " +
getNonOverriden());
+ writer.write(" yeah!!!");
writer.end();
+ counter++;
+ }
+
+ public int getCounter() {
+ return counter;
+ }
+
+ public int getSecondCounter() {
+ return secondCounter;
}
}
diff --git
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
index bc9d802cf..23f7b97da 100644
---
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
+++
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
@@ -3,6 +3,9 @@
<t:extend xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"
xmlns:p="tapestry:parameter">
<t:replace id="details">
Subclass with imports
+ <t:loop source="1..10">
+ getInt() call count: ${getIntCallCount} <br/>
+ </t:loop>
<!-- Just for testing ComponentDependencyRegistry.register() -->
<t:outputRaw value="literal:"/>
</t:replace>