SLING-12402: ResourceResolver: add additional log entry when there are many conflicting or invalid aliases (also add metrics) (#125)
* SLING-12402: metrics for broken aliases (WIP)
* SLING-12402: metrics for conflicting aliases (WIP)
* SLING-12402: metrics for conflicting aliases - adjust log message (WIP)
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - whitespace fix
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - improve test coverage and fix typos in metrics names (sic)
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - slightly simplify tests
* Update src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
Co-authored-by: Jörg Hoh <joerghoh@users.noreply.github.com>
* Update src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
Co-authored-by: Jörg Hoh <joerghoh@users.noreply.github.com>
* Update src/main/java/org/apache/sling/resourceresolver/impl/mapping/MapEntries.java
Co-authored-by: Jörg Hoh <joerghoh@users.noreply.github.com>
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - restore metrics names
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - explain why we clear the 'conflicts queues'
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - tune logging
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - fix javadoc
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - transform collecting lists into method parameters
* SLING-12402: metrics for defunct aliases - add warning after init when over (minimal) threshold - remove unused code
* SLING-12402: metrics for defunct aliases - fix log message templates
* SLING-12402: metrics for defunct aliases - refactor loadAlias
* SLING-12402: metrics for defunct aliases - refactor loadAlias
* SLING-12402: metrics for defunct aliases - reduce Cognitive Complexity in refactored code once more
---------
Co-authored-by: Jörg Hoh <joerghoh@users.noreply.github.com>
diff --git a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetrics.java b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetrics.java
index c073727..4c19e48 100644
--- a/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetrics.java
+++ b/src/main/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetrics.java
@@ -36,14 +36,16 @@
* Export metrics for the resource resolver bundle:
*
* org.apache.sling.resourceresolver.numberOfResourcesWithAliasesOnStartup -- the total number of resources with sling:alias properties found on startup
- * org.apache.sling.resourceresolver.numberOfResourcesWithVanityPathsOnStartup -- the total number of resources with sling:vanityPath properties found on startup
- * org.apache.sling.resourceresolver.numberOfVanityPaths -- the total number of vanity paths in the cache
- * org.apache.sling.resourceresolver.numberOfVanityPathLookups -- the total number of vanity path lookups
- * org.apache.sling.resourceresolver.numberOfVanityPathBloomNegatives -- the total number of vanity path lookups filtered by the bloom filter
- * org.apache.sling.resourceresolver.numberOfVanityPathBloomFalsePositives -- the total number of vanity path lookup that passed the bloom filter but were false positives
- * org.apache.sling.resourceresolver.numberOfAliases -- the total number of aliases
- * org.apache.sling.resourceresolver.unclosedResourceResolvers -- the total number of unclosed resource resolvers
+ * org.apache.sling.resourceresolver.numberOfDetectedConflictingAliases -- the total number of detected invalid aliases
+ * org.apache.sling.resourceresolver.numberOfDetectedInvalidAliases -- the total number of detected invalid aliases
*
+ * org.apache.sling.resourceresolver.numberOfResourcesWithVanityPathsOnStartup -- the total number of resources with sling:vanityPath properties found on startup
+ * org.apache.sling.resourceresolver.numberOfVanityPathBloomFalsePositives -- the total number of vanity path lookup that passed the bloom filter but were false positives
+ * org.apache.sling.resourceresolver.numberOfVanityPathBloomNegatives -- the total number of vanity path lookups filtered by the bloom filter
+ * org.apache.sling.resourceresolver.numberOfVanityPathLookups -- the total number of vanity path lookups
+ * org.apache.sling.resourceresolver.numberOfVanityPaths -- the total number of vanity paths in the cache
+ *
+ * org.apache.sling.resourceresolver.unclosedResourceResolvers -- the total number of unclosed resource resolvers
*/
@@ -70,12 +72,12 @@
private Supplier<Long> numberOfVanityPathLookupsSupplier = ZERO_SUPPLIER;
// number of vanity path lookups filtered by Bloom filter
- private ServiceRegistration<Gauge<Long>> numberOfVanityPathBloomNegativeGauge;
- private Supplier<Long> numberOfVanityPathBloomNegativeSupplier = ZERO_SUPPLIER;
+ private ServiceRegistration<Gauge<Long>> numberOfVanityPathBloomNegativesGauge;
+ private Supplier<Long> numberOfVanityPathBloomNegativesSupplier = ZERO_SUPPLIER;
// number of vanity path lookups passing the Bloom filter but being false positives
- private ServiceRegistration<Gauge<Long>> numberOfVanityPathBloomFalsePositiveGauge;
- private Supplier<Long> numberOfVanityPathBloomFalsePositiveSupplier = ZERO_SUPPLIER;
+ private ServiceRegistration<Gauge<Long>> numberOfVanityPathBloomFalsePositivesGauge;
+ private Supplier<Long> numberOfVanityPathBloomFalsePositivesSupplier = ZERO_SUPPLIER;
// number of resources with aliased children
private ServiceRegistration<Gauge<Long>> numberOfResourcesWithAliasedChildrenGauge;
@@ -85,18 +87,27 @@
private ServiceRegistration<Gauge<Long>> numberOfResourcesWithAliasesOnStartupGauge;
private Supplier<Long> numberOfResourcesWithAliasesOnStartupSupplier = ZERO_SUPPLIER;
- private Counter unclosedResourceResolvers;
+ // total number of detected invalid aliases on startup
+ private ServiceRegistration<Gauge<Long>> numberOfDetectedInvalidAliasesGauge;
+ private Supplier<Long> numberOfDetectedInvalidAliasesSupplier = ZERO_SUPPLIER;
+ // total number of detected conflicting aliases on startup
+ private ServiceRegistration<Gauge<Long>> numberOfDetectedConflictingAliasesGauge;
+ private Supplier<Long> numberOfDetectedConflictingAliasesSupplier = ZERO_SUPPLIER;
+
+ private Counter unclosedResourceResolvers;
@Activate
protected void activate(BundleContext bundleContext) {
- numberOfVanityPathsGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPaths", () -> numberOfVanityPathsSupplier );
- numberOfResourcesWithVanityPathsOnStartupGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfResourcesWithVanityPathsOnStartup", () -> numberOfResourcesWithVanityPathsOnStartupSupplier );
- numberOfVanityPathLookupsGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPathLookups", () -> numberOfVanityPathLookupsSupplier );
- numberOfVanityPathBloomNegativeGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPathBloomNegatives", () -> numberOfVanityPathBloomNegativeSupplier );
- numberOfVanityPathBloomFalsePositiveGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPathBloomFalsePositives", () -> numberOfVanityPathBloomFalsePositiveSupplier );
+ numberOfVanityPathsGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPaths", () -> numberOfVanityPathsSupplier);
+ numberOfResourcesWithVanityPathsOnStartupGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfResourcesWithVanityPathsOnStartup", () -> numberOfResourcesWithVanityPathsOnStartupSupplier);
+ numberOfVanityPathLookupsGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPathLookups", () -> numberOfVanityPathLookupsSupplier);
+ numberOfVanityPathBloomNegativesGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPathBloomNegatives", () -> numberOfVanityPathBloomNegativesSupplier);
+ numberOfVanityPathBloomFalsePositivesGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfVanityPathBloomFalsePositives", () -> numberOfVanityPathBloomFalsePositivesSupplier);
numberOfResourcesWithAliasedChildrenGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfResourcesWithAliasedChildren", () -> numberOfResourcesWithAliasedChildrenSupplier);
- numberOfResourcesWithAliasesOnStartupGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfResourcesWithAliasesOnStartup", () -> numberOfResourcesWithAliasesOnStartupSupplier );
+ numberOfResourcesWithAliasesOnStartupGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfResourcesWithAliasesOnStartup", () -> numberOfResourcesWithAliasesOnStartupSupplier);
+ numberOfDetectedInvalidAliasesGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfDetectedInvalidAliases", () -> numberOfDetectedInvalidAliasesSupplier);
+ numberOfDetectedConflictingAliasesGauge = registerGauge(bundleContext, METRICS_PREFIX + ".numberOfDetectedConflictingAliases", () -> numberOfDetectedConflictingAliasesSupplier);
unclosedResourceResolvers = metricsService.counter(METRICS_PREFIX + ".unclosedResourceResolvers");
}
@@ -105,10 +116,12 @@
numberOfVanityPathsGauge.unregister();
numberOfResourcesWithVanityPathsOnStartupGauge.unregister();
numberOfVanityPathLookupsGauge.unregister();
- numberOfVanityPathBloomNegativeGauge.unregister();
- numberOfVanityPathBloomFalsePositiveGauge.unregister();
- numberOfResourcesWithAliasesOnStartupGauge.unregister();
+ numberOfVanityPathBloomNegativesGauge.unregister();
+ numberOfVanityPathBloomFalsePositivesGauge.unregister();
numberOfResourcesWithAliasedChildrenGauge.unregister();
+ numberOfResourcesWithAliasesOnStartupGauge.unregister();
+ numberOfDetectedInvalidAliasesGauge.unregister();
+ numberOfDetectedConflictingAliasesGauge.unregister();
}
/**
@@ -139,16 +152,16 @@
* Set the number of vanity path lookups in the system that were filtered
* @param supplier a supplier returning the number of vanity path lookups
*/
- public void setNumberOfVanityPathBloomNegativeSupplier(Supplier<Long> supplier) {
- numberOfVanityPathBloomNegativeSupplier = supplier;
+ public void setNumberOfVanityPathBloomNegativesSupplier(Supplier<Long> supplier) {
+ numberOfVanityPathBloomNegativesSupplier = supplier;
}
/**
* Set the number of vanity path lookups in the system that were not catched by the Bloom filter
* @param supplier a supplier returning the number of vanity path lookups
*/
- public void setNumberOfVanityPathBloomFalsePositiveSupplier(Supplier<Long> supplier) {
- numberOfVanityPathBloomFalsePositiveSupplier = supplier;
+ public void setNumberOfVanityPathBloomFalsePositivesSupplier(Supplier<Long> supplier) {
+ numberOfVanityPathBloomFalsePositivesSupplier = supplier;
}
/**
@@ -168,12 +181,28 @@
}
/**
+ * Set the supplier for the number of invalid aliases
+ * @param supplier a supplier returning the number of invalid aliases
+ */
+ public void setNumberOfDetectedInvalidAliasesSupplier(Supplier<Long> supplier) {
+ numberOfDetectedInvalidAliasesSupplier = supplier;
+ }
+
+ /**
+ * Set the supplier for the number of duplicate aliases
+ * @param supplier a supplier returning the number of conflicting aliases
+ */
+ public void setNumberOfDetectedConflictingAliasesSupplier(Supplier<Long> supplier) {
+ numberOfDetectedConflictingAliasesSupplier = supplier;
+ }
+
+ /**
* Increment the counter for the number of unresolved resource resolvers
*/
public void reportUnclosedResourceResolver() {
unclosedResourceResolvers.increment();
}
-
+
/**
* Create a gauge metrics.
*
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 44b83df..801c66c 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
@@ -146,14 +146,19 @@
private Map<String, Map<String, Collection<String>>> aliasMapsMap;
private final AtomicLong aliasResourcesOnStartup;
+ private final AtomicLong detectedConflictingAliases;
+ private final AtomicLong detectedInvalidAliases;
+
+ // keep track of some defunct aliases for diagnostics (thus size-limited)
+ private static final int MAX_REPORT_DEFUNCT_ALIASES = 50;
private final ReentrantLock initializing = new ReentrantLock();
private final AtomicLong vanityCounter;
private final AtomicLong vanityResourcesOnStartup;
private final AtomicLong vanityPathLookups;
- private final AtomicLong vanityPathBloomNegative;
- private final AtomicLong vanityPathBloomFalsePositive;
+ private final AtomicLong vanityPathBloomNegatives;
+ private final AtomicLong vanityPathBloomFalsePositives;
private byte[] vanityBloomFilter;
@@ -180,6 +185,8 @@
this.stringInterpolationProvider = stringInterpolationProvider;
this.aliasResourcesOnStartup = new AtomicLong(0);
+ this.detectedConflictingAliases = new AtomicLong(0);
+ this.detectedInvalidAliases = new AtomicLong(0);
this.useOptimizeAliasResolution = doInit();
@@ -188,8 +195,9 @@
this.vanityCounter = new AtomicLong(0);
this.vanityResourcesOnStartup = new AtomicLong(0);
this.vanityPathLookups = new AtomicLong(0);
- this.vanityPathBloomNegative = new AtomicLong(0);
- this.vanityPathBloomFalsePositive = new AtomicLong(0);
+ this.vanityPathBloomNegatives = new AtomicLong(0);
+ this.vanityPathBloomFalsePositives = new AtomicLong(0);
+
initializeVanityPaths();
this.metrics = metrics;
@@ -197,10 +205,13 @@
this.metrics.get().setNumberOfVanityPathsSupplier(vanityCounter::get);
this.metrics.get().setNumberOfResourcesWithVanityPathsOnStartupSupplier(vanityResourcesOnStartup::get);
this.metrics.get().setNumberOfVanityPathLookupsSupplier(vanityPathLookups::get);
- this.metrics.get().setNumberOfVanityPathBloomNegativeSupplier(vanityPathBloomNegative::get);
- this.metrics.get().setNumberOfVanityPathBloomFalsePositiveSupplier(vanityPathBloomFalsePositive::get);
+ this.metrics.get().setNumberOfVanityPathBloomNegativesSupplier(vanityPathBloomNegatives::get);
+ this.metrics.get().setNumberOfVanityPathBloomFalsePositivesSupplier(vanityPathBloomFalsePositives::get);
this.metrics.get().setNumberOfResourcesWithAliasedChildrenSupplier(() -> (long) aliasMapsMap.size());
this.metrics.get().setNumberOfResourcesWithAliasesOnStartupSupplier(aliasResourcesOnStartup::get);
+ this.metrics.get().setNumberOfDetectedConflictingAliasesSupplier(detectedConflictingAliases::get);
+ this.metrics.get().setNumberOfDetectedInvalidAliasesSupplier(detectedInvalidAliases::get);
+ this.metrics.get().setNumberOfResourcesWithAliasesOnStartupSupplier(detectedInvalidAliases::get);
}
}
@@ -235,14 +246,28 @@
return this.factory.isOptimizeAliasResolutionEnabled();
}
+ List<String> conflictingAliases = new ArrayList<>();
+ List<String> invalidAliases = new ArrayList<>();
+
boolean isOptimizeAliasResolutionEnabled = this.factory.isOptimizeAliasResolutionEnabled();
//optimization made in SLING-2521
if (isOptimizeAliasResolutionEnabled) {
try {
- final Map<String, Map<String, Collection<String>>> loadedMap = this.loadAliases(resolver);
+ final Map<String, Map<String, Collection<String>>> loadedMap = this.loadAliases(resolver, conflictingAliases, invalidAliases);
this.aliasMapsMap = loadedMap;
-
+
+ // warn if there are more than a few defunct aliases
+ if (conflictingAliases.size() >= MAX_REPORT_DEFUNCT_ALIASES) {
+ log.warn("There are {} conflicting aliases; excerpt: {}", conflictingAliases.size(), conflictingAliases);
+ } else if (!conflictingAliases.isEmpty()) {
+ log.warn("There are {} conflicting aliases: {}", conflictingAliases.size(), conflictingAliases);
+ }
+ if (invalidAliases.size() >= MAX_REPORT_DEFUNCT_ALIASES) {
+ log.warn("There are {} invalid aliases; excerpt: {}", invalidAliases.size(), invalidAliases);
+ } else if (!invalidAliases.isEmpty()) {
+ log.warn("There are {} invalid aliases: {}", invalidAliases.size(), invalidAliases);
+ }
} catch (final Exception e) {
logDisableAliasOptimization(e);
@@ -580,7 +605,7 @@
}
private boolean doAddAlias(final Resource resource) {
- return loadAlias(resource, this.aliasMapsMap);
+ return loadAlias(resource, this.aliasMapsMap, null, null);
}
/**
@@ -737,8 +762,8 @@
if (current >= Long.MAX_VALUE - 100000) {
// reset counters when we get close the limit
this.vanityPathLookups.set(1);
- this.vanityPathBloomNegative.set(0);
- this.vanityPathBloomFalsePositive.set(0);
+ this.vanityPathBloomNegatives.set(0);
+ this.vanityPathBloomFalsePositives.set(0);
log.info("Vanity Path metrics reset to 0");
}
@@ -748,7 +773,7 @@
if (!probablyPresent) {
// filtered by Bloom filter
- this.vanityPathBloomNegative.incrementAndGet();
+ this.vanityPathBloomNegatives.incrementAndGet();
}
}
@@ -777,7 +802,7 @@
}
if (mapEntries == null && probablyPresent) {
// Bloom filter had a false positive
- this.vanityPathBloomFalsePositive.incrementAndGet();
+ this.vanityPathBloomFalsePositives.incrementAndGet();
}
}
@@ -1156,7 +1181,9 @@
* Load aliases - Search for all nodes (except under /jcr:system) below
* configured alias locations having the sling:alias property
*/
- private Map<String, Map<String, Collection<String>>> loadAliases(final ResourceResolver resolver) {
+ private Map<String, Map<String, Collection<String>>> loadAliases(final ResourceResolver resolver,
+ List<String> conflictingAliases, List<String> invalidAliases) {
+
final Map<String, Map<String, Collection<String>>> map = new ConcurrentHashMap<>();
final String baseQueryString = generateAliasQuery();
@@ -1177,7 +1204,7 @@
long processStart = System.nanoTime();
while (it.hasNext()) {
count += 1;
- loadAlias(it.next(), map);
+ loadAlias(it.next(), map, conflictingAliases, invalidAliases);
}
long processElapsed = System.nanoTime() - processStart;
long resourcePerSecond = (count * TimeUnit.SECONDS.toNanos(1) / (processElapsed == 0 ? 1 : processElapsed));
@@ -1231,7 +1258,8 @@
/**
* Load alias given a resource
*/
- private boolean loadAlias(final Resource resource, Map<String, Map<String, Collection<String>>> map) {
+ private boolean loadAlias(final Resource resource, Map<String, Map<String, Collection<String>>> map,
+ List<String> conflictingAliases, List<String> invalidAliases) {
// resource containing the alias
final Resource containingResource = getResourceToBeAliased(resource);
@@ -1239,58 +1267,71 @@
if (containingResource == null) {
log.warn("containingResource is null for alias on {}, skipping.", resource.getPath());
return false;
- }
+ } else {
+ final Resource parent = containingResource.getParent();
- final Resource parent = containingResource.getParent();
-
- if (parent == null) {
- log.warn("{} is null for alias on {}, skipping.", containingResource == resource ? "parent" : "grandparent",
- resource.getPath());
- return false;
- }
- else {
- // resource the alias is for
- String resourceName = containingResource.getName();
-
- // parent path of that resource
- String parentPath = parent.getPath();
-
- boolean hasAlias = false;
-
- // require properties
- final ValueMap props = resource.getValueMap();
- final String[] aliasArray = props.get(ResourceResolverImpl.PROP_ALIAS, String[].class);
-
- if (aliasArray != null) {
- log.debug("Found alias, total size {}", aliasArray.length);
- // the order matters here, the first alias in the array must come first
- for (final String alias : aliasArray) {
- if (isAliasInvalid(alias)) {
- log.warn("Encountered invalid alias '{}' under parent path '{}'. Refusing to use it.", alias, parentPath);
- } else {
- Map<String, Collection<String>> parentMap = map.computeIfAbsent(parentPath, key -> new ConcurrentHashMap<>());
- Optional<String> siblingResourceNameWithDuplicateAlias = parentMap.entrySet().stream()
- .filter(entry -> !entry.getKey().equals(resourceName)) // ignore entry for the current resource
- .filter(entry -> entry.getValue().contains(alias))
- .findFirst().map(Map.Entry::getKey);
- if (siblingResourceNameWithDuplicateAlias.isPresent()) {
- log.warn(
- "Encountered duplicate alias '{}' under parent path '{}'. Refusing to replace current target {} with {}.",
- alias, parentPath, siblingResourceNameWithDuplicateAlias.get(), resourceName);
- } else {
- Collection<String> existingAliases = parentMap.computeIfAbsent(resourceName, name -> new CopyOnWriteArrayList<>());
- existingAliases.add(alias);
- hasAlias = true;
- }
- }
+ if (parent == null) {
+ log.warn("{} is null for alias on {}, skipping.", containingResource == resource ? "parent" : "grandparent",
+ resource.getPath());
+ return false;
+ } else {
+ final String[] aliasArray = resource.getValueMap().get(ResourceResolverImpl.PROP_ALIAS, String[].class);
+ if (aliasArray == null) {
+ return false;
+ } else {
+ return loadAliasFromArray(aliasArray, map, conflictingAliases, invalidAliases, containingResource.getName(),
+ parent.getPath());
}
}
-
- return hasAlias;
}
}
/**
+ * Load alias given a an alias array, return success flag.
+ */
+ private boolean loadAliasFromArray(final String[] aliasArray, Map<String, Map<String, Collection<String>>> map,
+ List<String> conflictingAliases, List<String> invalidAliases, final String resourceName, final String parentPath) {
+
+ boolean hasAlias = false;
+
+ log.debug("Found alias, total size {}", aliasArray.length);
+
+ // the order matters here, the first alias in the array must come first
+ for (final String alias : aliasArray) {
+ if (isAliasInvalid(alias)) {
+ long invalids = detectedInvalidAliases.incrementAndGet();
+ log.warn("Encountered invalid alias '{}' under parent path '{}' (total so far: {}). Refusing to use it.",
+ alias, parentPath, invalids);
+ if (invalidAliases != null && invalids < MAX_REPORT_DEFUNCT_ALIASES) {
+ invalidAliases.add((String.format("'%s'/'%s'", parentPath, alias)));
+ }
+ } else {
+ Map<String, Collection<String>> parentMap = map.computeIfAbsent(parentPath, key -> new ConcurrentHashMap<>());
+ Optional<String> siblingResourceNameWithDuplicateAlias = parentMap.entrySet().stream()
+ .filter(entry -> !entry.getKey().equals(resourceName)) // ignore entry for the current resource
+ .filter(entry -> entry.getValue().contains(alias))
+ .findFirst().map(Map.Entry::getKey);
+ if (siblingResourceNameWithDuplicateAlias.isPresent()) {
+ long conflicting = detectedConflictingAliases.incrementAndGet();
+ log.warn(
+ "Encountered duplicate alias '{}' under parent path '{}'. Refusing to replace current target '{}' with '{}' (total duplicated aliases so far: {}).",
+ alias, parentPath, siblingResourceNameWithDuplicateAlias.get(), resourceName, conflicting);
+ if (conflictingAliases != null && conflicting < MAX_REPORT_DEFUNCT_ALIASES) {
+ conflictingAliases.add((String.format("'%s': '%s'/'%s' vs '%s'/'%s'", parentPath, resourceName,
+ alias, siblingResourceNameWithDuplicateAlias.get(), alias)));
+ }
+ } else {
+ Collection<String> existingAliases = parentMap.computeIfAbsent(resourceName, name -> new CopyOnWriteArrayList<>());
+ existingAliases.add(alias);
+ hasAlias = true;
+ }
+ }
+ }
+
+ return hasAlias;
+ }
+
+ /**
* Given a resource, check whether the name is "jcr:content", in which case return the parent resource
* @param resource resource to check
* @return parent of jcr:content resource (may be null), otherwise the resource itself
diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetricsTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetricsTest.java
index 14075c6..e35ac74 100644
--- a/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetricsTest.java
+++ b/src/test/java/org/apache/sling/resourceresolver/impl/ResourceResolverMetricsTest.java
@@ -30,14 +30,14 @@
import org.mockito.Mockito;
public class ResourceResolverMetricsTest {
-
+
@Rule
public OsgiContext context = new OsgiContext();
-
+
private MetricsService metricsService;
-
+
private ResourceResolverMetrics metrics;
-
+
@Before
public void setup() {
metrics = new ResourceResolverMetrics();
@@ -45,26 +45,70 @@
context.registerService(MetricsService.class, metricsService);
context.registerInjectActivateService(metrics);
}
-
+
@Test
- public void testGauges() {
- Gauge<Long> vanityPaths = getGauge(ResourceResolverMetrics.METRICS_PREFIX + ".numberOfVanityPaths");
- Gauge<Long> aliases = getGauge(ResourceResolverMetrics.METRICS_PREFIX + ".numberOfResourcesWithAliasedChildren");
- assertThat(vanityPaths.getValue(),is(0L));
- assertThat(aliases.getValue(),is(0L));
-
- metrics.setNumberOfResourcesWithAliasedChildrenSupplier(() -> 3L);
- metrics.setNumberOfVanityPathsSupplier(() -> 2L);
- assertThat(vanityPaths.getValue(),is(2L));
- assertThat(aliases.getValue(),is(3L));
-
- }
-
- private Gauge<Long> getGauge(String name) {
- String filter = String.format("(%s=%s)", Gauge.NAME,name);
- Gauge<Long>[] result = context.getServices(Gauge.class,filter);
- assertThat(result.length,is(1));
- return result[0];
+ public void testGaugesAliases() {
+ // get gauges
+ Gauge<Long> numberOfResourcesWithAliasedChildren = getGauge("numberOfResourcesWithAliasedChildren");
+ Gauge<Long> numberOfResourcesWithAliasesOnStartup = getGauge("numberOfResourcesWithAliasesOnStartup");
+ Gauge<Long> numberOfDetectedInvalidAliasesGauge = getGauge("numberOfDetectedInvalidAliases");
+ Gauge<Long> numberOfDetectedConflictingAliases = getGauge("numberOfDetectedConflictingAliases");
+
+ // check initial Values
+ assertThat(numberOfResourcesWithAliasedChildren.getValue(), is(0L));
+ assertThat(numberOfResourcesWithAliasesOnStartup.getValue(), is(0L));
+ assertThat(numberOfDetectedInvalidAliasesGauge.getValue(), is(0L));
+ assertThat(numberOfDetectedConflictingAliases.getValue(), is(0L));
+
+ // set values
+ metrics.setNumberOfResourcesWithAliasedChildrenSupplier(() -> 8L);
+ metrics.setNumberOfResourcesWithAliasesOnStartupSupplier(() -> 9L);
+ metrics.setNumberOfDetectedInvalidAliasesSupplier(() -> 10L);
+ metrics.setNumberOfDetectedConflictingAliasesSupplier(() -> 11L);
+
+ // check values
+ assertThat(numberOfResourcesWithAliasedChildren.getValue(), is(8L));
+ assertThat(numberOfResourcesWithAliasesOnStartup.getValue(), is(9L));
+ assertThat(numberOfDetectedInvalidAliasesGauge.getValue(), is(10L));
+ assertThat(numberOfDetectedConflictingAliases.getValue(), is(11L));
}
+ @Test
+ public void testGaugesVanityPaths() {
+ // get gauges
+ Gauge<Long> numberOfVanityPaths = getGauge("numberOfVanityPaths");
+ Gauge<Long> numberOfResourcesWithVanityPathsOnStartup = getGauge("numberOfResourcesWithVanityPathsOnStartup");
+ Gauge<Long> numberOfVanityPathLookups = getGauge("numberOfVanityPathLookups");
+ Gauge<Long> numberOfVanityPathBloomNegatives = getGauge("numberOfVanityPathBloomNegatives");
+ Gauge<Long> numberOfVanityPathBloomFalsePositives = getGauge("numberOfVanityPathBloomFalsePositives");
+
+ // check initial Values
+ assertThat(numberOfVanityPaths.getValue(), is(0L));
+ assertThat(numberOfResourcesWithVanityPathsOnStartup.getValue(), is(0L));
+ assertThat(numberOfVanityPathLookups.getValue(), is(0L));
+ assertThat(numberOfVanityPathBloomNegatives.getValue(), is(0L));
+ assertThat(numberOfVanityPathBloomFalsePositives.getValue(), is(0L));
+
+ // set values
+ metrics.setNumberOfVanityPathsSupplier(() -> 3L);
+ metrics.setNumberOfResourcesWithVanityPathsOnStartupSupplier(() -> 4L);
+ metrics.setNumberOfVanityPathLookupsSupplier(() -> 5L);
+ metrics.setNumberOfVanityPathBloomNegativesSupplier(() -> 6L);
+ metrics.setNumberOfVanityPathBloomFalsePositivesSupplier(() -> 7L);
+
+ // check values
+ assertThat(numberOfVanityPaths.getValue(), is(3L));
+ assertThat(numberOfResourcesWithVanityPathsOnStartup.getValue(), is(4L));
+ assertThat(numberOfVanityPathLookups.getValue(), is(5L));
+ assertThat(numberOfVanityPathBloomNegatives.getValue(), is(6L));
+ assertThat(numberOfVanityPathBloomFalsePositives.getValue(), is(7L));
+ }
+
+ private Gauge<Long> getGauge(String name) {
+ String filter = String.format("(%s=%s)", Gauge.NAME, ResourceResolverMetrics.METRICS_PREFIX + "." + name);
+ @SuppressWarnings("unchecked")
+ Gauge<Long>[] result = context.getServices(Gauge.class, filter);
+ assertThat(result.length, is(1));
+ return result[0];
+ }
}
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 4402741..75b5e50 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
@@ -100,6 +100,9 @@
private EventAdmin eventAdmin;
private Map<String, Map<String, String>> aliasMap;
+ private AtomicLong detectedInvalidAliases;
+ private AtomicLong detectedConflictingAliases;
+
private int testSize = 5;
private int pageSize;
@@ -162,10 +165,18 @@
Optional<ResourceResolverMetrics> metrics = Optional.empty();
mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider, metrics);
+
final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMapsMap");
aliasMapField.setAccessible(true);
+ this.aliasMap = (Map<String, Map<String, String>>) aliasMapField.get(mapEntries);
- this.aliasMap = ( Map<String, Map<String, String>>) aliasMapField.get(mapEntries);
+ final Field detectedInvalidAliasesField = MapEntries.class.getDeclaredField("detectedInvalidAliases");
+ detectedInvalidAliasesField.setAccessible(true);
+ this.detectedInvalidAliases = (AtomicLong) detectedInvalidAliasesField.get(mapEntries);
+
+ final Field detectedConflictingAliasesField = MapEntries.class.getDeclaredField("detectedConflictingAliases");
+ detectedConflictingAliasesField.setAccessible(true);
+ this.detectedConflictingAliases = (AtomicLong) detectedConflictingAliasesField.get(mapEntries);
}
@Override
@@ -232,16 +243,19 @@
assertNotNull(aliasMap);
assertTrue(aliasMap.containsKey("child"));
assertEquals(List.of("foo", "bar", "qux", " "), aliasMap.get("child"));
+ assertEquals(3, detectedInvalidAliases.get());
}
@Test
public void test_alias_support_invalid() {
- for (String invalidAlias : List.of(".", "..", "foo/bar", "# foo", "")) {
+ List<String> invalidAliases = List.of(".", "..", "foo/bar", "# foo", "");
+ for (String invalidAlias : invalidAliases) {
prepareMapEntriesForAlias(false, false, invalidAlias);
mapEntries.doInit();
Map<String, Collection<String>> aliasMap = mapEntries.getAliasMap("/parent");
assertEquals(Collections.emptyMap(), aliasMap);
}
+ assertEquals(invalidAliases.size(), detectedInvalidAliases.get());
}
private void prepareMapEntriesForAlias(boolean onJcrContent, boolean withNullParent, String... alias) {
@@ -310,6 +324,7 @@
assertNotNull(aliasMap);
assertTrue(aliasMap.containsKey("child"));
assertEquals(Collections.singletonList("alias"), aliasMap.get("child"));
+ assertEquals(1, detectedConflictingAliases.get());
}
@Test