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

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


The following commit(s) were added to refs/heads/1.x by this push:
     new 8520f3f7 SLING-12894: alias refactoring - support observation events 
while bg init not finished (#197)
8520f3f7 is described below

commit 8520f3f77d52868749b65587cb7272534fef9791
Author: Julian Reschke <[email protected]>
AuthorDate: Mon Aug 25 17:22:01 2025 +0200

    SLING-12894: alias refactoring - support observation events while bg init 
not finished (#197)
---
 .../impl/mapping/AliasHandler.java                 |  11 +-
 .../resourceresolver/impl/mapping/MapEntries.java  | 175 ++++++++++++++-------
 .../impl/mapping/VanityPathHandler.java            |   2 +-
 .../impl/mapping/AliasMapEntriesTest.java          |  98 +++++++++++-
 .../impl/mapping/MapEntriesTest.java               |  18 ++-
 .../impl/mapping/VanityPathMapEntriesTest.java     |  18 ++-
 6 files changed, 240 insertions(+), 82 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 40c8e57d..fdc8fa6b 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 ddda5586..fa9a48bd 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,7 +102,8 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
 
     private final Map<String, List<MapEntry>> resolveMapsMap;
 
-    private List<Map.Entry<String, ResourceChange.ChangeType>> 
resourceChangeQueue;
+    private final List<Map.Entry<String, ResourceChange.ChangeType>> 
resourceChangeQueueForAliases;
+    private final List<Map.Entry<String, ResourceChange.ChangeType>> 
resourceChangeQueueForVanityPaths;
 
     private Collection<MapEntry> mapMaps;
 
@@ -126,15 +127,23 @@ 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::drainAliasQueue);
         this.ah.initializeAliases();
 
         this.registration = registerResourceChangeListener(bundleContext);
 
-        this.vph = new VanityPathHandler(this.factory, this.resolveMapsMap, 
this.initializing, this::drainQueue);
+        this.vph =
+                new VanityPathHandler(this.factory, this.resolveMapsMap, 
this.initializing, this::drainVanityPathQueue);
         this.vph.initializeVanityPaths();
 
         if (metrics.isPresent()) {
@@ -165,19 +174,22 @@ 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.resourceChangeQueue = Collections.synchronizedList(new 
LinkedList<>());
+        this.resourceChangeQueueForAliases.clear();
+        this.resourceChangeQueueForVanityPaths.clear();
+
         return bundleContext.registerService(ResourceChangeListener.class, 
this, props);
     }
 
-    private boolean addResource(final String path, final AtomicBoolean 
resolverRefreshed) {
+    private boolean addResource(ChangeContext ctx, AtomicBoolean 
resolverRefreshed) {
         this.initializing.lock();
 
         try {
             this.refreshResolverIfNecessary(resolverRefreshed);
-            final Resource resource = this.resolver != null ? 
resolver.getResource(path) : null;
+
+            Resource resource = this.resolver != null ? 
resolver.getResource(ctx.path) : null;
             if (resource != null) {
-                boolean vanityPathAdded = vph.doAddVanity(resource);
-                boolean aliasAdded = ah.doAddAlias(resource);
+                boolean vanityPathAdded = ctx.forVanityPath && 
vph.doAddVanity(resource);
+                boolean aliasAdded = ctx.forAlias && ah.doAddAlias(resource);
                 return vanityPathAdded || aliasAdded;
             } else {
                 return false;
@@ -187,24 +199,24 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         }
     }
 
-    private boolean updateResource(final String path, final AtomicBoolean 
resolverRefreshed) {
+    private boolean updateResource(ChangeContext ctx, AtomicBoolean 
resolverRefreshed) {
 
         this.initializing.lock();
 
         try {
             this.refreshResolverIfNecessary(resolverRefreshed);
 
-            final Resource resource = this.resolver != null ? 
resolver.getResource(path) : null;
+            Resource resource = this.resolver != null ? 
resolver.getResource(ctx.path) : null;
 
-            final boolean isValidVanityPath = vph.isValidVanityPath(path);
+            boolean isValidVanityPath = vph.isValidVanityPath(ctx.path);
 
             if (resource != null) {
 
                 boolean vanityPathChanged = false;
 
-                if (isValidVanityPath) {
+                if (ctx.forVanityPath && isValidVanityPath) {
                     // we remove the old vanity path first
-                    vanityPathChanged |= vph.doRemoveVanity(path);
+                    vanityPathChanged |= vph.doRemoveVanity(ctx.path);
 
                     // add back vanity path
                     Resource contentRsrc = null;
@@ -215,7 +227,7 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
                     vanityPathChanged |= vph.doAddVanity(contentRsrc != null ? 
contentRsrc : resource);
                 }
 
-                boolean aliasChanged = ah.doUpdateAlias(resource);
+                boolean aliasChanged = ctx.forAlias && 
ah.doUpdateAlias(resource);
                 return vanityPathChanged || aliasChanged;
             }
         } finally {
@@ -225,24 +237,31 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         return false;
     }
 
-    private boolean removeResource(final String path, final AtomicBoolean 
resolverRefreshed) {
-        final String actualContentPath = getActualContentPath(path);
-        final String actualContentPathPrefix = actualContentPath + "/";
+    private boolean removeResource(ChangeContext ctx, AtomicBoolean 
resolverRefreshed) {
 
         boolean vanityPathChanged = false;
         boolean aliasChanged = false;
 
-        for (final String target : vph.getVanityPathMappings().keySet()) {
-            if (target.startsWith(actualContentPathPrefix) || 
target.equals(actualContentPath)) {
-                vanityPathChanged |= vph.removeVanityPath(target);
+        if (ctx.forAlias) {
+            String pathPrefix = ctx.path + "/";
+            for (String contentPath : ah.aliasMapsMap.keySet()) {
+                if (ctx.path.startsWith(contentPath + "/")
+                        || ctx.path.equals(contentPath)
+                        || contentPath.startsWith(pathPrefix)) {
+                    aliasChanged |= ah.removeAlias(
+                            resolver, contentPath, ctx.path, () -> 
this.refreshResolverIfNecessary(resolverRefreshed));
+                }
             }
         }
 
-        final String pathPrefix = path + "/";
-        for (final String contentPath : ah.aliasMapsMap.keySet()) {
-            if (path.startsWith(contentPath + "/") || path.equals(contentPath) 
|| contentPath.startsWith(pathPrefix)) {
-                aliasChanged |= ah.removeAlias(
-                        resolver, contentPath, path, () -> 
this.refreshResolverIfNecessary(resolverRefreshed));
+        if (ctx.forVanityPath) {
+            String actualContentPath = getActualContentPath(ctx.path);
+            String actualContentPathPrefix = actualContentPath + "/";
+
+            for (String target : vph.getVanityPathMappings().keySet()) {
+                if (target.startsWith(actualContentPathPrefix) || 
target.equals(actualContentPath)) {
+                    vanityPathChanged |= vph.removeVanityPath(target);
+                }
             }
         }
 
@@ -431,7 +450,7 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
             ResourceChange.ChangeType.ADDED, 
ResourceChange.ChangeType.CHANGED, ResourceChange.ChangeType.REMOVED);
 
     /**
-     * Handles the change to any of the node properties relevant for vanity 
paths
+     * Handles the change to any of the node properties relevant for vanity 
paths or aliases
      * mappings. The {@link #MapEntries(MapConfigurationProvider, 
BundleContext, EventAdmin, StringInterpolationProvider, Optional)}
      * constructor makes sure the event listener is registered to only get
      * appropriate events.
@@ -439,7 +458,8 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
     @Override
     public void onChange(final List<ResourceChange> changes) {
 
-        final boolean inStartup = !vph.isReady();
+        boolean ahInStartup = !ah.isReady();
+        boolean vphInStartup = !vph.isReady();
 
         final AtomicBoolean resolverRefreshed = new AtomicBoolean(false);
 
@@ -461,19 +481,30 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
                 continue;
             }
 
-            boolean queued = false;
+            boolean queuedForAlias = false;
+            boolean queuedForVanityPath = false;
 
             // during startup: just enqueue the events
 
-            if (inStartup && RELEVANT_CHANGE_TYPES.contains(type)) {
+            if (ahInStartup && RELEVANT_CHANGE_TYPES.contains(type)) {
                 Map.Entry<String, ResourceChange.ChangeType> entry = new 
SimpleEntry<>(path, type);
-                log.trace("enqueue: {}", entry);
-                resourceChangeQueue.add(entry);
-                queued = true;
+                log.trace("enqueued for aliases {}", entry);
+                resourceChangeQueueForAliases.add(entry);
+                queuedForAlias = true;
             }
 
-            if (!queued) {
-                sendEvent |= handleResourceChange(type, path, 
resolverRefreshed, hasReloadedConfig);
+            if (vphInStartup && RELEVANT_CHANGE_TYPES.contains(type)) {
+                Map.Entry<String, ResourceChange.ChangeType> entry = new 
SimpleEntry<>(path, type);
+                log.trace("enqueued for vanity paths {}", entry);
+                resourceChangeQueueForVanityPaths.add(entry);
+                queuedForVanityPath = true;
+            }
+
+            if (!queuedForAlias || !queuedForVanityPath) {
+                sendEvent |= handleResourceChange(
+                        new ChangeContext(type, path, !queuedForAlias, 
!queuedForVanityPath),
+                        resolverRefreshed,
+                        hasReloadedConfig);
             }
         }
 
@@ -482,40 +513,56 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         }
     }
 
+    // bundles information about contents and target of change event
+    static class ChangeContext {
+        final ResourceChange.ChangeType type;
+        final String path;
+        final boolean forAlias;
+        final boolean forVanityPath;
+
+        public ChangeContext(ResourceChange.ChangeType type, String path, 
boolean forAlias, boolean forVanityPath) {
+            this.type = type;
+            this.path = path;
+            this.forAlias = forAlias;
+            this.forVanityPath = forVanityPath;
+        }
+
+        public ChangeContext(String path, boolean forAlias, boolean 
forVanityPath) {
+            this(null, path, forAlias, forVanityPath);
+        }
+    }
+
     private boolean handleResourceChange(
-            ResourceChange.ChangeType type,
-            String path,
-            AtomicBoolean resolverRefreshed,
-            AtomicBoolean hasReloadedConfig) {
+            ChangeContext ctx, AtomicBoolean resolverRefreshed, AtomicBoolean 
hasReloadedConfig) {
         boolean changed = false;
 
         // removal of a resource is handled differently
-        if (type == ResourceChange.ChangeType.REMOVED) {
-            final Boolean result = handleConfigurationUpdate(path, 
hasReloadedConfig, resolverRefreshed, true);
+        if (ctx.type == ResourceChange.ChangeType.REMOVED) {
+            final Boolean result = handleConfigurationUpdate(ctx.path, 
hasReloadedConfig, resolverRefreshed, true);
             if (result != null) {
                 if (result) {
                     changed = true;
                 } else {
-                    changed |= removeResource(path, resolverRefreshed);
+                    changed |= removeResource(ctx, resolverRefreshed);
                 }
             }
             // session.move() is handled differently see also SLING-3713 and
-        } else if (type == ResourceChange.ChangeType.ADDED) {
-            final Boolean result = handleConfigurationUpdate(path, 
hasReloadedConfig, resolverRefreshed, false);
+        } else if (ctx.type == ResourceChange.ChangeType.ADDED) {
+            final Boolean result = handleConfigurationUpdate(ctx.path, 
hasReloadedConfig, resolverRefreshed, false);
             if (result != null) {
                 if (result) {
                     changed = true;
                 } else {
-                    changed |= addResource(path, resolverRefreshed);
+                    changed |= addResource(ctx, resolverRefreshed);
                 }
             }
-        } else if (type == ResourceChange.ChangeType.CHANGED) {
-            final Boolean result = handleConfigurationUpdate(path, 
hasReloadedConfig, resolverRefreshed, false);
+        } else if (ctx.type == ResourceChange.ChangeType.CHANGED) {
+            final Boolean result = handleConfigurationUpdate(ctx.path, 
hasReloadedConfig, resolverRefreshed, false);
             if (result != null) {
                 if (result) {
                     changed = true;
                 } else {
-                    changed |= updateResource(path, resolverRefreshed);
+                    changed |= updateResource(ctx, resolverRefreshed);
                 }
             }
         }
@@ -719,29 +766,39 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
         }
     }
 
-    private void drainQueue() {
+    // Drains the resource event queue for a specific queue
+    private boolean drainSpecificQueue(boolean isAlias, List<Map.Entry<String, 
ResourceChange.ChangeType>> queue) {
         final AtomicBoolean resolverRefreshed = new AtomicBoolean(false);
 
-        // send the change event only once
-        boolean sendEvent = false;
-
         // the config needs to be reloaded only once
         final AtomicBoolean hasReloadedConfig = new AtomicBoolean(false);
 
-        while (!resourceChangeQueue.isEmpty()) {
-            Map.Entry<String, ResourceChange.ChangeType> entry = 
resourceChangeQueue.remove(0);
+        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 type={}, path={}", type, path);
-            boolean changed = handleResourceChange(type, path, 
resolverRefreshed, hasReloadedConfig);
+            log.trace("drain {} queue - type={}, path={}", isAlias ? "alias" : 
"vanity path", type, path);
+            sendEvent |= handleResourceChange(
+                    new ChangeContext(type, path, isAlias, !isAlias), 
resolverRefreshed, hasReloadedConfig);
+        }
+
+        // do we need to send an event?
+        return sendEvent;
+    }
 
-            if (changed) {
-                sendEvent = true;
-            }
+    // Drains the resource event queue for aliases
+    private void drainAliasQueue() {
+        if (drainSpecificQueue(true, resourceChangeQueueForAliases)) {
+            sendChangeEvent();
         }
+    }
 
-        if (sendEvent) {
+    // Drains the resource event queue for vanity paths
+    private void drainVanityPathQueue() {
+        if (drainSpecificQueue(false, resourceChangeQueueForVanityPaths)) {
             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 3a90ef73..2ab3078e 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
@@ -31,6 +31,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Matcher;
@@ -41,6 +42,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;
@@ -61,7 +63,9 @@ import org.osgi.service.event.EventAdmin;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertIterableEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
@@ -101,7 +105,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}});
@@ -178,16 +182,18 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
 
     private static boolean addResource(MapEntries mapEntries, String path, 
AtomicBoolean bool)
             throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-        Method method = MapEntries.class.getDeclaredMethod("addResource", 
String.class, AtomicBoolean.class);
+        Method method =
+                MapEntries.class.getDeclaredMethod("addResource", 
MapEntries.ChangeContext.class, AtomicBoolean.class);
         method.setAccessible(true);
-        return (Boolean) method.invoke(mapEntries, path, bool);
+        return (Boolean) method.invoke(mapEntries, new 
MapEntries.ChangeContext(path, true, false), bool);
     }
 
     private static void removeResource(MapEntries mapEntries, String path, 
AtomicBoolean bool)
             throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-        Method method = MapEntries.class.getDeclaredMethod("removeResource", 
String.class, AtomicBoolean.class);
+        Method method = MapEntries.class.getDeclaredMethod(
+                "removeResource", MapEntries.ChangeContext.class, 
AtomicBoolean.class);
         method.setAccessible(true);
-        method.invoke(mapEntries, path, bool);
+        method.invoke(mapEntries, new MapEntries.ChangeContext(path, true, 
false), bool);
     }
 
     private static void removeAlias(
@@ -205,9 +211,10 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
 
     private static void updateResource(MapEntries mapEntries, String path, 
AtomicBoolean bool)
             throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-        Method method = MapEntries.class.getDeclaredMethod("updateResource", 
String.class, AtomicBoolean.class);
+        Method method = MapEntries.class.getDeclaredMethod(
+                "updateResource", MapEntries.ChangeContext.class, 
AtomicBoolean.class);
         method.setAccessible(true);
-        method.invoke(mapEntries, path, bool);
+        method.invoke(mapEntries, new MapEntries.ChangeContext(path, true, 
false), bool);
     }
 
     private void internal_test_simple_alias_support(boolean onJcrContent, 
boolean cached) {
@@ -1252,6 +1259,62 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
         assertFalse("alias handler should not have set up cache", 
ah.usesCache());
     }
 
+    @Test
+    public void test_event_alias_during_bg_init() {
+        Assume.assumeTrue(
+                "simulation of resource removal during bg init only meaningful 
in 'bg init' case",
+                resourceResolverFactory.isAliasCacheInitInBackground());
+
+        Resource root = createMockedResource("/");
+        Resource top = createMockedResource(root, "top");
+        Resource leaf1 = createMockedResource(top, "leaf1");
+        
when(leaf1.getValueMap()).thenReturn(buildValueMap(ResourceResolverImpl.PROP_ALIAS,
 "alias1"));
+
+        CountDownLatch greenLight = new CountDownLatch(1);
+
+        when(resourceResolver.findResources(anyString(), eq("JCR-SQL2")))
+                .thenAnswer((Answer<Iterator<Resource>>) invocation -> {
+                    greenLight.await();
+                    return Set.of(leaf1).iterator();
+                });
+
+        AliasHandler ah = mapEntries.ah;
+        ah.initializeAliases();
+        assertFalse(ah.isReady());
+
+        // bg init will wait until we give green light
+
+        Resource leaf2 = createMockedResource(top, "leaf2");
+        
when(leaf2.getValueMap()).thenReturn(buildValueMap(ResourceResolverImpl.PROP_ALIAS,
 "alias2"));
+
+        removeResource(leaf1);
+        mapEntries.onChange(List.of(new 
ResourceChange(ResourceChange.ChangeType.REMOVED, leaf1.getPath(), false)));
+        mapEntries.onChange(List.of(new 
ResourceChange(ResourceChange.ChangeType.ADDED, leaf2.getPath(), false)));
+
+        greenLight.countDown();
+        waitForBgInit();
+
+        assertTrue(ah.isReady());
+
+        Map<String, Collection<String>> aliasMapEntry = 
mapEntries.getAliasMap(top);
+        assertNotNull(aliasMapEntry);
+
+        Collection<String> leaf1Entry = aliasMapEntry.get(leaf1.getName());
+        assertNull(
+                "Alias Map Entry for " + top.getPath() + " should not contain 
an entry for " + leaf1.getName()
+                        + " due to removal event during background init, but 
got: "
+                        + leaf1Entry,
+                leaf1Entry);
+
+        Collection<String> leaf2Entry = aliasMapEntry.get(leaf2.getName());
+        assertNotNull(
+                "Alias Map Entry for " + top.getPath() + " should contain an 
entry for " + leaf2.getName()
+                        + " due to addition event during background init, but 
got: " + leaf2Entry,
+                leaf2Entry);
+
+        assertIterableEquals(Set.of("alias2"), leaf2Entry, "Alias Array for " 
+ leaf2.getName() + " incorrect");
+    }
+
     // utilities for testing alias queries
 
     // used for paged query of all
@@ -1327,7 +1390,6 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
     }
 
     private void attachChildResource(Resource parent, Resource child) {
-
         List<Resource> newChildren = new ArrayList<>();
         parent.getChildren().forEach(newChildren::add);
         newChildren.add(child);
@@ -1337,4 +1399,24 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
 
         when(child.getParent()).thenReturn(parent);
     }
+
+    private void detachChildResource(Resource parent, Resource child) {
+        List<Resource> oldChildren = new ArrayList<>();
+        parent.getChildren().forEach(oldChildren::add);
+        oldChildren.remove(child);
+
+        when(parent.getChildren()).thenReturn(oldChildren);
+        when(parent.getChild(child.getName())).thenReturn(null);
+
+        when(child.getParent()).thenReturn(null);
+    }
+
+    private void removeResource(Resource resource) {
+        Resource parent = resource.getParent();
+        if (parent != null) {
+            detachChildResource(parent, resource);
+        }
+        when(resource.getParent()).thenReturn(null);
+        
when(resourceResolver.getResource(resource.getPath())).thenReturn(null);
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
index d5d37ed6..9309dda9 100644
--- 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
+++ 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java
@@ -125,20 +125,22 @@ public class MapEntriesTest extends 
AbstractMappingMapEntriesTest {
     @Test
     // SLING-4847
     public void test_doNodeAdded1() throws Exception {
-        final Method addResource = 
MapEntries.class.getDeclaredMethod("addResource", String.class, 
AtomicBoolean.class);
+        final Method addResource =
+                MapEntries.class.getDeclaredMethod("addResource", 
MapEntries.ChangeContext.class, AtomicBoolean.class);
         addResource.setAccessible(true);
         final AtomicBoolean refreshed = new AtomicBoolean(false);
-        addResource.invoke(mapEntries, "/node", refreshed);
+        addResource.invoke(mapEntries, new MapEntries.ChangeContext("/node", 
true, true), refreshed);
         assertTrue(refreshed.get());
     }
 
     // tests SLING-6542
     @Test
     public void sessionConcurrency() throws Exception {
-        final Method addResource = 
MapEntries.class.getDeclaredMethod("addResource", String.class, 
AtomicBoolean.class);
+        final Method addResource =
+                MapEntries.class.getDeclaredMethod("addResource", 
MapEntries.ChangeContext.class, AtomicBoolean.class);
         addResource.setAccessible(true);
-        final Method updateResource =
-                MapEntries.class.getDeclaredMethod("updateResource", 
String.class, AtomicBoolean.class);
+        final Method updateResource = MapEntries.class.getDeclaredMethod(
+                "updateResource", MapEntries.ChangeContext.class, 
AtomicBoolean.class);
         updateResource.setAccessible(true);
         final Method handleConfigurationUpdate = 
MapEntries.class.getDeclaredMethod(
                 "handleConfigurationUpdate", String.class, 
AtomicBoolean.class, AtomicBoolean.class, boolean.class);
@@ -174,8 +176,10 @@ public class MapEntriesTest extends 
AbstractMappingMapEntriesTest {
                 try {
                     Thread.sleep(randomWait);
                     for (int i1 = 0; i1 < 3; i1++) {
-                        addResource.invoke(mapEntries, "/node", new 
AtomicBoolean());
-                        updateResource.invoke(mapEntries, "/node", new 
AtomicBoolean());
+                        addResource.invoke(
+                                mapEntries, new 
MapEntries.ChangeContext("/node", true, true), new AtomicBoolean());
+                        updateResource.invoke(
+                                mapEntries, new 
MapEntries.ChangeContext("/node", true, true), new AtomicBoolean());
                         handleConfigurationUpdate.invoke(
                                 mapEntries, "/node", new AtomicBoolean(), new 
AtomicBoolean(), false);
                     }
diff --git 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathMapEntriesTest.java
 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathMapEntriesTest.java
index d5b04a90..0c82c54a 100644
--- 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathMapEntriesTest.java
+++ 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/VanityPathMapEntriesTest.java
@@ -222,9 +222,11 @@ public class VanityPathMapEntriesTest extends 
AbstractMappingMapEntriesTest {
 
     private static void addResource(MapEntries mapEntries, String path, 
AtomicBoolean bool)
             throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-        Method method = MapEntries.class.getDeclaredMethod("addResource", 
String.class, AtomicBoolean.class);
+        Method method =
+                MapEntries.class.getDeclaredMethod("addResource", 
MapEntries.ChangeContext.class, AtomicBoolean.class);
         method.setAccessible(true);
-        method.invoke(mapEntries, path, bool);
+        MapEntries.ChangeContext ctx = new MapEntries.ChangeContext(path, 
false, true);
+        method.invoke(mapEntries, ctx, bool);
     }
 
     private static void loadVanityPaths(MapEntries mapEntries, 
ResourceResolver resourceResolver)
@@ -670,8 +672,8 @@ public class VanityPathMapEntriesTest extends 
AbstractMappingMapEntriesTest {
         Map<String, List<String>> vanityTargets = getVanityTargets(mapEntries);
         assertEquals(0, vanityTargets.size());
 
-        final Method updateResource =
-                MapEntries.class.getDeclaredMethod("updateResource", 
String.class, AtomicBoolean.class);
+        final Method updateResource = MapEntries.class.getDeclaredMethod(
+                "updateResource", MapEntries.ChangeContext.class, 
AtomicBoolean.class);
         updateResource.setAccessible(true);
 
         Resource justVanityPath = createMockedResource("/justVanityPath");
@@ -690,7 +692,8 @@ public class VanityPathMapEntriesTest extends 
AbstractMappingMapEntriesTest {
         // update vanity path
         when(justVanityPath.getValueMap())
                 .thenReturn(buildValueMap("sling:vanityPath", 
"/target/justVanityPathUpdated"));
-        updateResource.invoke(mapEntries, "/justVanityPath", new 
AtomicBoolean());
+        updateResource.invoke(
+                mapEntries, new MapEntries.ChangeContext("/justVanityPath", 
false, true), new AtomicBoolean());
 
         assertEquals(2, resolveMapsMap.size());
         assertEquals(1, vanityTargets.size());
@@ -723,7 +726,10 @@ public class VanityPathMapEntriesTest extends 
AbstractMappingMapEntriesTest {
         // update vanity path
         when(vanityPathOnJcrContent.getValueMap())
                 .thenReturn(buildValueMap("sling:vanityPath", 
"/target/vanityPathOnJcrContentUpdated"));
-        updateResource.invoke(mapEntries, 
"/vanityPathOnJcrContent/jcr:content", new AtomicBoolean());
+        updateResource.invoke(
+                mapEntries,
+                new 
MapEntries.ChangeContext("/vanityPathOnJcrContent/jcr:content", false, true),
+                new AtomicBoolean());
 
         assertEquals(3, resolveMapsMap.size());
         assertEquals(2, vanityTargets.size());

Reply via email to