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 af22dd7ae TAP5-2742: smart page cache invalidation for message (i18n) files af22dd7ae is described below commit af22dd7ae1cffb797492ce451b73cc8976a1cfed Author: Thiago H. de Paula Figueiredo <thi...@arsmachina.com.br> AuthorDate: Fri Dec 23 12:26:51 2022 -0300 TAP5-2742: smart page cache invalidation for message (i18n) files --- .../services/ComponentMessagesSourceImpl.java | 17 ++-- .../internal/services/MessagesSourceImpl.java | 109 ++++++++++++++++++--- .../internal/services/MessagesTrackingInfo.java | 38 +++---- .../internal/util/MessageCatalogResource.java | 6 +- .../services/ComponentMessagesSourceImplTest.java | 17 +++- 5 files changed, 145 insertions(+), 42 deletions(-) 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 223e0dca6..763dccea5 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 @@ -31,6 +31,7 @@ import org.apache.tapestry5.ioc.services.ClasspathURLConverter; import org.apache.tapestry5.ioc.services.ThreadLocale; import org.apache.tapestry5.ioc.services.UpdateListener; import org.apache.tapestry5.model.ComponentModel; +import org.apache.tapestry5.services.ComponentClassResolver; import org.apache.tapestry5.services.messages.ComponentMessagesSource; import org.apache.tapestry5.services.messages.PropertiesFileParser; import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; @@ -82,26 +83,30 @@ public class ComponentMessagesSourceImpl implements ComponentMessagesSource, Upd boolean productionMode, List<Resource> appCatalogResources, PropertiesFileParser parser, ComponentResourceLocator resourceLocator, ClasspathURLConverter classpathURLConverter, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale, Logger logger) + ThreadLocale threadLocale, ComponentClassResolver componentClassResolver, + Logger logger) { - this(productionMode, appCatalogResources, resourceLocator, parser, new URLChangeTracker(classpathURLConverter), componentRequestSelectorAnalyzer, threadLocale, logger); + this(productionMode, appCatalogResources, resourceLocator, parser, new URLChangeTracker(classpathURLConverter), + componentRequestSelectorAnalyzer, threadLocale, componentClassResolver, logger); } ComponentMessagesSourceImpl(boolean productionMode, Resource appCatalogResource, ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale, Logger logger) + ThreadLocale threadLocale, ComponentClassResolver componentClassResolver, + Logger logger) { - this(productionMode, Arrays.asList(appCatalogResource), resourceLocator, parser, tracker, componentRequestSelectorAnalyzer, threadLocale, logger); + this(productionMode, Arrays.asList(appCatalogResource), resourceLocator, parser, tracker, componentRequestSelectorAnalyzer, threadLocale, componentClassResolver, logger); } ComponentMessagesSourceImpl(boolean productionMode, List<Resource> appCatalogResources, ComponentResourceLocator resourceLocator, PropertiesFileParser parser, URLChangeTracker tracker, ComponentRequestSelectorAnalyzer componentRequestSelectorAnalyzer, - ThreadLocale threadLocale, Logger logger) + ThreadLocale threadLocale, ComponentClassResolver componentClassResolver, + Logger logger) { messagesSource = new MessagesSourceImpl(productionMode, productionMode ? null : tracker, resourceLocator, - parser, logger); + parser, componentClassResolver, logger); appCatalogBundle = createAppCatalogBundle(appCatalogResources); this.componentRequestSelectorAnalyzer = componentRequestSelectorAnalyzer; 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 ba4233716..9ef7c4f9e 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 @@ -12,6 +12,15 @@ package org.apache.tapestry5.internal.services; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + import org.apache.tapestry5.commons.Messages; import org.apache.tapestry5.commons.Resource; import org.apache.tapestry5.commons.util.CaseInsensitiveMap; @@ -20,18 +29,12 @@ import org.apache.tapestry5.commons.util.MultiKey; import org.apache.tapestry5.func.F; import org.apache.tapestry5.internal.event.InvalidationEventHubImpl; import org.apache.tapestry5.ioc.internal.util.URLChangeTracker; +import org.apache.tapestry5.services.ComponentClassResolver; 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; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - /** * A utility class that encapsulates all the logic for reading properties files and assembling {@link Messages} from * them, in accordance with extension rules and locale. This represents code that was refactored out of @@ -52,7 +55,11 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess private final PropertiesFileParser propertiesFileParser; private final ComponentResourceLocator resourceLocator; - + + private final ComponentClassResolver componentClassResolver; + + private final Logger logger; + /** * Keyed on bundle id and ComponentResourceSelector. */ @@ -73,6 +80,7 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess public MessagesSourceImpl(boolean productionMode, URLChangeTracker tracker, ComponentResourceLocator resourceLocator, PropertiesFileParser propertiesFileParser, + ComponentClassResolver componentClassResolver, Logger logger) { super(productionMode, logger); @@ -80,6 +88,8 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess this.tracker = tracker; this.propertiesFileParser = propertiesFileParser; this.resourceLocator = resourceLocator; + this.logger = logger; + this.componentClassResolver = componentClassResolver; } public void checkForUpdates() @@ -87,26 +97,77 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess if (tracker != null) { final Set<MessagesTrackingInfo> changedResources = tracker.getChangedResourcesInfo(); + if (!changedResources.isEmpty()) + { + logger.info("Changed message files: {}", changedResources.stream() + .map(MessagesTrackingInfo::getResource) + .map(Resource::toString) + .collect(Collectors.joining(", "))); + } + + boolean applicationLevelChange = false; + for (MessagesTrackingInfo info : changedResources) { + + final String className = info.getClassName(); + // An application-level file was changed, so we need to invalidate everything. - if (info == null) + if (info.getClassName() == null) { invalidate(); + applicationLevelChange = true; break; } else { + final Iterator<Entry<MultiKey, Messages>> messagesByBundleIdAndSelectorIterator = + messagesByBundleIdAndSelector.entrySet().iterator(); + + while (messagesByBundleIdAndSelectorIterator.hasNext()) + { + final Entry<MultiKey, Messages> entry = messagesByBundleIdAndSelectorIterator.next(); + if (className.equals(entry.getKey().getValues()[0])) + { + messagesByBundleIdAndSelectorIterator.remove(); + } + } + + final Iterator<Entry<MultiKey, Map<String, String>>> cookedPropertiesIterator = + cookedProperties.entrySet().iterator(); + while (cookedPropertiesIterator.hasNext()) + { + final Entry<MultiKey, Map<String, String>> entry = cookedPropertiesIterator.next(); + if (className.equals(entry.getKey().getValues()[0])) + { + cookedPropertiesIterator.remove(); + } + } + final String resourceFile = info.getResource().getFile(); + final Iterator<Entry<Resource, Map<String, String>>> rawPropertiesIterator = rawProperties.entrySet().iterator(); + while (rawPropertiesIterator.hasNext()) + { + final Entry<Resource, Map<String, String>> entry = rawPropertiesIterator.next(); + if (resourceFile.equals(entry.getKey().getFile())) + { + rawPropertiesIterator.remove(); + } + } } } - fireInvalidationEvent(changedResources.stream() - .map(ClassNameHolder::getClassName) - .filter(Objects::nonNull) - .collect(Collectors.toList())); + + if (!changedResources.isEmpty() && !applicationLevelChange) + { + fireInvalidationEvent(changedResources.stream() + .filter(Objects::nonNull) + .map(ClassNameHolder::getClassName) + .filter(Objects::nonNull) + .collect(Collectors.toList())); + } } } @@ -232,8 +293,8 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess if (tracker != null) { - MessagesTrackingInfo info = bundle != null ? new MessagesTrackingInfo( - resource.getFile(), bundle.getId(), bundle.getBaseResource().getFile()) : null; + MessagesTrackingInfo info = new MessagesTrackingInfo( + resource, bundle != null ? bundle.getId() : bundle, getClassName(bundle)); tracker.add(resource.toURL(), info); } @@ -246,4 +307,22 @@ public class MessagesSourceImpl extends InvalidationEventHubImpl implements Mess } } + private String getClassName(MessagesBundle bundle) + { + String className = null; + if (bundle != null && bundle.getBaseResource().getPath() != null) + { + final String path = bundle.getBaseResource().getPath(); + if (path.endsWith(".class")) + { + className = path.replace('/', '.').replace(".class", ""); + if (!componentClassResolver.isPage(className)) + { + className = null; + } + } + } + return className; + } + } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesTrackingInfo.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesTrackingInfo.java index 9943b261f..5eead5445 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesTrackingInfo.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MessagesTrackingInfo.java @@ -15,6 +15,8 @@ package org.apache.tapestry5.internal.services; import java.util.Objects; +import org.apache.tapestry5.commons.Resource; + /** * Class that holds information about a messages properties file for tracking. */ @@ -22,13 +24,13 @@ final public class MessagesTrackingInfo implements ClassNameHolder { private Object bundleId; - private String propertiesFile; + private Resource resource; private String className; - public MessagesTrackingInfo(String propertiesFile, Object bundleId, String className) + public MessagesTrackingInfo(Resource resource, Object bundleId, String className) { super(); - this.propertiesFile = propertiesFile; + this.resource = resource; this.className = className; this.bundleId = bundleId; } @@ -38,9 +40,9 @@ final public class MessagesTrackingInfo implements ClassNameHolder return bundleId; } - public String getPropertiesFile() + public Resource getResource() { - return propertiesFile; + return resource; } public String getClassName() @@ -49,31 +51,31 @@ final public class MessagesTrackingInfo implements ClassNameHolder } @Override - public int hashCode() + public int hashCode() { - return Objects.hash(bundleId, className, propertiesFile); + return Objects.hash(bundleId, className, resource); } @Override - public boolean equals(Object obj) + public boolean equals(Object obj) { - if (this == obj) - { + if (this == obj) return true; - } - if (!(obj instanceof MessagesTrackingInfo)) - { + if (obj == null) + return false; + if (getClass() != obj.getClass()) return false; - } MessagesTrackingInfo other = (MessagesTrackingInfo) obj; - return Objects.equals(bundleId, other.bundleId) && Objects.equals(className, other.className) && Objects.equals(propertiesFile, other.propertiesFile); + return Objects.equals(bundleId, other.bundleId) + && Objects.equals(className, other.className) + && Objects.equals(resource, other.resource); } @Override - public String toString() + public String toString() { - return "MessagesTrackingInfo [className=" + className + ", bundleId=" + bundleId + ", propertiesFile=" + propertiesFile + "]"; + return "MessagesTrackingInfo [resource=" + resource + ", className=" + className + + ", bundleId=" + bundleId + "]"; } - } \ No newline at end of file diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java index ef89f8cfe..8992efe59 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/util/MessageCatalogResource.java @@ -54,7 +54,11 @@ public class MessageCatalogResource extends VirtualResource // but that's the breaks). When that occurs, we tell the ResourceChangeTracker to fire its invalidation // event. That flushes out all the assets it has cached, including StreamableResources for JavaScript files, // including the one created here to represent the application message catalog. - changeTracker.forceInvalidationEvent(); + + // TAP-2742: now we're doing smarter page cache invalidation, + // so we don't need to clear everything anymore. + + // changeTracker.forceInvalidationEvent(); } }); } 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 a1e610454..323767acd 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 @@ -30,13 +30,16 @@ import org.apache.tapestry5.ioc.internal.util.URLChangeTracker; import org.apache.tapestry5.ioc.services.ClasspathURLConverter; import org.apache.tapestry5.ioc.services.ThreadLocale; import org.apache.tapestry5.model.ComponentModel; +import org.apache.tapestry5.services.ComponentClassResolver; import org.apache.tapestry5.services.messages.ComponentMessagesSource; import org.apache.tapestry5.services.pageload.ComponentRequestSelectorAnalyzer; import org.apache.tapestry5.services.pageload.ComponentResourceLocator; +import org.easymock.EasyMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; /** @@ -67,6 +70,16 @@ public class ComponentMessagesSourceImplTest extends InternalBaseTestCase private ComponentResourceLocator resourceLocator; private Logger logger = LoggerFactory.getLogger(ComponentMessagesSourceImplTest.class); + + private final ComponentClassResolver componentClassResolver = EasyMock.createMock(ComponentClassResolver.class); + + @BeforeMethod + public void setupMethod() + { + EasyMock.expect(componentClassResolver.isPage(EasyMock.anyString())).andReturn(false).anyTimes(); + + } + @BeforeClass public void setup() @@ -74,7 +87,7 @@ public class ComponentMessagesSourceImplTest extends InternalBaseTestCase resourceLocator = getService(ComponentResourceLocator.class); source = new ComponentMessagesSourceImpl(false, simpleComponentResource.forFile("AppCatalog.properties"), - resourceLocator, new PropertiesFileParserImpl(), tracker, componentRequestSelectorAnalyzer, threadLocale, logger); + resourceLocator, new PropertiesFileParserImpl(), tracker, componentRequestSelectorAnalyzer, threadLocale, componentClassResolver, logger); } @AfterClass @@ -244,7 +257,7 @@ public class ComponentMessagesSourceImplTest extends InternalBaseTestCase List<Resource> resources = Arrays.asList(resource); ComponentMessagesSource source = new ComponentMessagesSourceImpl(true, resources, - new PropertiesFileParserImpl(), resourceLocator, converter, componentRequestSelectorAnalyzer, threadLocale, logger); + new PropertiesFileParserImpl(), resourceLocator, converter, componentRequestSelectorAnalyzer, threadLocale, componentClassResolver, logger); Messages messages = source.getMessages(model, Locale.ENGLISH);