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

commit 0bd65e6a9922e5fced410264cb8259cc0cdfec6c
Author: Thiago H. de Paula Figueiredo <thi...@arsmachina.com.br>
AuthorDate: Sat Jun 10 11:56:51 2023 -0300

    TAP5-2742: creating multiple classloader mode
---
 583_RELEASE_NOTES.md                               |  5 ++
 .../java/org/apache/tapestry5/SymbolConstants.java | 10 +++
 .../internal/pageload/PageLoaderImpl.java          | 23 ++++--
 .../services/ComponentInstantiatorSourceImpl.java  | 92 ++++++++++++++-------
 .../services/ComponentMessagesSourceImpl.java      | 16 ++--
 .../services/ComponentTemplateSourceImpl.java      | 41 +++++++---
 .../internal/services/MessagesSourceImpl.java      | 11 ++-
 .../internal/services/PageSourceImpl.java          | 32 ++++----
 .../services/assets/ResourceChangeTrackerImpl.java | 10 ++-
 .../apache/tapestry5/modules/PageLoadModule.java   | 16 +++-
 .../PageClassLoaderContextManagerImpl.java         | 93 ++++++++++++++--------
 .../services/ComponentMessagesSourceImplTest.java  |  4 +-
 .../services/ComponentTemplateSourceImplTest.java  | 10 +--
 .../integration/app1/pages/nested/AssetDemo.tml    |  2 +
 14 files changed, 245 insertions(+), 120 deletions(-)

diff --git a/583_RELEASE_NOTES.md b/583_RELEASE_NOTES.md
index 882312c15..55a4de501 100644
--- a/583_RELEASE_NOTES.md
+++ b/583_RELEASE_NOTES.md
@@ -1,5 +1,10 @@
 Scratch pad for changes destined for the 5.8.3 release notes page.
 
+# New configuration symbol
+
+* SymbolConstants.MULTIPLE_CLASSLOADERS: when set to true (default false), 
enables
+multiple classloaders for smarter page cache invalidation.
+
 # Added methods
 
 * add(URL url, String memo) to URLChangeTracker
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java 
b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index d761905fb..ca3b542f8 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -33,6 +33,7 @@ import 
org.apache.tapestry5.services.assets.AssetPathConstructor;
 import org.apache.tapestry5.services.assets.ResourceMinimizer;
 import org.apache.tapestry5.services.compatibility.Trait;
 import org.apache.tapestry5.services.javascript.JavaScriptStack;
+import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager;
 import org.apache.tapestry5.services.rest.OpenApiDescriptionGenerator;
 
 /**
@@ -780,5 +781,14 @@ public class SymbolConstants
      * @since 5.8.2
      */
     public static final String CORS_MAX_AGE = 
TapestryHttpSymbolConstants.CORS_MAX_AGE;
+    
+    /**
+     * Defines whether multiple classloaders will be used instead of one for 
smarter page invalidation.
+     * This is ignored when in production mode.
+     * Default value is <code>false</code>.
+     * @see PageClassLoaderContextManager
+     * @since 5.8.3
+     */
+    public static final String MULTIPLE_CLASSLOADERS = 
"tapestry.multiple-classloaders";
 
 }
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java
index ec0566ed0..7a2233247 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/pageload/PageLoaderImpl.java
@@ -207,18 +207,27 @@ public class PageLoaderImpl implements PageLoader, 
ComponentAssemblerSource
     private List<String> listen(List<String> resources)
     {
         
-        final Iterator<Entry<Key, ComponentAssembler>> iterator = 
cache.entrySet().iterator();
-        
-        while (iterator.hasNext())
+        if (resources.isEmpty())
+        {
+            cache.clear();
+        }
+        else
         {
-            final Entry<Key, ComponentAssembler> entry = iterator.next();
-            for (String resource : resources) 
+        
+            final Iterator<Entry<Key, ComponentAssembler>> iterator = 
cache.entrySet().iterator();
+            
+            while (iterator.hasNext())
             {
-                if (resource.equals(entry.getKey().className))
+                final Entry<Key, ComponentAssembler> entry = iterator.next();
+                for (String resource : resources) 
                 {
-                    iterator.remove();
+                    if (resource.equals(entry.getKey().className))
+                    {
+                        iterator.remove();
+                    }
                 }
             }
+            
         }
         
         return Collections.emptyList();
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 b38bf2991..a0ae6d2ea 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
@@ -25,6 +25,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.tapestry5.ComponentResources;
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.beanmodel.services.PlasticProxyFactoryImpl;
 import org.apache.tapestry5.commons.Location;
 import org.apache.tapestry5.commons.ObjectCreator;
@@ -110,6 +111,8 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
     private final InternalComponentInvalidationEventHub invalidationHub;
 
     private final boolean productionMode;
+    
+    private final boolean multipleClassLoaders;
 
     private final ComponentClassResolver resolver;
     
@@ -162,6 +165,9 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
                                            
@Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
                                            boolean productionMode,
 
+                                           
@Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS)
+                                           boolean multipleClassLoaders,
+
                                            ComponentClassResolver resolver,
 
                                            
