This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch better-page-invalidation
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/better-page-invalidation by
this push:
new 52113b4ca TAP5-2742: first pass at multiple classloader support
52113b4ca is described below
commit 52113b4ca244f8f8acac6447d5c10b2b49b3b07c
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Thu Feb 16 01:03:18 2023 -0300
TAP5-2742: first pass at multiple classloader support
for live class reloading
---
583_RELEASE_NOTES.md | 1 +
.../services/PropertyConduitSourceImpl.java | 2 +-
.../services/PlasticProxyFactoryImpl.java | 6 +
.../commons/services/PlasticProxyFactory.java | 21 +-
.../apache/tapestry5/plastic/PlasticManager.java | 11 ++
.../internal/services/ComponentClassCacheImpl.java | 2 +-
.../services/ComponentDependencyRegistry.java | 22 ++-
.../services/ComponentDependencyRegistryImpl.java | 37 +++-
.../services/ComponentInstantiatorSourceImpl.java | 215 ++++++++++++++++++---
.../internal/services/PageSourceImpl.java | 33 ++--
.../apache/tapestry5/modules/PageLoadModule.java | 3 +
.../services/pageload/PageClassloaderContext.java | 211 ++++++++++++++++++++
.../pageload/PageClassloaderContextManager.java | 57 ++++++
.../PageClassloaderContextManagerImpl.java | 153 +++++++++++++++
.../tapestry5/services/pageload/package-info.java | 2 +-
.../ComponentDependencyRegistryImplTest.java | 52 ++++-
16 files changed, 767 insertions(+), 61 deletions(-)
diff --git a/583_RELEASE_NOTES.md b/583_RELEASE_NOTES.md
index 577db3087..e7923cc55 100644
--- a/583_RELEASE_NOTES.md
+++ b/583_RELEASE_NOTES.md
@@ -6,6 +6,7 @@ Scratch pad for changes destined for the 5.8.3 release notes
page.
* getChangeResourcesMemos() to URLChangeTracker
* getValues() to MultiKey
* New getLogicalName() method in ComponentClassResolver.
+* New getPlasticManager() method in PlasticProxyFactory
# Non-backward-compatible changes (but that probably won't cause problems)
diff --git
a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
index 8a304a5ce..66f106660 100644
---
a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
+++
b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
@@ -1524,7 +1524,7 @@ public class PropertyConduitSourceImpl implements
PropertyConduitSource
break;
}
- return proxyFactory.createProxy(InternalPropertyConduit.class,
+ return
proxyFactory.getProxyFactory(rootClass.getName()).createProxy(InternalPropertyConduit.class,
new PropertyConduitBuilder(rootClass, expression,
tree)).newInstance();
} catch (Exception ex)
{
diff --git
a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/services/PlasticProxyFactoryImpl.java
b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/services/PlasticProxyFactoryImpl.java
index abc5c762c..1e6a50785 100644
---
a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/services/PlasticProxyFactoryImpl.java
+++
b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/services/PlasticProxyFactoryImpl.java
@@ -297,4 +297,10 @@ public class PlasticProxyFactoryImpl implements
PlasticProxyFactory
manager.removePlasticClassListener(listener);
}
+ @Override
+ public PlasticManager getPlasticManager()
+ {
+ return manager;
+ }
+
}
diff --git
a/commons/src/main/java/org/apache/tapestry5/commons/services/PlasticProxyFactory.java
b/commons/src/main/java/org/apache/tapestry5/commons/services/PlasticProxyFactory.java
index 3b8b9bc47..0237cc7a4 100644
---
a/commons/src/main/java/org/apache/tapestry5/commons/services/PlasticProxyFactory.java
+++
b/commons/src/main/java/org/apache/tapestry5/commons/services/PlasticProxyFactory.java
@@ -1,4 +1,4 @@
-// Copyright 2011, 2012 The Apache Software Foundation
+// Copyright 2011, 2012, 2023 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.
@@ -24,6 +24,7 @@ import org.apache.tapestry5.plastic.ClassInstantiator;
import org.apache.tapestry5.plastic.PlasticClassListenerHub;
import org.apache.tapestry5.plastic.PlasticClassTransformation;
import org.apache.tapestry5.plastic.PlasticClassTransformer;
+import org.apache.tapestry5.plastic.PlasticManager;
/**
* A service used to create proxies of varying types. As a secondary concern,
manages to identify the
@@ -169,6 +170,24 @@ public interface PlasticProxyFactory extends
PlasticClassListenerHub
*
* @since 5.3.3
*/
+ @IncompatibleChange(release = "5.3.3", details = "Added method")
void clearCache();
+
+ /**
+ * Returns the {@linkplain PlasticManager} instance used by this
PlasticProxyFactory.
+ * @since 5.8.3
+ */
+ @IncompatibleChange(release = "5.8.3", details = "Added method")
+ PlasticManager getPlasticManager();
+
+ /**
+ * Returns the {@linkplain PlasticProxyFactory} instance to be used for a
given class.
+ * Default implementation returns <code>this</code>.
+ * @since 5.8.3
+ */
+ default PlasticProxyFactory getProxyFactory(String className)
+ {
+ return this;
+ }
}
diff --git
a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java
b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java
index 74acf21c1..1d42bd6b7 100644
--- a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java
+++ b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java
@@ -345,4 +345,15 @@ public class PlasticManager implements
PlasticClassListenerHub
{
pool.removePlasticClassListener(listener);
}
+
+ /**
+ * Returns whether a given class will have it classloading intercepted for
+ * live class reloading.
+ * @since 5.8.3
+ */
+ public boolean shouldInterceptClassLoading(String className)
+ {
+ return pool.shouldInterceptClassLoading(className);
+ }
+
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassCacheImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassCacheImpl.java
index 349189707..504f612ee 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassCacheImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassCacheImpl.java
@@ -105,7 +105,7 @@ public class ComponentClassCacheImpl implements
ComponentClassCache
private Class lookupClassForType(String className)
{
- ClassLoader componentLoader = plasticFactory.getClassLoader();
+ ClassLoader componentLoader =
plasticFactory.getProxyFactory(className).getClassLoader();
try
{
return PlasticInternalUtils.toClass(componentLoader, className);
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
index dfa89611d..c4ab81a6f 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistry.java
@@ -33,6 +33,12 @@ import org.apache.tapestry5.plastic.PlasticField;
* @since 5.8.3
*/
public interface ComponentDependencyRegistry {
+
+ /**
+ * Name of the file where the dependency information is stored between
webapp runs.
+ */
+ String FILENAME = "tapestryComponentDependencies.json";
+
/**
* Register all the dependencies of a given component.
@@ -80,10 +86,20 @@ public interface ComponentDependencyRegistry {
* @see #FILENAME
*/
void writeFile();
-
+
/**
- * Name of the file where the dependency information is stored between
webapp runs.
+ * Tells whether this registry already contans a given class name.
*/
- String FILENAME = "tapestryComponentDependencies.json";
+ boolean contains(String className);
+
+ /**
+ * Returns the set of all class names in the registry.
+ */
+ Set<String> getClassNames();
+
+ /**
+ * Returns the set of all root classes (i.e. ones with no dependencies).
+ */
+ Set<String> getRootClasses();
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
index 1765985b7..08689c4f3 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImpl.java
@@ -242,8 +242,20 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
add(getClassName(component), getClassName(dependency));
}
- // Protected just for testing
- void add(String component, String dependency)
+ // Just for unit tests
+ void add(String component, String dependency, boolean
markAsAlreadyProcessed)
+ {
+ if (markAsAlreadyProcessed)
+ {
+ alreadyProcessed.add(component);
+ }
+ if (dependency != null)
+ {
+ add(component, dependency);
+ }
+ }
+
+ private void add(String component, String dependency)
{
Objects.requireNonNull(component, "Parameter component cannot be
null");
Objects.requireNonNull(dependency, "Parameter dependency cannot be
null");
@@ -258,7 +270,7 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
dependents.add(component);
}
}
-
+
@Override
public void listen(InvalidationEventHub invalidationEventHub)
{
@@ -315,5 +327,24 @@ public class ComponentDependencyRegistryImpl implements
ComponentDependencyRegis
}
}
}
+
+ @Override
+ public boolean contains(String className)
+ {
+ return alreadyProcessed.contains(className);
+ }
+
+ @Override
+ public Set<String> getClassNames()
+ {
+ return Collections.unmodifiableSet(new HashSet<>(alreadyProcessed));
+ }
+
+ @Override
+ public Set<String> getRootClasses() {
+ return alreadyProcessed.stream()
+ .filter(c -> getDependencies(c).isEmpty())
+ .collect(Collectors.toSet());
+ }
}
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 cff65325e..a2ff8d87c 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
@@ -12,6 +12,10 @@
package org.apache.tapestry5.internal.services;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -22,6 +26,8 @@ import java.util.stream.Collectors;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.beanmodel.services.PlasticProxyFactoryImpl;
+import org.apache.tapestry5.commons.Location;
+import org.apache.tapestry5.commons.ObjectCreator;
import org.apache.tapestry5.commons.Resource;
import org.apache.tapestry5.commons.services.PlasticProxyFactory;
import org.apache.tapestry5.commons.util.CollectionFactory;
@@ -57,6 +63,8 @@ import org.apache.tapestry5.plastic.MethodInvocation;
import org.apache.tapestry5.plastic.PlasticClass;
import org.apache.tapestry5.plastic.PlasticClassEvent;
import org.apache.tapestry5.plastic.PlasticClassListener;
+import org.apache.tapestry5.plastic.PlasticClassTransformation;
+import org.apache.tapestry5.plastic.PlasticClassTransformer;
import org.apache.tapestry5.plastic.PlasticField;
import org.apache.tapestry5.plastic.PlasticManager;
import org.apache.tapestry5.plastic.PlasticManager.PlasticManagerBuilder;
@@ -71,6 +79,8 @@ import org.apache.tapestry5.runtime.PageLifecycleListener;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.ComponentEventHandler;
import org.apache.tapestry5.services.TransformConstants;
+import org.apache.tapestry5.services.pageload.PageClassloaderContext;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.ControlledPackageType;
import org.apache.tapestry5.services.transform.TransformationSupport;
@@ -101,10 +111,12 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
private final boolean productionMode;
private final ComponentClassResolver resolver;
-
- private volatile PlasticProxyFactory proxyFactory;
-
- private volatile PlasticManager manager;
+
+ private final PageClassloaderContextManager pageClassloaderContextManager;
+
+ private PageClassloaderContext rootPageClassloaderContext;
+
+ private PlasticProxyFactoryProxy plasticProxyFactoryProxy;
/**
* Map from class name to Instantiator.
@@ -147,7 +159,10 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
ComponentClassResolver resolver,
-
InternalComponentInvalidationEventHub invalidationHub)
+
InternalComponentInvalidationEventHub invalidationHub,
+
+ PageClassloaderContextManager
pageClassloaderContextManager
+ )
{
this.parent = proxyFactory.getClassLoader();
this.transformerChain = transformerChain;
@@ -158,6 +173,7 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
this.invalidationHub = invalidationHub;
this.productionMode = productionMode;
this.resolver = resolver;
+ this.pageClassloaderContextManager = pageClassloaderContextManager;
// For now, we just need the keys of the configuration. When there are
more types of controlled
// packages, we'll need to do more.
@@ -182,14 +198,23 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
final List<String> classNames =
changedResources.stream().map(ClassName::getClassName).collect(Collectors.toList());
+ final Set<String> classesToInvalidate = new HashSet<>();
+
+ for (String className : classNames)
+ {
+ final PageClassloaderContext context =
rootPageClassloaderContext.findByClassName(className);
+ if (context != rootPageClassloaderContext && context != null)
+ {
+
classesToInvalidate.addAll(pageClassloaderContextManager.invalidate(context));
+ }
+ }
+
+ classNames.clear();
+ classNames.addAll(classesToInvalidate);
+
invalidate(classNames);
-
- invalidationHub.fireInvalidationEvent(changedResources.stream()
- .map(ClassNameHolder::getClassName)
- .collect(Collectors.toList()));
- // TODO Remove this when multiple classloaders are figured out
- initializeService();
+ invalidationHub.fireInvalidationEvent(classNames);
}
}
@@ -220,14 +245,16 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
{
changeTracker.clear();
invalidationHub.classInControlledPackageHasChanged();
+ pageClassloaderContextManager.clear();
}
public void run()
{
changeTracker.clear();
classToInstantiator.clear();
- proxyFactory.clearCache();
+ // TODO fix this
// classToModel.clear();
+ pageClassloaderContextManager.clear();
initializeService();
}
@@ -238,30 +265,41 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
*/
private void initializeService()
{
- if (manager == null)
+
+ pageClassloaderContextManager.clear();
+
+ if (rootPageClassloaderContext == null)
{
logger.info("Initializing page pool");
+
+ pageClassloaderContextManager.clear();
+
+ PlasticProxyFactory proxyFactory =
createPlasticProxyFactory(parent);
+ rootPageClassloaderContext = new PageClassloaderContext(
+ "root", null, Collections.emptySet(), proxyFactory);
}
else
{
logger.info("Restarting page pool");
}
- PlasticManagerBuilder builder =
PlasticManager.withClassLoader(parent).delegate(this)
- .packages(controlledPackageNames);
+ classToInstantiator.clear();
+ classToModel.clear();
+ }
+
+ private PlasticProxyFactory createPlasticProxyFactory(final ClassLoader
parentClassloader)
+ {
+ PlasticManagerBuilder builder =
PlasticManager.withClassLoader(parentClassloader)
+ .delegate(this)
+ .packages(controlledPackageNames);
if (!productionMode)
{
builder.enable(TransformationOption.FIELD_WRITEBEHIND);
}
-
- manager = builder.create();
-
- manager.addPlasticClassListener(this);
-
- proxyFactory = new PlasticProxyFactoryImpl(manager, logger);
-
- classToInstantiator.clear();
- classToModel.clear();
+ PlasticManager plasticManager = builder.create();
+ plasticManager.addPlasticClassListener(this);
+ PlasticProxyFactory proxyFactory = new
PlasticProxyFactoryImpl(plasticManager, logger);
+ return proxyFactory;
}
public Instantiator getInstantiator(final String className)
@@ -279,7 +317,10 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
// Force the creation of the class (and the
transformation of the class). This will first
// trigger transformations of any base classes.
- final ClassInstantiator<Component> plasticInstantiator
= manager.getClassInstantiator(className);
+ final PageClassloaderContext context =
pageClassloaderContextManager.get(
+ className, rootPageClassloaderContext,
ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
+ final ClassInstantiator<Component> plasticInstantiator
=
+
context.getPlasticManager().getClassInstantiator(className);
final ComponentModel model =
classToModel.get(className);
@@ -313,9 +354,13 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
public PlasticProxyFactory getProxyFactory()
{
- return proxyFactory;
+ if (plasticProxyFactoryProxy == null)
+ {
+ plasticProxyFactoryProxy = new PlasticProxyFactoryProxy();
+ }
+ return plasticProxyFactoryProxy;
}
-
+
public void transform(final PlasticClass plasticClass)
{
tracker.run(String.format("Running component class transformations on
%s", plasticClass.getClassName()),
@@ -467,7 +512,9 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
{
try
{
- return PlasticInternalUtils.toClass(manager.getClassLoader(),
typeName);
+ final PageClassloaderContext context =
pageClassloaderContextManager.get(
+ typeName, rootPageClassloaderContext,
ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
+ return
PlasticInternalUtils.toClass(context.getPlasticManager().getClassLoader(),
typeName);
} catch (ClassNotFoundException ex)
{
throw new RuntimeException(String.format(
@@ -586,5 +633,115 @@ public final class ComponentInstantiatorSourceImpl
implements ComponentInstantia
}
}
-
+
+ private class PlasticProxyFactoryProxy implements PlasticProxyFactory
+ {
+
+ @Override
+ public void addPlasticClassListener(PlasticClassListener listener)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void removePlasticClassListener(PlasticClassListener listener)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ClassLoader getClassLoader() {
+ return
rootPageClassloaderContext.getProxyFactory().getClassLoader();
+ }
+
+ @Override
+ public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType,
PlasticClassTransformer callback)
+ {
+ return
getProxyFactory(interfaceType.getName()).createProxy(interfaceType, callback);
+ }
+
+ @Override
+ public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType,
+ Class<? extends T> implementationType,
+ PlasticClassTransformer callback,
+ boolean introduceInterface)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> ClassInstantiator<T> createProxy(Class<T> interfaceType,
Class<? extends T> implementationType, PlasticClassTransformer callback)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> PlasticClassTransformation<T>
createProxyTransformation(Class<T> interfaceType)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> PlasticClassTransformation<T>
createProxyTransformation(Class<T> interfaceType, Class<? extends T>
implementationType)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T createProxy(Class<T> interfaceType, ObjectCreator<T>
creator, String description)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public <T> T createProxy(Class<T> interfaceType, Class<? extends T>
implementationType, ObjectCreator<T> creator, String description)
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Location getMethodLocation(Method method) {
+ return
getProxyFactory(method.getDeclaringClass().getName()).getMethodLocation(method);
+ }
+
+ @Override
+ public Location getConstructorLocation(Constructor constructor)
+ {
+ return
getProxyFactory(constructor.getDeclaringClass().getName()).getConstructorLocation(constructor);
+ }
+
+ @Override
+ public void clearCache() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PlasticManager getPlasticManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public PlasticProxyFactory getProxyFactory(String className)
+ {
+ PageClassloaderContext context =
rootPageClassloaderContext.findByClassName(className);
+ if (context == null)
+ {
+ context = pageClassloaderContextManager.get(
+ className, rootPageClassloaderContext,
ComponentInstantiatorSourceImpl.this::createPlasticProxyFactory);
+ }
+// System.out.println("Class: " + className);
+// System.out.println("Root classloader : " +
rootPageClassloaderContext);
+// System.out.println("Parent classloader : " + parent);
+// if (!rootPageClassloaderContext.getChildren().isEmpty())
+// {
+// System.out.println("Unknown classloader : " +
rootPageClassloaderContext.getChildren().iterator().next());
+// }
+// System.out.println("Returned context : " + context);
+// System.out.println("Returned classloader: " +
context.getProxyFactory().getClassLoader());
+// System.out.println();
+ return context.getProxyFactory();
+ }
+
+ }
+
}
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 37986cfa8..ec1b5a0a9 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
@@ -14,6 +14,15 @@
package org.apache.tapestry5.internal.services;
+import java.lang.ref.SoftReference;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.tapestry5.SymbolConstants;
import org.apache.tapestry5.commons.services.InvalidationEventHub;
import org.apache.tapestry5.commons.util.CollectionFactory;
import org.apache.tapestry5.func.F;
@@ -23,6 +32,7 @@ import
org.apache.tapestry5.internal.structure.ComponentPageElement;
import org.apache.tapestry5.internal.structure.Page;
import org.apache.tapestry5.ioc.annotations.ComponentClasses;
import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.services.ComponentClassResolver;
import org.apache.tapestry5.services.ComponentMessages;
import org.apache.tapestry5.services.ComponentTemplates;
@@ -30,14 +40,6 @@ import
org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer;
import org.apache.tapestry5.services.pageload.ComponentResourceSelector;
import org.slf4j.Logger;
-import java.lang.ref.SoftReference;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
public class PageSourceImpl implements PageSource
{
private final ComponentRequestSelectorAnalyzer selectorAnalyzer;
@@ -49,7 +51,9 @@ public class PageSourceImpl implements PageSource
private final ComponentClassResolver componentClassResolver;
private final Logger logger;
-
+
+ final private boolean productionMode;
+
private static final class CachedPageKey
{
final String pageName;
@@ -86,12 +90,14 @@ public class PageSourceImpl implements PageSource
public PageSourceImpl(PageLoader pageLoader,
ComponentRequestSelectorAnalyzer selectorAnalyzer,
ComponentDependencyRegistry componentDependencyRegistry,
ComponentClassResolver componentClassResolver,
+ @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
Logger logger)
{
this.pageLoader = pageLoader;
this.selectorAnalyzer = selectorAnalyzer;
this.componentDependencyRegistry = componentDependencyRegistry;
this.componentClassResolver = componentClassResolver;
+ this.productionMode = productionMode;
this.logger = logger;
}
@@ -126,9 +132,12 @@ public class PageSourceImpl implements PageSource
pageCache.put(key, ref);
- final ComponentPageElement rootElement = page.getRootElement();
- componentDependencyRegistry.clear(rootElement);
- componentDependencyRegistry.register(rootElement);
+ if (!productionMode)
+ {
+ final ComponentPageElement rootElement = page.getRootElement();
+ componentDependencyRegistry.clear(rootElement);
+ componentDependencyRegistry.register(rootElement);
+ }
}
}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
index 597a38afc..a55a05833 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/modules/PageLoadModule.java
@@ -26,6 +26,8 @@ import org.apache.tapestry5.ioc.annotations.Symbol;
import org.apache.tapestry5.services.Core;
import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer;
import org.apache.tapestry5.services.pageload.ComponentResourceLocator;
+import org.apache.tapestry5.services.pageload.PageClassloaderContextManager;
+import
org.apache.tapestry5.services.pageload.PageClassloaderContextManagerImpl;
import org.apache.tapestry5.services.pageload.PagePreloader;
import org.apache.tapestry5.services.pageload.PreloaderMode;
@@ -41,6 +43,7 @@ public class PageLoadModule
binder.bind(ComponentResourceLocator.class,
DefaultComponentResourceLocator.class);
binder.bind(ComponentTemplateSource.class,
ComponentTemplateSourceImpl.class);
binder.bind(PagePreloader.class, PagePreloaderImpl.class);
+ binder.bind(PageClassloaderContextManager.class,
PageClassloaderContextManagerImpl.class);
}
@Startup
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContext.java
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContext.java
new file mode 100644
index 000000000..4fe237e34
--- /dev/null
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContext.java
@@ -0,0 +1,211 @@
+// Copyright 2023 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.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// 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.services.pageload;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.tapestry5.commons.services.PlasticProxyFactory;
+import org.apache.tapestry5.plastic.PlasticManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Class that encapsulates a classloader context for Tapestry's live class
reloading.
+ * Each instance contains basically a classloader, a set of classnames, a
parent
+ * context (possibly null) and child contexts (possibly empty).
+ */
+public class PageClassloaderContext
+{
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(PageClassloaderContext.class);
+
+ private final String name;
+
+ private final PageClassloaderContext parent;
+
+ private final Set<String> classNames = new HashSet<>();
+
+ private final Set<PageClassloaderContext> children;
+
+ private final PlasticManager plasticManager;
+
+ private final PlasticProxyFactory proxyFactory;
+
+ public PageClassloaderContext(String name,
+ PageClassloaderContext parent,
+ Set<String> classNames,
+ PlasticProxyFactory plasticProxyFactory)
+ {
+ super();
+ this.name = name;
+ this.parent = parent;
+ this.classNames.addAll(classNames);
+ this.plasticManager = plasticProxyFactory.getPlasticManager();
+ this.proxyFactory = plasticProxyFactory;
+ children = new HashSet<>();
+ }
+
+ /**
+ * Returns the name of this context.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Returns the parent of this context.
+ */
+ public PageClassloaderContext getParent()
+ {
+ return parent;
+ }
+
+ /**
+ * Returns the set of classes that belong in this context.
+ */
+ public Set<String> getClassNames()
+ {
+ return classNames;
+ }
+
+ /**
+ * Returns the children of this context.
+ */
+ public Set<PageClassloaderContext> getChildren()
+ {
+ return children;
+ }
+
+ /**
+ * Returns this context's {@linkplain PlasticManager} instance.
+ */
+ public PlasticManager getPlasticManager()
+ {
+ return plasticManager;
+ }
+
+ /**
+ * Returns this context's {@linkplain PlasticProxyFactory} instance.
+ */
+ public PlasticProxyFactory getProxyFactory()
+ {
+ return proxyFactory;
+ }
+
+ /**
+ * Adds a class to this context.
+ */
+ public void addClass(String className)
+ {
+ classNames.add(className);
+ }
+
+ public void addChildren(PageClassloaderContext context)
+ {
+ children.add(context);
+ }
+
+ /**
+ * Searches for the context that contains the given class in itself and
recursivel in its children.
+ */
+ public PageClassloaderContext findByClassName(String className)
+ {
+ PageClassloaderContext context = null;
+ if (classNames.contains(className))
+ {
+ context = this;
+ }
+ else
+ {
+ for (PageClassloaderContext child : children) {
+ context = child.findByClassName(className);
+ if (context != null)
+ {
+ break;
+ }
+ }
+ }
+ return context;
+ }
+
+ /**
+ * Returns the {@linkplain ClassLoader} associated with this context.
+ */
+ public ClassLoader getClassLoader()
+ {
+ return proxyFactory.getClassLoader();
+ }
+
+ /**
+ * Invalidates this context and its children recursively. This shouldn't
+ * be called directly, just through {@link
PageClassloaderContextManager#invalidate(PageClassloaderContext)}.
+ */
+ public void invalidate()
+ {
+ LOGGER.info("Invalidating page classloader context '{}' (class loader
{}, classes : {})",
+ name, proxyFactory.getClassLoader(), classNames);
+ classNames.clear();
+ parent.getChildren().remove(this);
+ proxyFactory.clearCache();
+ for (PageClassloaderContext child : children)
+ {
+ child.invalidate();
+ }
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(name);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (!(obj instanceof PageClassloaderContext))
+ {
+ return false;
+ }
+ PageClassloaderContext other = (PageClassloaderContext) obj;
+ return Objects.equals(name, other.name);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "PageClassloaderContext [name=" + name +
+ ", parent=" + (parent != null ? parent.getName() : "null" ) +
+ ", classLoader=" +
afterAt(proxyFactory.getClassLoader().toString()) +
+ ", object id" + afterAt(super.toString()) +
+// ", classNames=" + classNames +
+ "]";
+ }
+
+ private static String afterAt(String string)
+ {
+ int index = string.indexOf('@');
+ if (index > 0)
+ {
+ string = string.substring(index + 1);
+ }
+ return string;
+ }
+}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContextManager.java
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContextManager.java
new file mode 100644
index 000000000..d91857444
--- /dev/null
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContextManager.java
@@ -0,0 +1,57 @@
+// Copyright 2023 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.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// 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.services.pageload;
+
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.tapestry5.commons.services.PlasticProxyFactory;
+import org.apache.tapestry5.internal.services.ComponentInstantiatorSource;
+
+/**
+ * Service that creates {@linkplain PageClassloaderContext} instances (except
the root one)
+ * when a class in a controlled page is first used in the Tapestry page pool.
Existing
+ * contexts may be reused for a given class, specially when in production mode.
+ *
+ * @see ComponentInstantiatorSource
+ * @since 5.8.3
+ */
+public interface PageClassloaderContextManager
+{
+
+ /**
+ * Processes a class, given its class name and the root context.
+ * @param className the class fully qualified name.
+ * @param root the root {@link PageClassloaderContext}.
+ * @param plasticProxyFactoryProvider a function that receives a
+ * {@linkplain} ClassLoader} and returns a new {@linkplain
PlasticProxyFactory}.
+ * @return the {@link PageClassloaderContext} associated with that class.
+ */
+ PageClassloaderContext get(
+ String className,
+ PageClassloaderContext root,
+ Function<ClassLoader, PlasticProxyFactory>
plasticProxyFactoryProvider);
+
+ /**
+ * Invalidates one page classloader context and returns a set containing
the names
+ * of all classes that should be invalidated.
+ */
+ Set<String> invalidate(PageClassloaderContext context);
+
+ /**
+ * Clears any state held by this manager.
+ */
+ void clear();
+
+}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContextManagerImpl.java
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContextManagerImpl.java
new file mode 100644
index 000000000..e382f01b2
--- /dev/null
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/PageClassloaderContextManagerImpl.java
@@ -0,0 +1,153 @@
+// Copyright 2023 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.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// 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.services.pageload;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.commons.services.PlasticProxyFactory;
+import org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
+import org.apache.tapestry5.ioc.annotations.Symbol;
+
+/**
+ * Default {@linkplain PageClassloaderContextManager} implementation.
+ *
+ * @since 5.8.3
+ */
+public class PageClassloaderContextManagerImpl implements
PageClassloaderContextManager
+{
+
+ private static final String UNKOWN_CONTEXT_NAME = "unknown";
+
+ private final ComponentDependencyRegistry componentDependencyRegistry;
+
+ private final boolean productionMode;
+
+ public PageClassloaderContextManagerImpl(
+ final ComponentDependencyRegistry componentDependencyRegistry,
+ @Symbol(SymbolConstants.PRODUCTION_MODE) final boolean
productionMode)
+ {
+ super();
+ this.componentDependencyRegistry = componentDependencyRegistry;
+ this.productionMode = productionMode;
+ }
+
+ @Override
+ public PageClassloaderContext get(
+ final String className,
+ final PageClassloaderContext root,
+ final Function<ClassLoader, PlasticProxyFactory>
plasticProxyFactoryProvider)
+ {
+ PageClassloaderContext context;
+
+ if (productionMode)
+ {
+ root.addClass(className);
+ context = root;
+ }
+ else
+ {
+
+ context = root.findByClassName(className);
+
+ if (context == null)
+ {
+ context = processUsingDependencies(className, root, () ->
getUnknownContext(root, plasticProxyFactoryProvider));
+ }
+
+ }
+
+ return context;
+
+ }
+
+ private PageClassloaderContext getUnknownContext(final
PageClassloaderContext root,
+ final Function<ClassLoader, PlasticProxyFactory>
plasticProxyFactoryProvider) {
+ PageClassloaderContext unknownContext = null;
+
+ for (PageClassloaderContext child : root.getChildren())
+ {
+ if (child.getName().equals(UNKOWN_CONTEXT_NAME))
+ {
+ unknownContext = child;
+ break;
+ }
+ }
+
+ if (unknownContext == null)
+ {
+ unknownContext = new PageClassloaderContext(UNKOWN_CONTEXT_NAME,
root,
+ Collections.emptySet(),
+ plasticProxyFactoryProvider.apply(root.getClassLoader()));
+ root.addChildren(unknownContext);
+ }
+ return unknownContext;
+ }
+
+ private PageClassloaderContext processUsingDependencies(String className,
PageClassloaderContext root, Supplier<PageClassloaderContext>
unknownContextProvider)
+ {
+ PageClassloaderContext context = root.findByClassName(className);
+ if (context == null)
+ {
+
+ // Class isn't in a controlled package, so it doesn't get
transformed
+ // and should go for the root context, which is never thrown out.
+ if
(!root.getPlasticManager().shouldInterceptClassLoading(className))
+ {
+ context = root;
+ }
+ // If we don't have dependency information about this class, it
goes
+ // into the "unknown" context.
+ else if (!componentDependencyRegistry.contains(className))
+ {
+ context = unknownContextProvider.get();
+ }
+ // TODO: implement this correctly by creating and merging new
contexts
+ // as needed.
+ else
+ {
+ context = unknownContextProvider.get();
+ }
+
+ }
+ context.addClass(className);
+ return context;
+ }
+
+ @Override
+ public Set<String> invalidate(PageClassloaderContext context)
+ {
+ Set<String> classNames = new HashSet<>();
+ addClassNames(context, classNames);
+ context.invalidate();
+ return classNames;
+ }
+
+ private void addClassNames(PageClassloaderContext context, Set<String>
classNames) {
+ classNames.addAll(context.getClassNames());
+ for (PageClassloaderContext child : context.getChildren()) {
+ addClassNames(child, classNames);
+ }
+ }
+
+ @Override
+ public void clear()
+ {
+ }
+
+}
diff --git
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/package-info.java
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/package-info.java
index f80452ba0..5c462eb25 100644
---
a/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/package-info.java
+++
b/tapestry-core/src/main/java/org/apache/tapestry5/services/pageload/package-info.java
@@ -1,4 +1,4 @@
-// Copyright 2011 The Apache Software Foundation
+// Copyright 2011-2023 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.
diff --git
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
index 4a1c9e92a..b470b5cce 100644
---
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
+++
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentDependencyRegistryImplTest.java
@@ -14,8 +14,11 @@
package org.apache.tapestry5.internal.services;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.testng.Assert.assertEquals;
+import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
@@ -35,6 +38,9 @@ public class ComponentDependencyRegistryImplTest
@BeforeClass
public void setup()
{
+ assertFalse(
+ String.format("During testing, %s shouldn't exist",
ComponentDependencyRegistry.FILENAME),
+ new File(ComponentDependencyRegistry.FILENAME).exists());
componentDependencyRegistry = new ComponentDependencyRegistryImpl();
}
@@ -42,11 +48,11 @@ public class ComponentDependencyRegistryImplTest
public void listen()
{
- componentDependencyRegistry.add("foo", "bar");
- componentDependencyRegistry.add("d", "a");
- componentDependencyRegistry.add("dd", "aa");
- componentDependencyRegistry.add("dd", "a");
- componentDependencyRegistry.add("dd", "a");
+ add("foo", "bar");
+ add("d", "a");
+ add("dd", "aa");
+ add("dd", "a");
+ add("dd", "a");
final List<String> resources = Arrays.asList("a", "aa", "none");
final List<String> result =
componentDependencyRegistry.listen(resources);
@@ -77,11 +83,14 @@ public class ComponentDependencyRegistryImplTest
assertEquals(componentDependencyRegistry.getDependents(foo),
Collections.emptySet(),
"getDependents() should never return null");
- componentDependencyRegistry.add(foo, bar);
- componentDependencyRegistry.add(something, fulano);
- componentDependencyRegistry.add(other, beltrano);
- componentDependencyRegistry.add(other, fulano);
- componentDependencyRegistry.add(other, fulano);
+ add(foo, bar);
+ add(something, fulano);
+ add(other, beltrano);
+ add(other, fulano);
+ add(other, fulano);
+ add(bar, null);
+ add(fulano, null);
+ add(beltrano, null);
assertEquals(componentDependencyRegistry.getDependencies(other), new
HashSet<>(Arrays.asList(fulano, beltrano)));
assertEquals(componentDependencyRegistry.getDependencies(something),
new HashSet<>(Arrays.asList(fulano)));
@@ -93,6 +102,29 @@ public class ComponentDependencyRegistryImplTest
assertEquals(componentDependencyRegistry.getDependents(fulano), new
HashSet<>(Arrays.asList(other, something)));
assertEquals(componentDependencyRegistry.getDependents(foo), new
HashSet<>(Arrays.asList()));
+ assertEquals(componentDependencyRegistry.getRootClasses(), new
HashSet<>(Arrays.asList(bar, fulano, beltrano)));
+
+ assertTrue(componentDependencyRegistry.contains(foo));
+ assertTrue(componentDependencyRegistry.contains(bar));
+ assertTrue(componentDependencyRegistry.contains(other));
+ assertTrue(componentDependencyRegistry.contains(something));
+ assertTrue(componentDependencyRegistry.contains(fulano));
+ assertTrue(componentDependencyRegistry.contains(beltrano));
+ assertFalse(componentDependencyRegistry.contains("blah"));
+
+ assertTrue(componentDependencyRegistry.getClassNames().contains(foo));
+ assertTrue(componentDependencyRegistry.getClassNames().contains(bar));
+
assertTrue(componentDependencyRegistry.getClassNames().contains(other));
+
assertTrue(componentDependencyRegistry.getClassNames().contains(something));
+
assertTrue(componentDependencyRegistry.getClassNames().contains(fulano));
+
assertTrue(componentDependencyRegistry.getClassNames().contains(beltrano));
+
assertFalse(componentDependencyRegistry.getClassNames().contains("blah"));
+
+ }
+
+ private void add(String component, String dependency)
+ {
+ componentDependencyRegistry.add(component, dependency, true);
}
}