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 <[email protected]>
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 + "]";
+ }
+
+ }
}