InternalComponentInvalidationEventHub invalidationHub,
@@ -179,6 +185,7 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
         this.tracker = tracker;
         this.invalidationHub = invalidationHub;
         this.productionMode = productionMode;
+        this.multipleClassLoaders = multipleClassLoaders;
         this.resolver = resolver;
         this.pageClassLoaderContextManager = pageClassLoaderContextManager;
         this.componentDependencyRegistry = componentDependencyRegistry;
@@ -211,59 +218,86 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
             
             final List<String> classNames = 
changedResources.stream().map(ClassName::getClassName).collect(Collectors.toList());
             
-            final Set<String> classesToInvalidate = new HashSet<>();
+            if (logger.isInfoEnabled())
+            {
+                logger.info("Component class(es) changed: {}", String.join(", 
", classNames));
+            }
             
-            for (String className : classNames) 
+            if (multipleClassLoaders)
             {
-                final PageClassLoaderContext context = 
rootPageClassloaderContext.findByClassName(className);
-                if (context != rootPageClassloaderContext && context != null)
+                
+                final Set<String> classesToInvalidate = new HashSet<>();
+                
+                for (String className : classNames) 
                 {
-                    
classesToInvalidate.addAll(pageClassLoaderContextManager.invalidate(context));
+                    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(classNames);
+            }
+            else
+            {
+                invalidationHub.classInControlledPackageHasChanged();
             }
-            
-            classNames.clear();
-            classNames.addAll(classesToInvalidate);
-            
-            invalidate(classNames);
-            
-            invalidationHub.fireInvalidationEvent(classNames);
             
         }
     }
 
     private List<String> invalidate(final List<String> classNames) {
 
-        final String currentPage = CURRENT_PAGE.get();
-        
-        final Iterator<Entry<String, Instantiator>> 
classToInstantiatorIterator = classToInstantiator.entrySet().iterator();
-        while (classToInstantiatorIterator.hasNext())
+        if (classNames.isEmpty())
         {
-            final String className = 
classToInstantiatorIterator.next().getKey();
-            if (!className.equals(currentPage) && 
classNames.contains(className))
-            {
-                classToInstantiatorIterator.remove();
-            }
+            clearCaches();
         }
-
-        final Iterator<Entry<String, ComponentModel>> classToModelIterator = 
classToModel.entrySet().iterator();
-        while (classToModelIterator.hasNext())
+        else
         {
-            final String className = classToModelIterator.next().getKey();
-            if (!className.equals(currentPage) && 
classNames.contains(className))
+        
+            final String currentPage = CURRENT_PAGE.get();
+            
+            final Iterator<Entry<String, Instantiator>> 
classToInstantiatorIterator = classToInstantiator.entrySet().iterator();
+            while (classToInstantiatorIterator.hasNext())
+            {
+                final String className = 
classToInstantiatorIterator.next().getKey();
+                if (!className.equals(currentPage) && 
classNames.contains(className))
+                {
+                    classToInstantiatorIterator.remove();
+                }
+            }
+    
+            final Iterator<Entry<String, ComponentModel>> classToModelIterator 
= classToModel.entrySet().iterator();
+            while (classToModelIterator.hasNext())
             {
-                classToModelIterator.remove();
+                final String className = classToModelIterator.next().getKey();
+                if (!className.equals(currentPage) && 
classNames.contains(className))
+                {
+                    classToModelIterator.remove();
+                }
             }
+            
         }
+        
         return Collections.emptyList();
     }
 
     public void forceComponentInvalidation()
     {
-        changeTracker.clear();
+        clearCaches();
         invalidationHub.classInControlledPackageHasChanged();
+    }
+
+    private void clearCaches() 
+    {
+        classToInstantiator.clear();
         pageClassLoaderContextManager.clear();
-        classToModel.clear();
     }
 
     public void run()
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
index 763dccea5..fbd52e358 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImpl.java
@@ -19,6 +19,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.Callable;
 
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.commons.Messages;
 import org.apache.tapestry5.commons.Resource;
 import org.apache.tapestry5.commons.internal.util.TapestryException;
@@ -80,32 +81,35 @@ public class ComponentMessagesSourceImpl implements 
ComponentMessagesSource, Upd
     }
 
     public 
