This is an automated email from the ASF dual-hosted git repository. diru pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
commit 184243311bb2b02021ce21e5adc88402266c01c6 Author: Dirk Rudolph <d...@apache.org> AuthorDate: Mon Jun 7 15:32:34 2021 +0200 add API to schedule sitemap regeneration --- sitemap/pom.xml | 9 +- .../org/apache/sling/sitemap/SitemapService.java | 29 +++++ .../sling/sitemap/impl/SitemapScheduler.java | 46 +++++-- .../sling/sitemap/impl/SitemapServiceImpl.java | 73 ++++++++++- .../impl/SitemapServiceImplSchedulingTest.java | 135 +++++++++++++++++++++ 5 files changed, 273 insertions(+), 19 deletions(-) diff --git a/sitemap/pom.xml b/sitemap/pom.xml index 0302154..cea534d 100644 --- a/sitemap/pom.xml +++ b/sitemap/pom.xml @@ -137,6 +137,10 @@ <artifactId>org.osgi.service.event</artifactId> </dependency> <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.tracker</artifactId> + </dependency> + <dependency> <groupId>org.apache.jackrabbit</groupId> <artifactId>jackrabbit-jcr-commons</artifactId> <version>2.21.3</version> @@ -221,11 +225,6 @@ </dependency> <dependency> <groupId>org.osgi</groupId> - <artifactId>org.osgi.util.tracker</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>org.osgi</groupId> <artifactId>org.osgi.resource</artifactId> <scope>test</scope> </dependency> diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/SitemapService.java b/sitemap/src/main/java/org/apache/sling/sitemap/SitemapService.java index 190db7f..a5aa2b9 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/SitemapService.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/SitemapService.java @@ -34,6 +34,35 @@ public interface SitemapService { String PROPERTY_SITEMAP_ROOT = "sitemapRoot"; /** + * Calls all registered SitemapSchedulers to schedule (re)generation for all sitemap roots and names. + */ + void scheduleGeneration(); + + /** + * Calls all registered SitemapSchedulers registered for the given name to schedule (re)generation. + * + * @param name + */ + void scheduleGeneration(String name); + + /** + * Calls all registered SitemapSchedulers with a search path containing the given resource to schedule + * (re)generation for all names. + * + * @param sitemapRoot + */ + void scheduleGeneration(Resource sitemapRoot); + + /** + * Calls all registered SitemapSchedulers with a search path containing the given resource and being registered for + * the given name to schedule (re)generation. + * + * @param sitemapRoot + * @param name + */ + void scheduleGeneration(Resource sitemapRoot, String name); + + /** * Returns the urls to the given {@link Resource}'s sitemaps, if any. * <p> * The returned urls may contain a sitemap index when there are multiple sitemaps generated for the given sitemap diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapScheduler.java b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapScheduler.java index 078e938..0ce4615 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapScheduler.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapScheduler.java @@ -42,10 +42,10 @@ import java.util.*; import static org.apache.sling.sitemap.impl.SitemapUtil.findSitemapRoots; @Component( - service = Runnable.class, + service = {SitemapScheduler.class, Runnable.class}, configurationPolicy = ConfigurationPolicy.REQUIRE, property = { - Scheduler.PROPERTY_SCHEDULER_CONCURRENT + "=false", + Scheduler.PROPERTY_SCHEDULER_CONCURRENT + ":Boolean=false", Scheduler.PROPERTY_SCHEDULER_RUN_ON + "=" + Scheduler.VALUE_RUN_ON_SINGLE } ) @@ -94,25 +94,45 @@ public class SitemapScheduler implements Runnable { @Override public void run() { + run(Arrays.asList(names)); + } + + public void run(String name) { + run(Collections.singleton(name)); + } + + public void run(Resource sitemapRoot) { + run(sitemapRoot, Arrays.asList(names)); + } + + public void run(Resource sitemapRoot, Collection<String> names) { + Set<String> applicableNames = generatorManager.getApplicableNames(sitemapRoot, names); + for (String applicableName : applicableNames) { + Map<String, Object> jobProperties = new HashMap<>(); + jobProperties.put(SitemapGeneratorExecutor.JOB_PROPERTY_SITEMAP_NAME, applicableName); + jobProperties.put(SitemapGeneratorExecutor.JOB_PROPERTY_SITEMAP_ROOT, sitemapRoot.getPath()); + Job job = jobManager.addJob(SitemapGeneratorExecutor.JOB_TOPIC, jobProperties); + LOG.debug("Added job {}", job.getId()); + } + } + + void run(ResourceResolver resolver) { + run(resolver, Arrays.asList(names)); + } + + private void run(Collection<String> names) { try (ResourceResolver resolver = resourceResolverFactory.getServiceResourceResolver(AUTH)) { - run(resolver); + run(resolver, names); } catch (LoginException ex) { LOG.warn("Failed start sitemap jobs: {}", ex.getMessage(), ex); } } - public void run(ResourceResolver resolver) { + private void run(ResourceResolver resolver, Collection<String> names) { Iterator<Resource> sitemapRoots = findSitemapRoots(resolver, searchPath); while (sitemapRoots.hasNext()) { - Resource sitemapRoot = sitemapRoots.next(); - Set<String> applicableNames = generatorManager.getApplicableNames(sitemapRoot, Arrays.asList(names)); - for (String applicableName : applicableNames) { - Map<String, Object> jobProperties = new HashMap<>(); - jobProperties.put(SitemapGeneratorExecutor.JOB_PROPERTY_SITEMAP_NAME, applicableName); - jobProperties.put(SitemapGeneratorExecutor.JOB_PROPERTY_SITEMAP_ROOT, sitemapRoot.getPath()); - Job job = jobManager.addJob(SitemapGeneratorExecutor.JOB_TOPIC, jobProperties); - LOG.debug("Added job {}", job.getId()); - } + run(sitemapRoots.next(), names); } } + } diff --git a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java index aa3703c..b8925e9 100644 --- a/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java +++ b/sitemap/src/main/java/org/apache/sling/sitemap/impl/SitemapServiceImpl.java @@ -26,10 +26,12 @@ import org.apache.sling.sitemap.SitemapService; import org.apache.sling.sitemap.common.SitemapLinkExternalizer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.osgi.framework.*; import org.osgi.service.component.annotations.*; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.Designate; import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -67,15 +69,23 @@ public class SitemapServiceImpl implements SitemapService { @Reference private SitemapStorage storage; + private ServiceTracker<SitemapScheduler, SitemapScheduler> schedulers; private Set<String> onDemandNames; private int maxSize; private int maxEntries; @Activate - protected void activate(Configuration configuration) { + protected void activate(Configuration configuration, BundleContext bundleContext) { onDemandNames = Arrays.stream(configuration.onDemandNames()).collect(Collectors.toSet()); maxSize = configuration.maxSize(); maxEntries = configuration.maxEntries(); + schedulers = new ServiceTracker<>(bundleContext, SitemapScheduler.class, null); + schedulers.open(); + } + + @Deactivate + protected void deactivate() { + schedulers.close(); } public Set<String> getSitemapNamesServedOnDemand() { @@ -86,6 +96,67 @@ public class SitemapServiceImpl implements SitemapService { return size <= maxSize && entries <= maxEntries; } + @Override + public void scheduleGeneration() { + if (schedulers.getServiceReferences() == null) { + return; + } + for (ServiceReference<SitemapScheduler> scheduler : schedulers.getServiceReferences()) { + schedulers.getService(scheduler).run(); + } + } + + @Override + public void scheduleGeneration(String name) { + if (schedulers.getServiceReferences() == null) { + return; + } + try { + Filter filter = FrameworkUtil.createFilter("(names=" + name + ")"); + for (ServiceReference<SitemapScheduler> scheduler : schedulers.getServiceReferences()) { + if (filter.match(scheduler)) { + schedulers.getService(scheduler).run(name); + } + } + } catch (InvalidSyntaxException ex) { + LOG.warn("Failed to build filter for given argument: {}", name, ex); + } + } + + @Override + public void scheduleGeneration(Resource sitemapRoot) { + if (schedulers.getServiceReferences() == null || !SitemapUtil.isSitemapRoot(sitemapRoot)) { + return; + } + for (ServiceReference<SitemapScheduler> scheduler : schedulers.getServiceReferences()) { + Object searchPath = scheduler.getProperty("searchPath"); + if (searchPath instanceof String && sitemapRoot.getPath().startsWith(searchPath + "/")) { + schedulers.getService(scheduler).run(sitemapRoot); + } + } + } + + @Override + public void scheduleGeneration(Resource sitemapRoot, String name) { + if (schedulers.getServiceReferences() == null || !SitemapUtil.isSitemapRoot(sitemapRoot)) { + return; + } + try { + Filter filter = FrameworkUtil.createFilter("(names=" + name + ")"); + for (ServiceReference<SitemapScheduler> scheduler : schedulers.getServiceReferences()) { + if (!filter.match(scheduler)) { + continue; + } + Object searchPath = scheduler.getProperty("searchPath"); + if (searchPath instanceof String && sitemapRoot.getPath().startsWith(searchPath + "/")) { + schedulers.getService(scheduler).run(sitemapRoot, Collections.singleton(name)); + } + } + } catch (InvalidSyntaxException ex) { + LOG.warn("Failed to build filter for given argument: {}", name, ex); + } + } + @NotNull @Override public Collection<SitemapInfo> getSitemapInfo(@NotNull Resource resource) { diff --git a/sitemap/src/test/java/org/apache/sling/sitemap/impl/SitemapServiceImplSchedulingTest.java b/sitemap/src/test/java/org/apache/sling/sitemap/impl/SitemapServiceImplSchedulingTest.java new file mode 100644 index 0000000..c495344 --- /dev/null +++ b/sitemap/src/test/java/org/apache/sling/sitemap/impl/SitemapServiceImplSchedulingTest.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.sitemap.impl; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.event.jobs.JobManager; +import org.apache.sling.serviceusermapping.ServiceUserMapped; +import org.apache.sling.sitemap.generator.SitemapGenerator; +import org.apache.sling.testing.mock.sling.junit5.SlingContext; +import org.apache.sling.testing.mock.sling.junit5.SlingContextExtension; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Collections; + +import static org.mockito.Mockito.*; + +@ExtendWith({SlingContextExtension.class, MockitoExtension.class}) +public class SitemapServiceImplSchedulingTest { + + public final SlingContext context = new SlingContext(); + + private final SitemapServiceImpl subject = new SitemapServiceImpl(); + private final SitemapStorage storage = new SitemapStorage(); + private final SitemapGeneratorManager generatorManager = new SitemapGeneratorManager(); + + @Mock + private ServiceUserMapped serviceUser; + @Mock + private JobManager jobManager; + @Mock + private SitemapGenerator generator; + + private Resource root1; + private Resource root2; + private SitemapScheduler scheduler1; + private SitemapScheduler scheduler2; + + @BeforeEach + public void setup() { + root1 = context.create().resource("/content/site/de", Collections.singletonMap( + "sitemapRoot", Boolean.TRUE + )); + root2 = context.create().resource("/content/microsite/de", Collections.singletonMap( + "sitemapRoot", Boolean.TRUE + )); + + context.registerService(ServiceUserMapped.class, serviceUser, "subServiceName", "sitemap-writer"); + context.registerService(ServiceUserMapped.class, serviceUser, "subServiceName", "sitemap-reader"); + context.registerService(SitemapGenerator.class, generator); + context.registerService(JobManager.class, jobManager); + context.registerInjectActivateService(generatorManager); + context.registerInjectActivateService(storage); + context.registerInjectActivateService(subject); + + scheduler1 = context.registerInjectActivateService(spy(new SitemapScheduler()), + "names", "<default>", + "searchPath", "/content/site" + ); + scheduler2 = context.registerInjectActivateService(spy(new SitemapScheduler()), + "names", new String[]{"<default>", "foo"}, + "searchPath", "/content/microsite" + ); + } + + @Test + public void testAllSchedulersCalled() { + // when + subject.scheduleGeneration(); + + // then + verify(scheduler1, times(1)).run(); + verify(scheduler2, times(1)).run(); + } + + @Test + public void testAllSchedulersCalledForName() { + // when + subject.scheduleGeneration("<default>"); + + // then + verify(scheduler1, times(1)).run("<default>"); + verify(scheduler2, times(1)).run("<default>"); + } + + @Test + public void testSchedulersCalledForName() { + // when + subject.scheduleGeneration("foo"); + + // then + verify(scheduler1, never()).run(); + verify(scheduler2, times(1)).run("foo"); + } + + @Test + public void testSchedulersCalledForPath() { + // when + subject.scheduleGeneration(root1); + + // then + verify(scheduler1, times(1)).run(root1); + verify(scheduler2, never()).run(); + } + + @Test + public void testSchedulersCalledForPathAndName() { + // when + subject.scheduleGeneration(root1, "foo"); + subject.scheduleGeneration(root2, "foo"); + + // then + verify(scheduler1, never()).run(eq(root1), argThat(collection -> collection.contains("foo"))); + verify(scheduler2, times(1)).run(eq(root2), argThat(collection -> collection.contains("foo"))); + } +}