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