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()