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 ca6835cd9 TAP5-2742: page cache invalidation on template change working ca6835cd9 is described below commit ca6835cd9670b2aa5b53c5957fcae469e881ee14 Author: Thiago H. de Paula Figueiredo <thi...@arsmachina.com.br> AuthorDate: Sat Nov 26 10:51:37 2022 -0300 TAP5-2742: page cache invalidation on template change working plus some additional logging for when invalidations are done and page instances created --- 583_RELEASE_NOTES.md | 10 ++- .../commons/services/InvalidationEventHub.java | 8 ++ .../apache/tapestry5/commons/util/MultiKey.java | 10 ++- .../internal/event/InvalidationEventHubImpl.java | 18 ++++- .../services/ComponentClassResolverImpl.java | 9 ++- .../services/ComponentDependencyRegistryImpl.java | 23 ++++-- .../services/ComponentInstantiatorSourceImpl.java | 8 +- .../services/ComponentMessagesSourceImpl.java | 13 ++-- .../services/ComponentTemplateSourceImpl.java | 55 ++++++++++---- .../InternalComponentInvalidationEventHubImpl.java | 5 +- .../internal/services/MessagesSourceImpl.java | 6 +- .../internal/services/PageSourceImpl.java | 55 +++++++++++++- .../tapestry5/internal/services/ReloadHelper.java | 2 +- .../services/ResourceDigestManagerImpl.java | 6 ++ .../services/assets/ResourceChangeTrackerImpl.java | 5 +- .../event/InvalidationEventHubImplTest.java | 7 +- .../ComponentDependencyRegistryImplTest.java | 5 ++ .../services/ComponentMessagesSourceImplTest.java | 8 +- .../services/ComponentTemplateSourceImplTest.java | 14 ++-- .../internal/AbstractReloadableObjectCreator.java | 2 +- .../ioc/internal/util/URLChangeTracker.java | 85 +++++++++++++++++++--- 21 files changed, 283 insertions(+), 71 deletions(-) diff --git a/583_RELEASE_NOTES.md b/583_RELEASE_NOTES.md index 2e6bea9dd..34e9fdf3d 100644 --- a/583_RELEASE_NOTES.md +++ b/583_RELEASE_NOTES.md @@ -1,7 +1,13 @@ Scratch pad for changes destined for the 5.8.3 release notes page. -# Non-backward-compatible changes +# Added methods + +* add(URL url, String memo) to URLChangeTracker +* getChangeResourcesMemos() to URLChangeTracker +* getValues() to MultiKey + +# Non-backward-compatible changes (but that probably won't cause problems) * New addInvalidationCallback(Function<List<String>, List<String>> callback) method in InvalidationEventHub * New getEmbeddedElementIds() method in ComponentPageElement (internal service) -* New getLogicalName() method in ComponentClassResolver. \ No newline at end of file +* New getLogicalName() method in ComponentClassResolver. diff --git a/commons/src/main/java/org/apache/tapestry5/commons/services/InvalidationEventHub.java b/commons/src/main/java/org/apache/tapestry5/commons/services/InvalidationEventHub.java index 85e3fbb69..4e0f37ec5 100644 --- a/commons/src/main/java/org/apache/tapestry5/commons/services/InvalidationEventHub.java +++ b/commons/src/main/java/org/apache/tapestry5/commons/services/InvalidationEventHub.java @@ -71,4 +71,12 @@ public interface InvalidationEventHub */ @IncompatibleChange(release = "5.8.3", details = "Added method") void addInvalidationCallback(Function<List<String>, List<String>> function); + + /** + * Notify resource-specific invalidations to listeners. + * @since 5.8.3 + */ + @IncompatibleChange(release = "5.8.3", details = "Added method") + void fireInvalidationEvent(List<String> resources); + } diff --git a/commons/src/main/java/org/apache/tapestry5/commons/util/MultiKey.java b/commons/src/main/java/org/apache/tapestry5/commons/util/MultiKey.java index bd595fc98..b9d29b8de 100644 --- a/commons/src/main/java/org/apache/tapestry5/commons/util/MultiKey.java +++ b/commons/src/main/java/org/apache/tapestry5/commons/util/MultiKey.java @@ -82,5 +82,13 @@ public final class MultiKey return builder.toString(); } - + + /** + * Returns a copy of the values array. + * @since 5.8.3 + */ + public Object[] getValues() { + return values.clone(); + } + } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/event/InvalidationEventHubImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/event/InvalidationEventHubImpl.java index 46186ee00..aab89d38c 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/event/InvalidationEventHubImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/event/InvalidationEventHubImpl.java @@ -27,6 +27,7 @@ import org.apache.tapestry5.commons.internal.util.TapestryException; import org.apache.tapestry5.commons.services.InvalidationEventHub; import org.apache.tapestry5.commons.services.InvalidationListener; import org.apache.tapestry5.commons.util.CollectionFactory; +import org.slf4j.Logger; /** * Base implementation class for classes (especially services) that need to manage a list of @@ -36,7 +37,9 @@ public class InvalidationEventHubImpl implements InvalidationEventHub { private final List<Function<List<String>, List<String>>> callbacks; - protected InvalidationEventHubImpl(boolean productionMode) + private final Logger logger; + + protected InvalidationEventHubImpl(boolean productionMode, Logger logger) { if (productionMode) { @@ -45,6 +48,7 @@ public class InvalidationEventHubImpl implements InvalidationEventHub { callbacks = CollectionFactory.newThreadSafeList(); } + this.logger = logger; } /** @@ -58,7 +62,7 @@ public class InvalidationEventHubImpl implements InvalidationEventHub /** * Notifies all listeners/callbacks. */ - protected final void fireInvalidationEvent(List<String> resources) + public final void fireInvalidationEvent(List<String> resources) { if (callbacks == null) { @@ -67,10 +71,19 @@ public class InvalidationEventHubImpl implements InvalidationEventHub final Set<String> alreadyProcessed = new HashSet<>(); + int level = 1; do { final Set<String> extraResources = new HashSet<>(); Set<String> actuallyNewResources; + if (!resources.isEmpty()) + { + logger.info("Invalidating {} resource(s) at level {}: {}", resources.size(), level, String.join(", ", resources)); + } + else + { + logger.info("Invalidating all resources"); + } for (Function<List<String>, List<String>> callback : callbacks) { final List<String> newResources = callback.apply(resources); @@ -83,6 +96,7 @@ public class InvalidationEventHubImpl implements InvalidationEventHub extraResources.addAll(actuallyNewResources); alreadyProcessed.addAll(newResources); } + level++; resources = new ArrayList<>(extraResources); } while (!resources.isEmpty()); diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java index f8c6d6df1..349755fc7 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentClassResolverImpl.java @@ -820,13 +820,14 @@ public class ComponentClassResolverImpl implements ComponentClassResolver, Inval @Override public String getLogicalName(String className) { - String result = getData().pageClassNameToLogicalName.get(className); + final Data thisData = getData(); + String result = thisData.pageClassNameToLogicalName.get(className); if (result == null) { - result = getKeyByValue(getData().componentToClassName, className); + result = getKeyByValue(thisData.componentToClassName, className); } - else { - result = getKeyByValue(getData().mixinToClassName, className); + if (result == null ){ + result = getKeyByValue(thisData.mixinToClassName, className); } return result; 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 a7d7dcae8..1810b9804 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 @@ -176,18 +176,27 @@ public class ComponentDependencyRegistryImpl implements ComponentDependencyRegis // Protected just for testing List<String> listen(List<String> resources) { - List<String> furtherDependents = new ArrayList<>(); - for (String resource : resources) + List<String> furtherDependents; + if (resources.isEmpty()) { - final Set<String> dependents = getDependents(resource); - for (String furtherDependent : dependents) + clear(); + furtherDependents = Collections.emptyList(); + } + else + { + furtherDependents = new ArrayList<>(); + for (String resource : resources) { - if (!resources.contains(furtherDependent) && !furtherDependents.contains(furtherDependent)) + final Set<String> dependents = getDependents(resource); + for (String furtherDependent : dependents) { - furtherDependents.add(furtherDependent); + if (!resources.contains(furtherDependent) && !furtherDependents.contains(furtherDependent)) + { + furtherDependents.add(furtherDependent); + } } + clear(resource); } - clear(resource); } return furtherDependents; } 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 09c568f41..c3e91f270 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,7 @@ package org.apache.tapestry5.internal.services; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; @@ -172,9 +173,10 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia public synchronized void checkForUpdates() { - if (changeTracker.containsChanges()) + final Set<String> changedResources = changeTracker.getChangedResourcesMemos(); + if (!changedResources.isEmpty()) { - invalidationHub.classInControlledPackageHasChanged(); + invalidationHub.fireInvalidationEvent(new ArrayList<>(changedResources)); } } @@ -306,7 +308,7 @@ public final class ComponentInstantiatorSourceImpl implements ComponentInstantia Resource baseResource = new ClasspathResource(parent, PlasticInternalUtils .toClassPath(className)); - changeTracker.add(baseResource.toURL()); + changeTracker.add(baseResource.toURL(), className); if (isRoot) { 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 1a97c9551..223e0dca6 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 @@ -36,6 +36,7 @@ import org.apache.tapestry5.services.messages.PropertiesFileParser; import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; import org.apache.tapestry5.services.pageload.ComponentResourceLocator; import org.apache.tapestry5.services.pageload.ComponentResourceSelector; +import org.slf4j.Logger; public class ComponentMessagesSourceImpl implements ComponentMessagesSource, UpdateListener { @@ -81,26 +82,26 @@ public class ComponentMessagesSourceImpl implements ComponentMessagesSource, Upd boolean productionMode, List<Resource> appCatalogResources, PropertiesFileParser parser, ComponentResourceLocator resourceLocator, ClasspathURLConverter classpathURLConverter, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale) + ThreadLocale threadLocale, Logger logger) { - this(productionMode, appCatalogResources, resourceLocator, parser, new URLChangeTracker(classpathURLConverter), componentRequestSelectorAnalyzer, threadLocale); + this(productionMode, appCatalogResources, resourceLocator, parser, new URLChangeTracker(classpathURLConverter), componentRequestSelectorAnalyzer, threadLocale, logger); } ComponentMessagesSourceImpl(boolean productionMode, Resource appCatalogResource, ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale) + ThreadLocale threadLocale, Logger logger) { - this(productionMode, Arrays.asList(appCatalogResource), resourceLocator, parser, tracker, componentRequestSelectorAnalyzer, threadLocale); + this(productionMode, Arrays.asList(appCatalogResource), resourceLocator, parser, tracker, componentRequestSelectorAnalyzer, threadLocale, logger); } ComponentMessagesSourceImpl(boolean productionMode, List<Resource> appCatalogResources, ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale) + ThreadLocale threadLocale, Logger logger) { messagesSource = new MessagesSourceImpl(productionMode, productionMode ? null : tracker, resourceLocator, - parser); + parser, logger); appCatalogBundle = createAppCatalogBundle(appCatalogResources); this.componentRequestSelectorAnalyzer = componentRequestSelectorAnalyzer; 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 71bac204b..f8e2b83ae 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 @@ -12,6 +12,15 @@ package org.apache.tapestry5.internal.services; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + import org.apache.tapestry5.TapestryConstants; import org.apache.tapestry5.commons.Location; import org.apache.tapestry5.commons.Resource; @@ -34,12 +43,7 @@ import org.apache.tapestry5.model.ComponentModel; import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; import org.apache.tapestry5.services.pageload.ComponentResourceLocator; import org.apache.tapestry5.services.pageload.ComponentResourceSelector; -import org.apache.tapestry5.services.templates.ComponentTemplateLocator; - -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; +import org.slf4j.Logger; /** * Service implementation that manages a cache of parsed component templates. @@ -56,6 +60,8 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl private final ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer; private final ThreadLocale threadLocale; + + private final Logger logger; /** * Caches from a key (combining component name and locale) to a resource. Often, many different keys will point to @@ -112,22 +118,23 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl boolean productionMode, TemplateParser parser, ComponentResourceLocator locator, ClasspathURLConverter classpathURLConverter, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale) + ThreadLocale threadLocale, Logger logger) { - this(productionMode, parser, locator, new URLChangeTracker(classpathURLConverter), componentRequestSelectorAnalyzer, threadLocale); + this(productionMode, parser, locator, new URLChangeTracker(classpathURLConverter), componentRequestSelectorAnalyzer, threadLocale, logger); } ComponentTemplateSourceImpl(boolean productionMode, TemplateParser parser, ComponentResourceLocator locator, URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale) + ThreadLocale threadLocale, Logger logger) { - super(productionMode); + super(productionMode, logger); this.parser = parser; this.locator = locator; this.tracker = tracker; this.componentRequestSelectorAnalyzer = componentRequestSelectorAnalyzer; this.threadLocale = threadLocale; + this.logger = logger; } @PostInjection @@ -170,7 +177,7 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl if (result == null) { - result = parseTemplate(resource); + result = parseTemplate(resource, componentModel.getComponentClassName()); templates.put(resource, result); } @@ -196,7 +203,7 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl } } - private ComponentTemplate parseTemplate(Resource r) + private ComponentTemplate parseTemplate(Resource r, String className) { // In a race condition, we may parse the same template more than once. This will likely add // the resource to the tracker multiple times. Not likely this will cause a big issue. @@ -204,7 +211,7 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl if (!r.exists()) return missingTemplate; - tracker.add(r.toURL()); + tracker.add(r.toURL(), className); return parser.parseTemplate(r); } @@ -235,12 +242,30 @@ public final class ComponentTemplateSourceImpl extends InvalidationEventHubImpl * Checks to see if any parsed resource has changed. If so, then all internal caches are cleared, and an * invalidation event is fired. This is brute force ... a more targeted dependency management strategy may come * later. + * Actually, TAP5-2742 did exactly that! :D */ public void checkForUpdates() { - if (tracker.containsChanges()) + final Set<String> changedResourcesMemos = tracker.getChangedResourcesMemos(); + if (!changedResourcesMemos.isEmpty()) { - invalidate(); + logger.info("Changed template(s) found: {}", String.join(", ", changedResourcesMemos)); + + final Iterator<Entry<MultiKey, Resource>> templateResourcesIterator = templateResources.entrySet().iterator(); + for (String className : changedResourcesMemos) + { + while (templateResourcesIterator.hasNext()) + { + final MultiKey key = templateResourcesIterator.next().getKey(); + if (className.equals((String) key.getValues()[0])) + { + templates.remove(templateResources.get(key)); + templateResourcesIterator.remove(); + } + } + } + + fireInvalidationEvent(new ArrayList<>(changedResourcesMemos)); } } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java index e536a7766..fb1864edc 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalComponentInvalidationEventHubImpl.java @@ -18,14 +18,15 @@ import org.apache.tapestry5.http.TapestryHttpSymbolConstants; import org.apache.tapestry5.internal.event.InvalidationEventHubImpl; import org.apache.tapestry5.ioc.annotations.PostInjection; import org.apache.tapestry5.ioc.annotations.Symbol; +import org.slf4j.Logger; public class InternalComponentInvalidationEventHubImpl extends InvalidationEventHubImpl implements InternalComponentInvalidationEventHub { public InternalComponentInvalidationEventHubImpl(@Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) - boolean productionMode) + boolean productionMode, Logger logger) { - super(productionMode); + super(productionMode, logger); } @PostInjection 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 adeddc167..43b7e70d1 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 @@ -23,6 +23,7 @@ import org.apache.tapestry5.ioc.internal.util.URLChangeTracker; import org.apache.tapestry5.services.messages.PropertiesFileParser; import org.apache.tapestry5.services.pageload.ComponentResourceLocator; import org.apache.tapestry5.services.pageload.ComponentResourceSelector; +import org.slf4j.Logger; import java.util.Collections; import java.util.List; @@ -68,9 +69,10 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess private final Map<String, String> emptyMap = Collections.emptyMap(); public MessagesSourceImpl(boolean productionMode, URLChangeTracker tracker, - ComponentResourceLocator resourceLocator, PropertiesFileParser propertiesFileParser) + ComponentResourceLocator resourceLocator, PropertiesFileParser propertiesFileParser, + Logger logger) { - super(productionMode); + super(productionMode, logger); this.tracker = tracker; this.propertiesFileParser = propertiesFileParser; 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 e9db70ad9..33736dade 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 @@ -22,13 +22,19 @@ import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker; 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.services.ComponentClassResolver; import org.apache.tapestry5.services.ComponentMessages; import org.apache.tapestry5.services.ComponentTemplates; 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 @@ -38,6 +44,10 @@ public class PageSourceImpl implements PageSource private final PageLoader pageLoader; private final ComponentDependencyRegistry componentDependencyRegistry; + + private final ComponentClassResolver componentClassResolver; + + private final Logger logger; private static final class CachedPageKey { @@ -73,11 +83,15 @@ public class PageSourceImpl implements PageSource private final Map<CachedPageKey, SoftReference<Page>> pageCache = CollectionFactory.newConcurrentMap(); public PageSourceImpl(PageLoader pageLoader, ComponentRequestSelectorAnalyzer selectorAnalyzer, - ComponentDependencyRegistry componentDependencyRegistry) + ComponentDependencyRegistry componentDependencyRegistry, + ComponentClassResolver componentClassResolver, + Logger logger) { this.pageLoader = pageLoader; this.selectorAnalyzer = selectorAnalyzer; this.componentDependencyRegistry = componentDependencyRegistry; + this.componentClassResolver = componentClassResolver; + this.logger = logger; } public Page getPage(String canonicalPageName) @@ -121,9 +135,9 @@ public class PageSourceImpl implements PageSource @ComponentMessages InvalidationEventHub messagesHub, ResourceChangeTracker resourceChangeTracker) { - classesHub.clearOnInvalidation(pageCache); - templatesHub.clearOnInvalidation(pageCache); - messagesHub.clearOnInvalidation(pageCache); + classesHub.addInvalidationCallback(this::listen); + templatesHub.addInvalidationCallback(this::listen); + messagesHub.addInvalidationCallback(this::listen); // Because Assets can be injected into pages, and Assets are invalidated when // an Asset's value is changed (partly due to the change, in 5.4, to include the asset's @@ -131,9 +145,42 @@ public class PageSourceImpl implements PageSource // any Resource, it is necessary to discard all page instances. resourceChangeTracker.clearOnInvalidation(pageCache); } + + private List<String> listen(List<String> resources) + { + + if (resources.isEmpty()) + { + clearCache(); + } + else + { + String pageName; + for (String className : resources) + { + pageName = componentClassResolver.getLogicalName(className); + if (pageName != null && !pageName.isEmpty()) + { + final Iterator<Entry<CachedPageKey, SoftReference<Page>>> iterator = pageCache.entrySet().iterator(); + while (iterator.hasNext()) + { + final Entry<CachedPageKey, SoftReference<Page>> entry = iterator.next(); + if (entry.getKey().pageName.equalsIgnoreCase(pageName)) + { + logger.info("Clearing cached page '{}'", pageName); + iterator.remove(); + } + } + } + } + } + + return Collections.emptyList(); + } public void clearCache() { + logger.info("Clearing page cache"); pageCache.clear(); } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java index 91a8fec2c..d0266b088 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ReloadHelper.java @@ -15,7 +15,7 @@ package org.apache.tapestry5.internal.services; /** - * Forces a reload of all caches and invalidates the component class cache. This is only allowed + * Forces a reload of all caches and invalidates the component class cache. This is only allowed when production mode is off. * * @since 5.4 */ diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceDigestManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceDigestManagerImpl.java index 08ca9c066..eb0e78939 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceDigestManagerImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ResourceDigestManagerImpl.java @@ -49,4 +49,10 @@ public class ResourceDigestManagerImpl implements ResourceDigestManager public void addInvalidationCallback(Function<List<String>, List<String>> function) { } + + @Override + public void fireInvalidationEvent(List<String> resources) + { + } + } 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 917af3a7e..de9fbc166 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 @@ -23,6 +23,7 @@ import org.apache.tapestry5.ioc.internal.util.URLChangeTracker; import org.apache.tapestry5.ioc.services.ClasspathURLConverter; import org.apache.tapestry5.ioc.services.UpdateListener; import org.apache.tapestry5.ioc.services.UpdateListenerHub; +import org.slf4j.Logger; public class ResourceChangeTrackerImpl extends InvalidationEventHubImpl implements ResourceChangeTracker, UpdateListener @@ -38,9 +39,9 @@ public class ResourceChangeTrackerImpl extends InvalidationEventHubImpl implemen public ResourceChangeTrackerImpl(ClasspathURLConverter classpathURLConverter, @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) - boolean productionMode) + boolean productionMode, Logger logger) { - super(productionMode); + super(productionMode, logger); // Use granularity of seconds (not milliseconds) since that works properly // with response headers for identifying last modified. Don't track diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/event/InvalidationEventHubImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/event/InvalidationEventHubImplTest.java index 7112126d9..9edb441c3 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/event/InvalidationEventHubImplTest.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/event/InvalidationEventHubImplTest.java @@ -19,6 +19,7 @@ import java.util.function.Function; import org.apache.tapestry5.commons.internal.util.TapestryException; import org.apache.tapestry5.internal.services.ComponentTemplateSourceImplTest; +import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.annotations.Test; @@ -36,7 +37,7 @@ public class InvalidationEventHubImplTest @Test public void add_invalidation_callback_with_parameter() { - InvalidationEventHubImpl invalidationEventHub = new InvalidationEventHubImpl(false); + InvalidationEventHubImpl invalidationEventHub = new InvalidationEventHubImpl(false, LoggerFactory.getLogger(InvalidationEventHubImpl.class)); final String firstInitialElement = "a"; final String secondInitialElement = "b"; final List<String> initialResources = Arrays.asList(firstInitialElement, secondInitialElement); @@ -65,9 +66,9 @@ public class InvalidationEventHubImplTest @Test(expectedExceptions = TapestryException.class) public void null_check_for_callback_method() { - InvalidationEventHubImpl invalidationEventHub = new InvalidationEventHubImpl(false); + InvalidationEventHubImpl invalidationEventHub = new InvalidationEventHubImpl(false, LoggerFactory.getLogger(InvalidationEventHubImpl.class)); invalidationEventHub.addInvalidationCallback((s) -> null); - invalidationEventHub.fireInvalidationEvent(); + invalidationEventHub.fireInvalidationEvent(Arrays.asList("a")); } } 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 e6fc2bd86..4a1c9e92a 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 @@ -53,6 +53,11 @@ public class ComponentDependencyRegistryImplTest Collections.sort(result); assertEquals(result, Arrays.asList("d", "dd")); + final List<String> returnValue = componentDependencyRegistry.listen(Collections.emptyList()); + assertEquals(returnValue, Collections.emptyList()); + assertEquals(componentDependencyRegistry.getDependents("bar").size(), 0); + assertEquals(componentDependencyRegistry.getDependencies("foo").size(), 0); + } @Test 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 7a0247805..a1e610454 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 @@ -33,6 +33,8 @@ import org.apache.tapestry5.model.ComponentModel; import org.apache.tapestry5.services.messages.ComponentMessagesSource; import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; import org.apache.tapestry5.services.pageload.ComponentResourceLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -63,6 +65,8 @@ public class ComponentMessagesSourceImplTest extends InternalBaseTestCase private ComponentMessagesSourceImpl source; private ComponentResourceLocator resourceLocator; + + private Logger logger = LoggerFactory.getLogger(ComponentMessagesSourceImplTest.class); @BeforeClass public void setup() @@ -70,7 +74,7 @@ public class ComponentMessagesSourceImplTest extends InternalBaseTestCase resourceLocator = getService(ComponentResourceLocator.class); source = new ComponentMessagesSourceImpl(false, simpleComponentResource.forFile("AppCatalog.properties"), - resourceLocator, new PropertiesFileParserImpl(), tracker, componentRequestSelectorAnalyzer, threadLocale); + resourceLocator, new PropertiesFileParserImpl(), tracker, componentRequestSelectorAnalyzer, threadLocale, logger); } @AfterClass @@ -240,7 +244,7 @@ public class ComponentMessagesSourceImplTest extends InternalBaseTestCase List<Resource> resources = Arrays.asList(resource); ComponentMessagesSource source = new ComponentMessagesSourceImpl(true, resources, - new PropertiesFileParserImpl(), resourceLocator, converter, componentRequestSelectorAnalyzer, threadLocale); + new PropertiesFileParserImpl(), resourceLocator, converter, componentRequestSelectorAnalyzer, threadLocale, 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 63a89d586..e44a4580a 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 @@ -35,6 +35,8 @@ import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; import org.apache.tapestry5.services.pageload.ComponentResourceLocator; import org.apache.tapestry5.services.pageload.ComponentResourceSelector; import org.apache.tapestry5.services.templates.ComponentTemplateLocator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.testng.annotations.Test; public class ComponentTemplateSourceImplTest extends InternalBaseTestCase @@ -53,6 +55,8 @@ public class ComponentTemplateSourceImplTest extends InternalBaseTestCase private final ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer = new DefaultComponentRequestSelectorAnalyzer(threadLocale); + + private final Logger logger = LoggerFactory.getLogger(ComponentTemplateSourceImplTest.class); /** * Creates a new class loader, whose parent is the thread's context class loader, but adds a single classpath root @@ -111,7 +115,7 @@ public class ComponentTemplateSourceImplTest extends InternalBaseTestCase replay(); - ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale); + ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale, logger); assertSame(source.getTemplate(model, english), template); @@ -160,7 +164,7 @@ public class ComponentTemplateSourceImplTest extends InternalBaseTestCase replay(); - ComponentTemplateSourceImpl source = new ComponentTemplateSourceImpl(false, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale); + ComponentTemplateSourceImpl source = new ComponentTemplateSourceImpl(false, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale, logger); source.addInvalidationListener(listener); assertSame(source.getTemplate(model, Locale.ENGLISH), template); @@ -228,7 +232,7 @@ public class ComponentTemplateSourceImplTest extends InternalBaseTestCase replay(); - ComponentTemplateSourceImpl source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale); + ComponentTemplateSourceImpl source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale, logger); assertSame(source.getTemplate(model, Locale.ENGLISH), template); @@ -266,7 +270,7 @@ public class ComponentTemplateSourceImplTest extends InternalBaseTestCase replay(); - ComponentTemplateSourceImpl source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale); + ComponentTemplateSourceImpl source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale, logger); ComponentTemplate template = source.getTemplate(model, Locale.ENGLISH); @@ -298,7 +302,7 @@ public class ComponentTemplateSourceImplTest extends InternalBaseTestCase replay(); - ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale); + ComponentTemplateSource source = new ComponentTemplateSourceImpl(true, parser, locator, converter, componentRequestSelectorAnalyzer, threadLocale, logger); assertSame(source.getTemplate(model, english), template); diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/AbstractReloadableObjectCreator.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/AbstractReloadableObjectCreator.java index 0efb4fd24..fa9d56b0e 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/AbstractReloadableObjectCreator.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/AbstractReloadableObjectCreator.java @@ -284,7 +284,7 @@ public abstract class AbstractReloadableObjectCreator implements ObjectCreator, if (isFileURL(url)) { - changeTracker.add(url); + changeTracker.add(url, className); } } diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/URLChangeTracker.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/URLChangeTracker.java index aa2c85df8..2e78e0697 100644 --- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/URLChangeTracker.java +++ b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/util/URLChangeTracker.java @@ -22,7 +22,9 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.net.URL; +import java.util.HashSet; import java.util.Map; +import java.util.Set; /** * Given a (growing) set of URLs, can periodically check to see if any of the underlying resources has changed. This @@ -35,7 +37,7 @@ public class URLChangeTracker { private static final long FILE_DOES_NOT_EXIST_TIMESTAMP = -1L; - private final Map<File, Long> fileToTimestamp = CollectionFactory.newConcurrentMap(); + private final Map<File, TrackingInfo> fileToTimestamp = CollectionFactory.newConcurrentMap(); private final boolean granularitySeconds; @@ -135,6 +137,23 @@ public class URLChangeTracker * null */ public long add(URL url) + { + return add(url, null); + } + /** + * Stores a new URL and associated memo (most probably a related class name) + * into the tracker, or returns the previous time stamp for a previously added URL. Filters out all + * non-file URLs. + * + * @param url + * of the resource to add, or null if not known + * @param memo + * some information about the tracked URL, most probably the associated class name + * @return the current timestamp for the URL (possibly rounded off for granularity reasons), or 0 if the URL is + * null + * @since 5.8.3 + */ + public long add(URL url, String memo) { if (url == null) return 0; @@ -147,14 +166,14 @@ public class URLChangeTracker File resourceFile = toFileFromFileProtocolURL(converted); if (fileToTimestamp.containsKey(resourceFile)) - return fileToTimestamp.get(resourceFile); + return fileToTimestamp.get(resourceFile).timestamp; long timestamp = readTimestamp(resourceFile); // A quick and imperfect fix for TAPESTRY-1918. When a file // is added, add the directory containing the file as well. - fileToTimestamp.put(resourceFile, timestamp); + fileToTimestamp.put(resourceFile, new TrackingInfo(timestamp, memo)); if (trackFolderChanges) { @@ -163,7 +182,7 @@ public class URLChangeTracker if (!fileToTimestamp.containsKey(dir)) { long dirTimestamp = readTimestamp(dir); - fileToTimestamp.put(dir, dirTimestamp); + fileToTimestamp.put(dir, new TrackingInfo(dirTimestamp, null)); } } @@ -205,20 +224,48 @@ public class URLChangeTracker // concurrently, but CheckForUpdatesFilter ensures that it will be invoked // synchronously. - for (Map.Entry<File, Long> entry : fileToTimestamp.entrySet()) + for (Map.Entry<File, TrackingInfo> entry : fileToTimestamp.entrySet()) { long newTimestamp = readTimestamp(entry.getKey()); - long current = entry.getValue(); + long current = entry.getValue().timestamp; if (current == newTimestamp) continue; result = true; - entry.setValue(newTimestamp); + entry.getValue().timestamp = newTimestamp; } return result; } + + /** + * Re-acquires the last updated timestamp for each URL and returns the memo value for all files with a changed timestamp. + */ + public Set<String> getChangedResourcesMemos() + { + + Set<String> changedResourcesMemos = new HashSet<>(); + + for (Map.Entry<File, TrackingInfo> entry : fileToTimestamp.entrySet()) + { + long newTimestamp = readTimestamp(entry.getKey()); + final TrackingInfo value = entry.getValue(); + long current = value.timestamp; + + if (current != newTimestamp) + { + if (value.memo != null) + { + changedResourcesMemos.add(value.memo); + } + value.timestamp = newTimestamp; + } + } + + return changedResourcesMemos; + } + /** * Returns the time that the specified file was last modified, possibly rounded down to the nearest second. @@ -249,9 +296,9 @@ public class URLChangeTracker */ public void forceChange() { - for (Map.Entry<File, Long> e : fileToTimestamp.entrySet()) + for (Map.Entry<File, TrackingInfo> e : fileToTimestamp.entrySet()) { - e.setValue(0l); + e.getValue().timestamp = 0l; } } @@ -262,5 +309,25 @@ public class URLChangeTracker { return fileToTimestamp.size(); } + + private static final class TrackingInfo + { + + private long timestamp; + private String memo; + + public TrackingInfo(long timestamp, String memo) + { + this.timestamp = timestamp; + this.memo = memo; + } + + @Override + public String toString() + { + return "Info [timestamp=" + timestamp + ", memo=" + memo + "]"; + } + + } }