ComponentMessagesSourceImpl(@Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
-                                       boolean productionMode, List<Resource> 
appCatalogResources, PropertiesFileParser parser,
+                                       boolean productionMode, 
+                                       
@Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS)
+                                       boolean multipleClassLoaders, 
+                                       List<Resource> appCatalogResources, 
PropertiesFileParser parser,
                                        ComponentResourceLocator 
resourceLocator, ClasspathURLConverter classpathURLConverter,
                                        ComponentRequestSelectorAnalyzer 
componentRequestSelectorAnalyzer,
                                        ThreadLocale threadLocale, 
ComponentClassResolver componentClassResolver,
                                        Logger logger)
     {
-        this(productionMode, appCatalogResources, resourceLocator, parser, new 
URLChangeTracker(classpathURLConverter), 
+        this(productionMode, multipleClassLoaders, appCatalogResources, 
resourceLocator, parser, new URLChangeTracker(classpathURLConverter), 
                 componentRequestSelectorAnalyzer, threadLocale, 
componentClassResolver, logger);
     }
 
-    ComponentMessagesSourceImpl(boolean productionMode, Resource 
appCatalogResource,
+    ComponentMessagesSourceImpl(boolean productionMode, boolean 
multipleClassLoaders, Resource appCatalogResource,
                                 ComponentResourceLocator resourceLocator, 
PropertiesFileParser parser, 
                                 URLChangeTracker tracker, 
ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer,
                                 ThreadLocale threadLocale, 
ComponentClassResolver componentClassResolver, 
                                 Logger logger)
     {
-        this(productionMode, Arrays.asList(appCatalogResource), 
resourceLocator, parser, tracker, componentRequestSelectorAnalyzer, 
threadLocale, componentClassResolver, logger);
+        this(productionMode, multipleClassLoaders, 
Arrays.asList(appCatalogResource), resourceLocator, parser, tracker, 
componentRequestSelectorAnalyzer, threadLocale, componentClassResolver, logger);
     }
 
-    ComponentMessagesSourceImpl(boolean productionMode, List<Resource> 
appCatalogResources,
+    ComponentMessagesSourceImpl(boolean productionMode, boolean 
multipleClassLoaders, List<Resource> appCatalogResources,
                                 ComponentResourceLocator resourceLocator, 
PropertiesFileParser parser, 
                                 URLChangeTracker tracker, 
ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer,
                                 ThreadLocale threadLocale, 
ComponentClassResolver componentClassResolver,
                                 Logger logger)
     {
-        messagesSource = new MessagesSourceImpl(productionMode, productionMode 
? null : tracker, resourceLocator,
+        messagesSource = new MessagesSourceImpl(productionMode, 
multipleClassLoaders, productionMode ? null : tracker, resourceLocator,
                 parser, componentClassResolver, logger);
 
         appCatalogBundle = createAppCatalogBundle(appCatalogResources);
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
index 5c6dae4ce..b1ca0d9a2 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImpl.java
@@ -22,6 +22,7 @@ import java.util.Map.Entry;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.TapestryConstants;
 import org.apache.tapestry5.commons.Location;
 import org.apache.tapestry5.commons.Resource;
@@ -64,6 +65,8 @@ public final class ComponentTemplateSourceImpl extends 
InvalidationEventHubImpl
     private final ThreadLocale threadLocale;
     
     private final Logger logger;
+    
+    private final boolean multipleClassLoaders;
 
     /**
      * Caches from a key (combining component name and locale) to a resource. 
Often, many different keys will point to
@@ -117,15 +120,19 @@ public final class ComponentTemplateSourceImpl extends 
InvalidationEventHubImpl
 
     public ComponentTemplateSourceImpl(@Inject
                                        
@Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
-                                       boolean productionMode, TemplateParser 
parser, ComponentResourceLocator locator,
+                                       boolean productionMode, 
+                                       @Inject
+                                       
@Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS)
+                                       boolean multipleClassLoaders,           
                             
+                                       TemplateParser parser, 
ComponentResourceLocator locator,
                                        ClasspathURLConverter 
classpathURLConverter,
                                        ComponentRequestSelectorAnalyzer 
componentRequestSelectorAnalyzer,
                                        ThreadLocale threadLocale, Logger 
logger)
     {
-        this(productionMode, parser, locator, new 
URLChangeTracker<TemplateTrackingInfo>(classpathURLConverter), 
componentRequestSelectorAnalyzer, threadLocale, logger);
+        this(productionMode, multipleClassLoaders, parser, locator, new 
URLChangeTracker<TemplateTrackingInfo>(classpathURLConverter), 
componentRequestSelectorAnalyzer, threadLocale, logger);
     }
 
-    ComponentTemplateSourceImpl(boolean productionMode, TemplateParser parser, 
ComponentResourceLocator locator,
+    ComponentTemplateSourceImpl(boolean productionMode, boolean 
multipleClassLoaders, TemplateParser parser, ComponentResourceLocator locator,
                                 URLChangeTracker<TemplateTrackingInfo> 
tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer,
                                 ThreadLocale threadLocale, Logger logger)
     {
@@ -137,6 +144,7 @@ public final class ComponentTemplateSourceImpl extends 
InvalidationEventHubImpl
         this.componentRequestSelectorAnalyzer = 
componentRequestSelectorAnalyzer;
         this.threadLocale = threadLocale;
         this.logger = logger;
+        this.multipleClassLoaders = multipleClassLoaders;
     }
 
     @PostInjection
@@ -257,21 +265,30 @@ public final class ComponentTemplateSourceImpl extends 
InvalidationEventHubImpl
                         
changedResourcesInfo.stream().map(TemplateTrackingInfo::getTemplate).collect(Collectors.toList())));
             }
             
-            final Iterator<Entry<MultiKey, Resource>> 
templateResourcesIterator = templateResources.entrySet().iterator();
-            for (TemplateTrackingInfo info : changedResourcesInfo) 
+            if (multipleClassLoaders)
             {
-                while (templateResourcesIterator.hasNext())
+            
+                final Iterator<Entry<MultiKey, Resource>> 
templateResourcesIterator = templateResources.entrySet().iterator();
+                for (TemplateTrackingInfo info : changedResourcesInfo) 
                 {
-                    final MultiKey key = 
templateResourcesIterator.next().getKey();
-                    if (info.getClassName().equals((String) 
key.getValues()[0]))
+                    while (templateResourcesIterator.hasNext())
                     {
-                        templates.remove(templateResources.get(key));
-                        templateResourcesIterator.remove();
+                        final MultiKey key = 
templateResourcesIterator.next().getKey();
+                        if (info.getClassName().equals((String) 
key.getValues()[0]))
+                        {
+                            templates.remove(templateResources.get(key));
+                            templateResourcesIterator.remove();
+                        }
                     }
                 }
+                
+                
fireInvalidationEvent(changedResourcesInfo.stream().map(TemplateTrackingInfo::getClassName).collect(Collectors.toList()));
+                
+            }
+            else
+            {
+                invalidate();
             }
-            
-            
fireInvalidationEvent(changedResourcesInfo.stream().map(TemplateTrackingInfo::getClassName).collect(Collectors.toList()));
         }
     }
 
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
index 9ef7c4f9e..f96ad6979 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesSourceImpl.java
@@ -58,6 +58,8 @@ public class MessagesSourceImpl extends 
InvalidationEventHubImpl implements Mess
     
     private final ComponentClassResolver componentClassResolver;
     
+    private final boolean multipleClassLoaders;
+    
     private final Logger logger;        
     
     /**
@@ -78,7 +80,7 @@ public class MessagesSourceImpl extends 
InvalidationEventHubImpl implements Mess
 
     private final Map<String, String> emptyMap = Collections.emptyMap();
 
-    public MessagesSourceImpl(boolean productionMode, URLChangeTracker tracker,
+    public MessagesSourceImpl(boolean productionMode, boolean 
multipleClassLoaders, URLChangeTracker tracker,
                               ComponentResourceLocator resourceLocator, 
PropertiesFileParser propertiesFileParser,
                               ComponentClassResolver componentClassResolver,
                               Logger logger)
@@ -90,6 +92,7 @@ public class MessagesSourceImpl extends 
InvalidationEventHubImpl implements Mess
         this.resourceLocator = resourceLocator;
         this.logger = logger;
         this.componentClassResolver = componentClassResolver;
+        this.multipleClassLoaders = multipleClassLoaders;
     }
 
     public void checkForUpdates()
@@ -97,9 +100,9 @@ public class MessagesSourceImpl extends 
InvalidationEventHubImpl implements Mess
         if (tracker != null)
         {
             final Set<MessagesTrackingInfo> changedResources = 
tracker.getChangedResourcesInfo();
-            if (!changedResources.isEmpty())
+            if (!changedResources.isEmpty() && logger.isInfoEnabled())
             {
-                logger.info("Changed message files: {}", 
changedResources.stream()
+                logger.info("Changed message file(s): {}", 
changedResources.stream()
                         .map(MessagesTrackingInfo::getResource)
                         .map(Resource::toString)
                         .collect(Collectors.joining(", ")));
@@ -113,7 +116,7 @@ public class MessagesSourceImpl extends 
InvalidationEventHubImpl implements Mess
                 final String className = info.getClassName();
                 
                 // An application-level file was changed, so we need to 
invalidate everything.
-                if (info.getClassName() == null)
+                if (className == null || !multipleClassLoaders)
                 {
                     invalidate();
                     applicationLevelChange = true;
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 a4a8cdea8..fe92fe3df 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
@@ -60,6 +60,8 @@ public class PageSourceImpl implements PageSource
     
     final private boolean productionMode;
     
+    final private boolean multipleClassLoaders;
+    
     private static final class CachedPageKey
     {
         final String pageName;
@@ -105,6 +107,7 @@ public class PageSourceImpl implements PageSource
             ComponentClassResolver componentClassResolver,
             PageClassLoaderContextManager pageClassLoaderContextManager,
             @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
+            @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean 
multipleClassLoaders,
             Logger logger)
     {
         this.pageLoader = pageLoader;
@@ -112,6 +115,7 @@ public class PageSourceImpl implements PageSource
         this.componentDependencyRegistry = componentDependencyRegistry;
         this.componentClassResolver = componentClassResolver;
         this.productionMode = productionMode;
+        this.multipleClassLoaders = multipleClassLoaders;
         this.pageClassLoaderContextManager = pageClassLoaderContextManager;
         this.logger = logger;
     }
@@ -156,21 +160,19 @@ public class PageSourceImpl implements PageSource
                 return page;
             }
             
-            // Avoiding problems in PlasticClassPool.createTransformation()
-            // when the class being loaded has a page superclass
             final String className = 
componentClassResolver.resolvePageNameToClassName(canonicalPageName);
-//            PageClassloaderContext context = 
pageClassLoaderContextManager.get(className);
-//            final Class<?> superclass = getSuperclass(className, context);
-//            final String superclassName = superclass.getName();
-//            if (componentClassResolver.isPage(superclassName)) 
-//            {
-//                
getPage(componentClassResolver.resolvePageClassNameToPageName(superclassName), 
false);
-//            }
-            final List<String> pageDependencies = 
preprocessPageDependencies(className);
-            
-            for (String pageClassName : pageDependencies)
+            if (multipleClassLoaders)
             {
-                page = 
getPage(componentClassResolver.resolvePageClassNameToPageName(pageClassName), 
false);
+            
+                // Avoiding problems in PlasticClassPool.createTransformation()
+                // when the class being loaded has a page superclass
+                final List<String> pageDependencies = 
preprocessPageDependencies(className);
+                
+                for (String pageClassName : pageDependencies)
+                {
+                    page = 
getPage(componentClassResolver.resolvePageClassNameToPageName(pageClassName), 
false);
+                }
+                
             }
 
             // In rare race conditions, we may see the same page loaded 
multiple times across
@@ -190,16 +192,14 @@ public class PageSourceImpl implements PageSource
                 componentDependencyRegistry.register(rootElement);
                 PageClassLoaderContext context = 
pageClassLoaderContextManager.get(className);
                 
-                if (context.isUnknown())
+                if (context.isUnknown() && multipleClassLoaders)
                 {
                     this.pageCache.remove(key);
                     if (invalidateUnknownContext)
                     {
-//                        pageClassLoaderContextManager.invalidate(context);
                         
pageClassLoaderContextManager.invalidateAndFireInvalidationEvents(context);
                         preprocessPageDependencies(className);
                     }
-//                    context.getClassNames().remove(className);
                     context.getClassNames().clear();
                     // Avoiding bad invalidations
                     return getPage(canonicalPageName, false);
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java
index 888961115..73d4d2644 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/assets/ResourceChangeTrackerImpl.java
@@ -20,6 +20,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.commons.Resource;
 import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
 import org.apache.tapestry5.internal.event.InvalidationEventHubImpl;
@@ -40,6 +41,8 @@ public class ResourceChangeTrackerImpl extends 
InvalidationEventHubImpl implemen
     private final ThreadLocal<String> currentClassName;
     
     private final Logger logger;
+    
+    private final boolean multipleClassLoaders;
 
     /**
      * Used in production mode as the last modified time of any resource 
exposed to the client. Remember that
@@ -50,10 +53,13 @@ public class ResourceChangeTrackerImpl extends 
InvalidationEventHubImpl implemen
 
     public ResourceChangeTrackerImpl(ClasspathURLConverter 
classpathURLConverter,
                                      
@Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE)
-                                     boolean productionMode, Logger logger)
+                                     boolean productionMode, 
+                                     
@Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS)
+                                     boolean multipleClassLoaders, Logger 
logger)
     {
         super(productionMode, logger);
         this.logger = logger;
+        this.multipleClassLoaders = multipleClassLoaders;
 
         // Use granularity of seconds (not milliseconds) since that works 
properly
         // with response headers for identifying last modified. Don't track
@@ -112,7 +118,7 @@ public class ResourceChangeTrackerImpl extends 
InvalidationEventHubImpl implemen
             {
                 
                 // An application-level file was changed, so we need to 
invalidate everything.
-                if (info.getClassName() == null)
+                if (info.getClassName() == null || !multipleClassLoaders)
                 {
                     forceInvalidationEvent();
                     applicationLevelChange = true;
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 f67e50d68..1a993ab03 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
@@ -13,6 +13,7 @@
 package org.apache.tapestry5.modules;
 
 import org.apache.tapestry5.SymbolConstants;
+import org.apache.tapestry5.commons.MappedConfiguration;
 import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
 import 
org.apache.tapestry5.internal.pageload.DefaultComponentRequestSelectorAnalyzer;
 import org.apache.tapestry5.internal.pageload.DefaultComponentResourceLocator;
@@ -38,6 +39,15 @@ import org.apache.tapestry5.services.pageload.PreloaderMode;
 @Marker(Core.class)
 public class PageLoadModule
 {
+    
+    /**
+     * Contributes factory defaults that may be overridden.
+     */
+    public static void contributeFactoryDefaults(MappedConfiguration<String, 
Object> configuration)
+    {
+        configuration.add(SymbolConstants.MULTIPLE_CLASSLOADERS, false);
+    }
+    
     public static void bind(ServiceBinder binder)
     {
         binder.bind(ComponentRequestSelectorAnalyzer.class, 
DefaultComponentRequestSelectorAnalyzer.class);
@@ -64,14 +74,14 @@ public class PageLoadModule
     public void preloadPageClassLoaderContexts(
             PageClassLoaderContextManager pageClassLoaderContextManager,
             ComponentDependencyRegistry componentDependencyRegistry,
-            @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode)
+            @Symbol(SymbolConstants.PRODUCTION_MODE) boolean productionMode,
+            @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean 
multipleClassLoaders)
     {
-        if (!productionMode)
+        if (!productionMode && multipleClassLoaders)
         {
             // Preload the page activation context tree for the already known 
classes
             for (int i = 0; i < 5; i++)
             {
-                System.out.println();
                 for (String className : 
componentDependencyRegistry.getClassNames()) 
                 {
                     pageClassLoaderContextManager.get(className);
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
index 06da36917..bbfa1b7af 100644
--- 
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
@@ -24,6 +24,7 @@ import java.util.function.Function;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
+import org.apache.tapestry5.SymbolConstants;
 import org.apache.tapestry5.commons.internal.util.TapestryException;
 import org.apache.tapestry5.commons.services.InvalidationEventHub;
 import org.apache.tapestry5.commons.services.PlasticProxyFactory;
@@ -31,6 +32,7 @@ import 
org.apache.tapestry5.internal.services.ComponentDependencyRegistry;
 import 
org.apache.tapestry5.internal.services.ComponentDependencyRegistry.DependencyType;
 import 
org.apache.tapestry5.internal.services.InternalComponentInvalidationEventHub;
 import org.apache.tapestry5.ioc.annotations.ComponentClasses;
+import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.plastic.PlasticUtils;
 import org.apache.tapestry5.services.ComponentClassResolver;
 import org.slf4j.Logger;
@@ -54,6 +56,8 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
     
     private final InvalidationEventHub componentClassesInvalidationEventHub;
     
+    private final boolean multipleClassLoaders;
+    
     private final static ThreadLocal<Integer> NESTED_MERGE_COUNT = 
ThreadLocal.withInitial(() -> 0);
     
     private final static ThreadLocal<Boolean> INVALIDATING_CONTEXT = 
ThreadLocal.withInitial(() -> false);
@@ -68,13 +72,15 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
             final ComponentDependencyRegistry componentDependencyRegistry, 
             final ComponentClassResolver componentClassResolver,
             final InternalComponentInvalidationEventHub invalidationHub,
-            final @ComponentClasses InvalidationEventHub 
componentClassesInvalidationEventHub) 
+            final @ComponentClasses InvalidationEventHub 
componentClassesInvalidationEventHub,
+            final @Symbol(SymbolConstants.MULTIPLE_CLASSLOADERS) boolean 
multipleClassLoaders) 
     {
         super();
         this.componentDependencyRegistry = componentDependencyRegistry;
         this.componentClassResolver = componentClassResolver;
         this.invalidationHub = invalidationHub;
         this.componentClassesInvalidationEventHub = 
componentClassesInvalidationEventHub;
+        this.multipleClassLoaders = multipleClassLoaders;
         invalidationHub.addInvalidationCallback(this::listen);
         NESTED_MERGE_COUNT.set(0);
     }
@@ -88,20 +94,6 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
             {
                 if (context.isUnknown())
                 {
-//                    componentDependencyRegistry.disableInvalidations();
-//                    final Set<String> classNames = invalidate(context);
-//                    List<String> toInvalidate = new ArrayList<>();
-//                    for (String className : classNames) 
-//                    {
-//                        if (root.findByClassName(className) == null)
-//                        {
-//                            toInvalidate.add(className);
-//                        }
-//                    }
-//                    
componentClassesInvalidationEventHub.fireInvalidationEvent(toInvalidate);
-//                    invalidationHub.fireInvalidationEvent(toInvalidate);
-//                    
-//                    componentDependencyRegistry.enableInvalidations();
                     invalidateAndFireInvalidationEvents(context);
                     break;
                 }
@@ -122,7 +114,10 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
         Objects.requireNonNull(plasticProxyFactoryProvider);
         this.root = root;
         this.plasticProxyFactoryProvider = plasticProxyFactoryProvider;
-        LOGGER.debug("Root context: {}", root);
+        if (multipleClassLoaders)
+        {
+            LOGGER.debug("Root context: {}", root);
+        }
     }
 
     @Override
@@ -182,7 +177,10 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
                     plasticProxyFactoryProvider.apply(root.getClassLoader()),
                     this::get);
             root.addChild(unknownContext);
-            LOGGER.debug("Unknown context: {}", unknownContext);
+            if (multipleClassLoaders)
+            {
+                LOGGER.debug("Unknown context: {}", unknownContext);
+            }
         }
         return unknownContext;
     }
@@ -229,7 +227,8 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
                 context = root;
             } else {
                 if (
-                        !componentDependencyRegistry.contains(className) 
+                        !componentDependencyRegistry.contains(className) ||
+                        !multipleClassLoaders
                         // TODO: review this
 //                        && 
componentDependencyRegistry.getDependents(className).isEmpty()
                         )
@@ -491,34 +490,60 @@ public class PageClassLoaderContextManagerImpl implements 
PageClassLoaderContext
     
     private List<String> listen(List<String> resources)
     {
-        if (INVALIDATING_CONTEXT.get())
+
+        List<String> returnValue;
+        
+        if (!multipleClassLoaders)
+        {
+            for (PageClassLoaderContext context : root.getChildren()) 
+            {
+                context.invalidate();
+            }
+            returnValue = Collections.emptyList();
+        }
+        else if (INVALIDATING_CONTEXT.get())
         {
-            return Collections.emptyList();
+            returnValue = Collections.emptyList();
         }
-        Set<PageClassLoaderContext> contextsToInvalidate = new HashSet<>();
-        for (String resource : resources) 
+        else
         {
-            PageClassLoaderContext context = root.findByClassName(resource);
-            if (context != null && !context.isRoot())
+        
+            Set<PageClassLoaderContext> contextsToInvalidate = new HashSet<>();
+            for (String resource : resources) 
             {
-                contextsToInvalidate.add(context);
+                PageClassLoaderContext context = 
root.findByClassName(resource);
+                if (context != null && !context.isRoot())
+                {
+                    contextsToInvalidate.add(context);
+                }
             }
+            
+            Set<String> furtherResources = 
invalidate(contextsToInvalidate.toArray(
+                    new PageClassLoaderContext[contextsToInvalidate.size()]));
+            
+            // We don't want to invalidate resources more than once
+            furtherResources.removeAll(resources);
+            
+            returnValue = new ArrayList<>(furtherResources);
         }
         
-        Set<String> furtherResources = invalidate(contextsToInvalidate.toArray(
-                new PageClassLoaderContext[contextsToInvalidate.size()]));
-        
-        // We don't want to invalidate resources more than once
-        furtherResources.removeAll(resources);
-        
-        return new ArrayList<>(furtherResources);
+        return returnValue;
+            
     }
 
+    @SuppressWarnings("unchecked")
     @Override
     public void invalidateAndFireInvalidationEvents(PageClassLoaderContext... 
contexts) {
-        final Set<String> classNames = invalidate(contexts);
         markAsInvalidatingContext();
-        invalidate(classNames);
+        if (multipleClassLoaders)
+        {
+            final Set<String> classNames = invalidate(contexts);
+            invalidate(classNames);
+        }
+        else
+        {
+            invalidate(Collections.EMPTY_SET);            
+        }
         markAsNotInvalidatingContext();
     }
 
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java
index 8c050cf73..69e0d9566 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentMessagesSourceImplTest.java
@@ -87,7 +87,7 @@ public class ComponentMessagesSourceImplTest extends 
InternalBaseTestCase
     {
         resourceLocator = getService(ComponentResourceLocator.class);
 
-        source = new ComponentMessagesSourceImpl(false, 
simpleComponentResource.forFile("AppCatalog.properties"),
+        source = new ComponentMessagesSourceImpl(false, false, 
simpleComponentResource.forFile("AppCatalog.properties"),
                 resourceLocator, new PropertiesFileParserImpl(), tracker, 
componentRequestSelectorAnalyzer, threadLocale, componentClassResolver, logger);
     }
 
@@ -257,7 +257,7 @@ public class ComponentMessagesSourceImplTest extends 
InternalBaseTestCase
         Resource resource = 
simpleComponentResource.forFile("NoSuchAppCatalog.properties");
         List<Resource> resources = Arrays.asList(resource);
 
-        ComponentMessagesSource source = new ComponentMessagesSourceImpl(true, 
resources,
+        ComponentMessagesSource source = new ComponentMessagesSourceImpl(true, 
false, resources,
                 new PropertiesFileParserImpl(), resourceLocator, converter, 
componentRequestSelectorAnalyzer, threadLocale, componentClassResolver, logger);
 
         Messages messages = source.getMessages(model, Locale.ENGLISH);
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImplTest.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImplTest.java
index 31ff8feb9..ee303a45e 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImplTest.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/ComponentTemplateSourceImplTest.java
@@ -117,7 +117,7 @@ public class ComponentTemplateSourceImplTest extends 
InternalBaseTestCase
 
         replay();
 
-        ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, 
parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale, 
logger);
+        ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, 
false, parser, locator, converter, componentRequestSelectorAnalyzer, 
threadLocale, logger);
 
         assertSame(source.getTemplate(model, english), template);
 
@@ -166,7 +166,7 @@ public class ComponentTemplateSourceImplTest extends 
InternalBaseTestCase
 
         replay();
 
-        ComponentTemplateSourceImpl source = new 
ComponentTemplateSourceImpl(false, parser, locator, converter, 
componentRequestSelectorAnalyzer, threadLocale, logger);
+        ComponentTemplateSourceImpl source = new 
ComponentTemplateSourceImpl(false, false, parser, locator, converter, 
componentRequestSelectorAnalyzer, threadLocale, logger);
         source.addInvalidationListener(listener);
 
         assertSame(source.getTemplate(model, Locale.ENGLISH), template);
@@ -236,7 +236,7 @@ public class ComponentTemplateSourceImplTest extends 
InternalBaseTestCase
 
         replay();
 
-        ComponentTemplateSourceImpl source = new 
ComponentTemplateSourceImpl(true, parser, locator, converter, 
componentRequestSelectorAnalyzer, threadLocale, logger);
+        ComponentTemplateSourceImpl source = new 
ComponentTemplateSourceImpl(true, false, parser, locator, converter, 
componentRequestSelectorAnalyzer, threadLocale, logger);
 
         assertSame(source.getTemplate(model, Locale.ENGLISH), template);
 
@@ -274,7 +274,7 @@ public class ComponentTemplateSourceImplTest extends 
InternalBaseTestCase
 
         replay();
 
-        ComponentTemplateSourceImpl source = new 
ComponentTemplateSourceImpl(true, parser, locator, converter, 
componentRequestSelectorAnalyzer, threadLocale, logger);
+        ComponentTemplateSourceImpl source = new 
ComponentTemplateSourceImpl(true, false, parser, locator, converter, 
componentRequestSelectorAnalyzer, threadLocale, logger);
 
         ComponentTemplate template = source.getTemplate(model, Locale.ENGLISH);
 
@@ -308,7 +308,7 @@ public class ComponentTemplateSourceImplTest extends 
InternalBaseTestCase
 
         replay();
 
-        ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, 
parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale, 
logger);
+        ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, 
false, parser, locator, converter, componentRequestSelectorAnalyzer, 
threadLocale, logger);
 
         assertSame(source.getTemplate(model, english), template);
 
diff --git 
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.tml
 
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.tml
index ab2b1d10d..fafd9332c 100644
--- 
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.tml
+++ 
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/pages/nested/AssetDemo.tml
@@ -89,4 +89,6 @@
 <p>Asset with bad checksum: ${assetWithWrongChecksumUrl}</p>
 <p id="assetWithWrongChecksum" style="display: none">Asset with wrong checksum 
handled correctly.</p>
 
+<p>Message: ${message:note}</p>
+
 </html>
\ No newline at end of file


Reply via email to