This is an automated email from the ASF dual-hosted git repository.

reschke pushed a commit to branch SLING-12894
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git


The following commit(s) were added to refs/heads/SLING-12894 by this push:
     new b1849229 SLING-12894: alias refactoring - support observation events 
while bg init not finished - wip
b1849229 is described below

commit b18492292a239a1146c19f8549f533933e5403c5
Author: Julian Reschke <[email protected]>
AuthorDate: Thu Aug 21 11:41:20 2025 +0100

    SLING-12894: alias refactoring - support observation events while bg init 
not finished - wip
---
 .../impl/mapping/AliasHandler.java                 |  11 ++-
 .../resourceresolver/impl/mapping/MapEntries.java  | 105 +++++++++++++--------
 .../impl/mapping/VanityPathHandler.java            |   2 +-
 .../impl/mapping/AliasMapEntriesTest.java          |  14 ++-
 4 files changed, 87 insertions(+), 45 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/AliasHandler.java
 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/AliasHandler.java
index de9b3676..f3d26beb 100644
--- 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/AliasHandler.java
+++ 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/AliasHandler.java
@@ -70,6 +70,7 @@ class AliasHandler {
 
     private final Runnable doUpdateConfiguration;
     private final Runnable sendChangeEvent;
+    private final Runnable drain;
 
     // static value for the case when cache is not (yet) not initialized
     private static final Map<String, Map<String, Collection<String>>> 
UNITIALIZED_MAP = Collections.emptyMap();
@@ -95,11 +96,13 @@ class AliasHandler {
             @NotNull MapConfigurationProvider factory,
             @NotNull ReentrantLock initializing,
             @NotNull Runnable doUpdateConfiguration,
-            @NotNull Runnable sendChangeEvent) {
+            @NotNull Runnable sendChangeEvent,
+            @NotNull Runnable drain) {
         this.factory = factory;
         this.initializing = initializing;
         this.doUpdateConfiguration = doUpdateConfiguration;
         this.sendChangeEvent = sendChangeEvent;
+        this.drain = drain;
 
         this.aliasResourcesOnStartup = new AtomicLong(0);
         this.detectedConflictingAliases = new AtomicLong(0);
@@ -176,8 +179,14 @@ class AliasHandler {
 
                 aliasMapsMap = loadAliases(resolver, conflictingAliases, 
invalidAliases, diagnostics);
 
+                // process pending events
+                AliasHandler.this.drain.run();
+
                 aliasesProcessed.set(true);
 
+                // drain once more in case more events have arrived
+                AliasHandler.this.drain.run();
+
                 long processElapsed = System.nanoTime() - initStart;
                 long resourcePerSecond = (aliasResourcesOnStartup.get()
                         * TimeUnit.SECONDS.toNanos(1)
diff --git 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
index 7356058c..6d6d35b9 100644
--- 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
+++ 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
@@ -102,8 +102,8 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
 
     private final Map<String, List<MapEntry>> resolveMapsMap;
 
-    private List<Map.Entry<String, ResourceChange.ChangeType>> 
resourceChangeQueueForAliases;
-    private List<Map.Entry<String, ResourceChange.ChangeType>> 
resourceChangeQueueForVanityPaths;
+    private final List<Map.Entry<String, ResourceChange.ChangeType>> 
resourceChangeQueueForAliases;
+    private final List<Map.Entry<String, ResourceChange.ChangeType>> 
resourceChangeQueueForVanityPaths;
 
     private Collection<MapEntry> mapMaps;
 
@@ -127,16 +127,18 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         this.eventAdmin = eventAdmin;
 
         this.resolveMapsMap = new ConcurrentHashMap<>(Map.of(GLOBAL_LIST_KEY, 
List.of()));
+        this.resourceChangeQueueForAliases = Collections.synchronizedList(new 
LinkedList<>());
+        this.resourceChangeQueueForVanityPaths = 
Collections.synchronizedList(new LinkedList<>());
         this.mapMaps = Collections.emptyList();
         this.stringInterpolationProvider = stringInterpolationProvider;
 
-        this.ah = new AliasHandler(this.factory, this.initializing, 
this::doUpdateConfiguration, this::sendChangeEvent);
+        this.ah = new AliasHandler(
+                this.factory, this.initializing, this::doUpdateConfiguration, 
this::sendChangeEvent, this::drainQueue);
         this.ah.initializeAliases();
 
         this.registration = registerResourceChangeListener(bundleContext);
 
-        this.vph =
-                new VanityPathHandler(this.factory, this.resolveMapsMap, 
this.initializing, this::drainVanityPathQueue);
+        this.vph = new VanityPathHandler(this.factory, this.resolveMapsMap, 
this.initializing, this::drainQueue);
         this.vph.initializeVanityPaths();
 
         if (metrics.isPresent()) {
@@ -167,8 +169,8 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
         log.info("Registering for {}", 
Arrays.toString(factory.getObservationPaths()));
 
-        this.resourceChangeQueueForAliases = Collections.synchronizedList(new 
LinkedList<>());
-        this.resourceChangeQueueForVanityPaths = 
Collections.synchronizedList(new LinkedList<>());
+        this.resourceChangeQueueForAliases.clear();
+        this.resourceChangeQueueForVanityPaths.clear();
 
         return bundleContext.registerService(ResourceChangeListener.class, 
this, props);
     }
@@ -237,17 +239,6 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         boolean vanityPathChanged = false;
         boolean aliasChanged = false;
 
-        if (forVanityPath) {
-            String actualContentPath = getActualContentPath(path);
-            String actualContentPathPrefix = actualContentPath + "/";
-
-            for (String target : vph.getVanityPathMappings().keySet()) {
-                if (target.startsWith(actualContentPathPrefix) || 
target.equals(actualContentPath)) {
-                    vanityPathChanged |= vph.removeVanityPath(target);
-                }
-            }
-        }
-
         if (forAlias) {
             String pathPrefix = path + "/";
             for (String contentPath : ah.aliasMapsMap.keySet()) {
@@ -260,6 +251,17 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
             }
         }
 
+        if (forVanityPath) {
+            String actualContentPath = getActualContentPath(path);
+            String actualContentPathPrefix = actualContentPath + "/";
+
+            for (String target : vph.getVanityPathMappings().keySet()) {
+                if (target.startsWith(actualContentPathPrefix) || 
target.equals(actualContentPath)) {
+                    vanityPathChanged |= vph.removeVanityPath(target);
+                }
+            }
+        }
+
         return vanityPathChanged || aliasChanged;
     }
 
@@ -453,8 +455,8 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
     @Override
     public void onChange(final List<ResourceChange> changes) {
 
-        final boolean ahInStartup = !ah.isReady();
-        final boolean vphInStartup = !vph.isReady();
+        boolean ahInStartup = !ah.isReady();
+        boolean vphInStartup = !vph.isReady();
 
         final AtomicBoolean resolverRefreshed = new AtomicBoolean(false);
 
@@ -476,15 +478,30 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
                 continue;
             }
 
-            // during startup: just enqueue the events
-            if (vphInStartup) {
-                if (RELEVANT_CHANGE_TYPES.contains(type)) {
+            boolean queuedForAlias = false;
+            boolean queuedForVanityPath = false;
+
+            if (RELEVANT_CHANGE_TYPES.contains(type)) {
+                // during startup: just enqueue the events
+
+                if (ahInStartup) {
+                    Map.Entry<String, ResourceChange.ChangeType> entry = new 
SimpleEntry<>(path, type);
+                    log.trace("enqueued for aliases {}", entry);
+                    resourceChangeQueueForAliases.add(entry);
+                    queuedForAlias = true;
+                }
+
+                if (vphInStartup) {
                     Map.Entry<String, ResourceChange.ChangeType> entry = new 
SimpleEntry<>(path, type);
                     log.trace("enqueued for vanity paths {}", entry);
                     resourceChangeQueueForVanityPaths.add(entry);
+                    queuedForVanityPath = true;
                 }
-            } else {
-                sendEvent |= handleResourceChange(type, path, true, true, 
resolverRefreshed, hasReloadedConfig);
+            }
+
+            if (!queuedForAlias || !queuedForVanityPath) {
+                sendEvent |= handleResourceChange(
+                        type, path, !queuedForAlias, !queuedForVanityPath, 
resolverRefreshed, hasReloadedConfig);
             }
         }
 
@@ -732,7 +749,29 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         }
     }
 
-    private void drainVanityPathQueue() {
+    // Drains the resource event queue for a specific queue
+    private boolean drainSpecificQueue(
+            boolean isAlias,
+            List<Map.Entry<String, ResourceChange.ChangeType>> queue,
+            AtomicBoolean resolverRefreshed,
+            AtomicBoolean hasReloadedConfig) {
+        boolean sendEvent = false;
+
+        while (!queue.isEmpty()) {
+            Map.Entry<String, ResourceChange.ChangeType> entry = 
queue.remove(0);
+            final ResourceChange.ChangeType type = entry.getValue();
+            final String path = entry.getKey();
+
+            log.trace("drain {} queue - type={}, path={}", isAlias ? "alias" : 
"vanity path", type, path);
+            sendEvent |= handleResourceChange(type, path, isAlias, !isAlias, 
resolverRefreshed, hasReloadedConfig);
+        }
+
+        // do we need to send an event?
+        return sendEvent;
+    }
+
+    // Drains the resource event queues both for aliases and vanity paths
+    private void drainQueue() {
         final AtomicBoolean resolverRefreshed = new AtomicBoolean(false);
 
         // send the change event only once
@@ -741,18 +780,8 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         // the config needs to be reloaded only once
         final AtomicBoolean hasReloadedConfig = new AtomicBoolean(false);
 
-        while (!resourceChangeQueueForVanityPaths.isEmpty()) {
-            Map.Entry<String, ResourceChange.ChangeType> entry = 
resourceChangeQueueForVanityPaths.remove(0);
-            final ResourceChange.ChangeType type = entry.getValue();
-            final String path = entry.getKey();
-
-            log.trace("drain vanity path queue - type={}, path={}", type, 
path);
-            boolean changed = handleResourceChange(type, path, false, true, 
resolverRefreshed, hasReloadedConfig);
-
-            if (changed) {
-                sendEvent = true;
-            }
-        }
+        sendEvent |= drainSpecificQueue(true, resourceChangeQueueForAliases, 
resolverRefreshed, hasReloadedConfig);
+        sendEvent |= drainSpecificQueue(false, 
resourceChangeQueueForVanityPaths, resolverRefreshed, hasReloadedConfig);
 
         if (sendEvent) {
             sendChangeEvent();
diff --git 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathHandler.java
 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathHandler.java
index 95f8f6ed..d9a21d65 100644
--- 
a/src/main/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathHandler.java
+++ 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathHandler.java
@@ -176,7 +176,7 @@ public class VanityPathHandler {
 
                 vanityTargets = loadVanityPaths(resolver);
 
-                // process pending event
+                // process pending events
                 VanityPathHandler.this.drain.run();
 
                 vanityPathsProcessed.set(true);
diff --git 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AliasMapEntriesTest.java
 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AliasMapEntriesTest.java
index 3090df85..36a77aaa 100644
--- 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AliasMapEntriesTest.java
+++ 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AliasMapEntriesTest.java
@@ -41,6 +41,7 @@ import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.resource.observation.ResourceChange;
 import org.apache.sling.api.resource.path.Path;
 import org.apache.sling.resourceresolver.impl.ResourceResolverImpl;
 import org.apache.sling.resourceresolver.impl.ResourceResolverMetrics;
@@ -101,7 +102,7 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
 
     private final boolean isAliasCacheInitInBackground;
 
-    @Parameterized.Parameters(name = 
"isOptimizeAliasResolutionEnabled={0},isAliasCacheInitInBackground{1}")
+    @Parameterized.Parameters(name = 
"isOptimizeAliasResolutionEnabled={0},isAliasCacheInitInBackground={1}")
     public static Collection<Object[]> data() {
         // (optimized==false && backgroundInit == false) does not need to be 
tested
         return List.of(new Object[][] {{false, false}, {true, false}, {true, 
true}});
@@ -1242,12 +1243,10 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
     }
 
     @Test
-    public void test_remove_alias_during_bg_init()
-            throws InvocationTargetException, IllegalAccessException, 
NoSuchMethodException {
+    public void test_remove_alias_during_bg_init() {
         Assume.assumeTrue(
                 "simulation of resource removal during bg init only meaningful 
in 'bg init' case",
                 resourceResolverFactory.isAliasCacheInitInBackground());
-        AliasHandler ah = mapEntries.ah;
 
         Resource root = createMockedResource("/");
         Resource top = createMockedResource(root, "top");
@@ -1260,18 +1259,23 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
                 .thenAnswer((Answer<Iterator<Resource>>) invocation -> {
                     while (!greenLight.get()) {
                         // yes, busy wait; eat this, code quality checkers
+                        Thread.sleep(10);
                     }
                     return Set.of(leaf).iterator();
                 });
 
+        AliasHandler ah = mapEntries.ah;
         ah.initializeAliases();
+        assertFalse(ah.isReady());
 
         // bg init will wait until we give green light
-        removeResource(mapEntries, leaf.getPath(), null);
+        mapEntries.onChange(List.of(new 
ResourceChange(ResourceChange.ChangeType.REMOVED, leaf.getPath(), false)));
 
         greenLight.set(true);
         waitForBgInit();
 
+        assertTrue(ah.isReady());
+
         Map<String, Collection<String>> aliasMapEntry = 
mapEntries.getAliasMap(top);
         assertTrue(
                 "Alias Map for " + top.getPath()

Reply via email to