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

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

commit c187c3193d3b7d343b2c0cfd95f4290818c08866
Author: Julian Reschke <[email protected]>
AuthorDate: Wed Apr 2 07:27:53 2025 +0100

    SLING-12734: Move AliasHandler into new class
---
 .../impl/mapping/AliasHandler.java                 | 529 +++++++++++++++++++++
 .../resourceresolver/impl/mapping/MapEntries.java  | 495 -------------------
 .../mapping/AbstractMappingMapEntriesTest.java     |   2 +-
 .../impl/mapping/AliasMapEntriesTest.java          |  12 +-
 .../impl/mapping/MapEntriesTest.java               |   2 +-
 5 files changed, 536 insertions(+), 504 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
new file mode 100755
index 00000000..4982fcd4
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/resourceresolver/impl/mapping/AliasHandler.java
@@ -0,0 +1,529 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.resourceresolver.impl.mapping;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.QuerySyntaxException;
+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.resourceresolver.impl.ResourceResolverImpl;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * All things related to the handling of aliases.
+ */
+class AliasHandler {
+
+    private static final String JCR_CONTENT = "jcr:content";
+
+    private static final String JCR_CONTENT_PREFIX = JCR_CONTENT + "/";
+
+    private static final String JCR_CONTENT_SUFFIX = "/" + JCR_CONTENT;
+
+    private MapConfigurationProvider factory;
+
+    private final ReentrantLock initializing;
+
+    private final Logger log = LoggerFactory.getLogger(AliasHandler.class);
+
+    // keep track of some defunct aliases for diagnostics (thus size-limited)
+    private static final int MAX_REPORT_DEFUNCT_ALIASES = 50;
+
+    private Runnable doUpdateConfiguration;
+    private Runnable sendChangeEvent;
+
+    /**
+     * The key of the map is the parent path, while the value is a map with 
the the resource name as key and the actual aliases as values)
+     */
+    Map<String, Map<String, Collection<String>>> aliasMapsMap;
+
+    final AtomicLong aliasResourcesOnStartup;
+    final AtomicLong detectedConflictingAliases;
+    final AtomicLong detectedInvalidAliases;
+
+    public AliasHandler(
+            MapConfigurationProvider factory,
+            ReentrantLock initializing,
+            Runnable doUpdateConfiguration,
+            Runnable sendChangeEvent) {
+        this.factory = factory;
+        this.initializing = initializing;
+        this.aliasMapsMap = new ConcurrentHashMap<>();
+        this.doUpdateConfiguration = doUpdateConfiguration;
+        this.sendChangeEvent = sendChangeEvent;
+
+        this.aliasResourcesOnStartup = new AtomicLong(0);
+        this.detectedConflictingAliases = new AtomicLong(0);
+        this.detectedInvalidAliases = new AtomicLong(0);
+    }
+
+    public void dispose() {
+        this.factory = null;
+    }
+
+    /**
+     * Actual initializer. Guards itself against concurrent use by using a
+     * ReentrantLock. Does nothing if the resource resolver has already been
+     * null-ed.
+     *
+     * @return true if the optimizedAliasResolution is enabled, false otherwise
+     */
+    protected boolean initializeAliases() {
+
+        this.initializing.lock();
+        try {
+            // already disposed?
+            if (this.factory == null) {
+                return false;
+            }
+
+            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(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);
+
+                    // disable optimize alias resolution
+                    isOptimizeAliasResolutionEnabled = false;
+                }
+            }
+
+            doUpdateConfiguration.run();
+            sendChangeEvent.run();
+
+            return isOptimizeAliasResolutionEnabled;
+
+        } finally {
+
+            this.initializing.unlock();
+        }
+    }
+
+    boolean doAddAlias(final Resource resource) {
+        return loadAlias(resource, this.aliasMapsMap, null, null);
+    }
+
+    /**
+     * Remove all aliases for the content path
+     *
+     * @param contentPath The content path
+     * @param path        Optional sub path of the vanity path
+     * @return {@code true} if a change happened
+     */
+    boolean removeAlias(
+            ResourceResolver resolver, final String contentPath, final String 
path, final Runnable notifyOfChange) {
+        // if path is specified we first need to find out if it is
+        // a direct child of vanity path but not jcr:content, or a jcr:content 
child of a direct child
+        // otherwise we can discard the event
+        boolean handle = true;
+        final String resourcePath;
+        if (path != null && path.length() > contentPath.length()) {
+            final String subPath = path.substring(contentPath.length() + 1);
+            final int firstSlash = subPath.indexOf('/');
+            if (firstSlash == -1) {
+                if (subPath.equals(JCR_CONTENT)) {
+                    handle = false;
+                }
+                resourcePath = path;
+            } else if (subPath.lastIndexOf('/') == firstSlash) {
+                if (subPath.startsWith(JCR_CONTENT_PREFIX) || 
!subPath.endsWith(JCR_CONTENT_SUFFIX)) {
+                    handle = false;
+                }
+                resourcePath = ResourceUtil.getParent(path);
+            } else {
+                handle = false;
+                resourcePath = null;
+            }
+        } else {
+            resourcePath = contentPath;
+        }
+        if (!handle) {
+            return false;
+        }
+
+        this.initializing.lock();
+        try {
+            final Map<String, Collection<String>> aliasMapEntry = 
aliasMapsMap.get(contentPath);
+            if (aliasMapEntry != null) {
+                notifyOfChange.run();
+
+                String prefix = contentPath.endsWith("/") ? contentPath : 
contentPath + "/";
+                if (aliasMapEntry.entrySet().removeIf(e -> (prefix + 
e.getKey()).startsWith(resourcePath))
+                        && (aliasMapEntry.isEmpty())) {
+                    this.aliasMapsMap.remove(contentPath);
+                }
+
+                Resource containingResource = resolver != null ? 
resolver.getResource(resourcePath) : null;
+
+                if (containingResource != null) {
+                    if 
(containingResource.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) 
{
+                        doAddAlias(containingResource);
+                    }
+                    final Resource child = 
containingResource.getChild(JCR_CONTENT);
+                    if (child != null && 
child.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) {
+                        doAddAlias(child);
+                    }
+                }
+            }
+            return aliasMapEntry != null;
+        } finally {
+            this.initializing.unlock();
+        }
+    }
+
+    /**
+     * Update alias from a resource
+     *
+     * @param resource The resource
+     * @return {@code true} if any change
+     */
+    boolean doUpdateAlias(final Resource resource) {
+
+        // resource containing the alias
+        final Resource containingResource = getResourceToBeAliased(resource);
+
+        if (containingResource != null) {
+            final String containingResourceName = containingResource.getName();
+            final String parentPath = 
ResourceUtil.getParent(containingResource.getPath());
+
+            final Map<String, Collection<String>> aliasMapEntry =
+                    parentPath == null ? null : aliasMapsMap.get(parentPath);
+            if (aliasMapEntry != null) {
+                aliasMapEntry.remove(containingResourceName);
+                if (aliasMapEntry.isEmpty()) {
+                    this.aliasMapsMap.remove(parentPath);
+                }
+            }
+
+            boolean changed = aliasMapEntry != null;
+
+            if 
(containingResource.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) 
{
+                changed |= doAddAlias(containingResource);
+            }
+            final Resource child = containingResource.getChild(JCR_CONTENT);
+            if (child != null && 
child.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) {
+                changed |= doAddAlias(child);
+            }
+
+            return changed;
+        } else {
+            log.warn("containingResource is null for alias on {}, skipping.", 
resource.getPath());
+        }
+
+        return false;
+    }
+
+    public @NotNull Map<String, Collection<String>> getAliasMap(final String 
parentPath) {
+        Map<String, Collection<String>> aliasMapForParent = 
aliasMapsMap.get(parentPath);
+        return aliasMapForParent != null ? aliasMapForParent : 
Collections.emptyMap();
+    }
+
+    /**
+     * 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(
+            List<String> conflictingAliases, List<String> invalidAliases) 
throws LoginException {
+
+        final ResourceResolver resolver =
+                
factory.getServiceResourceResolver(factory.getServiceUserAuthenticationInfo("mapping"));
+
+        final Map<String, Map<String, Collection<String>>> map = new 
ConcurrentHashMap<>();
+        final String baseQueryString = generateAliasQuery();
+
+        Iterator<Resource> it;
+        try {
+            final String queryStringWithSort =
+                    baseQueryString + " AND FIRST([sling:alias]) >= '%s' ORDER 
BY FIRST([sling:alias])";
+            it = new PagedQueryIterator("alias", "sling:alias", resolver, 
queryStringWithSort, 2000);
+        } catch (QuerySyntaxException ex) {
+            log.debug("sort with first() not supported, falling back to base 
query", ex);
+            it = queryUnpaged(baseQueryString, resolver);
+        } catch (UnsupportedOperationException ex) {
+            log.debug("query failed as unsupported, retrying without 
paging/sorting", ex);
+            it = queryUnpaged(baseQueryString, resolver);
+        }
+
+        log.debug("alias initialization - start");
+        long count = 0;
+        long processStart = System.nanoTime();
+        while (it.hasNext()) {
+            count += 1;
+            loadAlias(it.next(), map, conflictingAliases, invalidAliases);
+        }
+        long processElapsed = System.nanoTime() - processStart;
+        long resourcePerSecond = (count * TimeUnit.SECONDS.toNanos(1) / 
(processElapsed == 0 ? 1 : processElapsed));
+
+        String diagnostics = "";
+        if (it instanceof PagedQueryIterator) {
+            PagedQueryIterator pit = (PagedQueryIterator) it;
+
+            if (!pit.getWarning().isEmpty()) {
+                log.warn(pit.getWarning());
+            }
+
+            diagnostics = pit.getStatistics();
+        }
+
+        log.info(
+                "alias initialization - completed, processed {} resources with 
sling:alias properties in {}ms (~{} resource/s){}",
+                count,
+                TimeUnit.NANOSECONDS.toMillis(processElapsed),
+                resourcePerSecond,
+                diagnostics);
+
+        this.aliasResourcesOnStartup.set(count);
+
+        return map;
+    }
+
+    /*
+     * generate alias query based on configured alias locations
+     */
+    private String generateAliasQuery() {
+        final Set<String> allowedLocations = 
this.factory.getAllowedAliasLocations();
+
+        StringBuilder baseQuery = new StringBuilder("SELECT [sling:alias] FROM 
[nt:base] WHERE");
+
+        if (allowedLocations.isEmpty()) {
+            baseQuery.append(" ").append(QueryBuildHelper.excludeSystemPath());
+        } else {
+            Iterator<String> pathIterator = allowedLocations.iterator();
+            baseQuery.append(" (");
+            String sep = "";
+            while (pathIterator.hasNext()) {
+                String prefix = pathIterator.next();
+                baseQuery
+                        .append(sep)
+                        .append("isdescendantnode('")
+                        .append(QueryBuildHelper.escapeString(prefix))
+                        .append("')");
+                sep = " OR ";
+            }
+            baseQuery.append(")");
+        }
+
+        baseQuery.append(" AND [sling:alias] IS NOT NULL");
+        return baseQuery.toString();
+    }
+
+    /**
+     * Load alias given a resource
+     */
+    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);
+
+        if (containingResource == null) {
+            log.warn("containingResource is null for alias on {}, skipping.", 
resource.getPath());
+            return false;
+        } else {
+            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 {
+                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());
+                }
+            }
+        }
+    }
+
+    /**
+     * 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
+     */
+    @Nullable
+    private Resource getResourceToBeAliased(Resource resource) {
+        if (JCR_CONTENT.equals(resource.getName())) {
+            return resource.getParent();
+        } else {
+            return resource;
+        }
+    }
+
+    /**
+     * Check alias syntax
+     */
+    private boolean isAliasInvalid(String alias) {
+        boolean invalid = alias.equals("..") || alias.equals(".") || 
alias.isEmpty();
+        if (!invalid) {
+            for (final char c : alias.toCharArray()) {
+                // invalid if / or # or a ?
+                if (c == '/' || c == '#' || c == '?') {
+                    invalid = true;
+                    break;
+                }
+            }
+        }
+        return invalid;
+    }
+
+    private Iterator<Resource> queryUnpaged(String query, ResourceResolver 
resolver) {
+        log.debug("start alias query: {}", query);
+        long queryStart = System.nanoTime();
+        final Iterator<Resource> it = resolver.findResources(query, 
"JCR-SQL2");
+        long queryElapsed = System.nanoTime() - queryStart;
+        log.debug("end alias query; elapsed {}ms", 
TimeUnit.NANOSECONDS.toMillis(queryElapsed));
+        return it;
+    }
+
+    private final AtomicLong lastTimeLogged = new AtomicLong(-1);
+
+    private final long LOGGING_ERROR_PERIOD = 1000 * 60 * 5;
+
+    void logDisableAliasOptimization(final Exception e) {
+        if (e != null) {
+            log.error(
+                    "Unexpected problem during initialization of optimize 
alias resolution. Therefore disabling optimize alias resolution. Please fix the 
problem.",
+                    e);
+        } else {
+            final long now = System.currentTimeMillis();
+            if (now - lastTimeLogged.getAndSet(now) > LOGGING_ERROR_PERIOD) {
+                log.error(
+                        "A problem occured during initialization of optimize 
alias resolution. Optimize alias resolution is disabled. Check the logs for the 
reported problem.",
+                        e);
+            }
+        }
+    }
+}
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 9397f487..48950b3a 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
@@ -35,20 +35,16 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
-import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.sling.api.SlingConstants;
 import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.QuerySyntaxException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
@@ -59,7 +55,6 @@ import 
org.apache.sling.api.resource.observation.ResourceChangeListener;
 import org.apache.sling.resourceresolver.impl.ResourceResolverImpl;
 import org.apache.sling.resourceresolver.impl.ResourceResolverMetrics;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
@@ -763,494 +758,4 @@ public class MapEntries implements MapEntriesHandler, 
ResourceChangeListener, Ex
             sendChangeEvent();
         }
     }
-
-    // Alias handling code
-
-    class AliasHandler {
-
-        private static final String JCR_CONTENT = "jcr:content";
-
-        private static final String JCR_CONTENT_PREFIX = JCR_CONTENT + "/";
-
-        private static final String JCR_CONTENT_SUFFIX = "/" + JCR_CONTENT;
-
-        private MapConfigurationProvider factory;
-
-        private final ReentrantLock initializing;
-
-        private final Logger log = LoggerFactory.getLogger(AliasHandler.class);
-
-        // keep track of some defunct aliases for diagnostics (thus 
size-limited)
-        private static final int MAX_REPORT_DEFUNCT_ALIASES = 50;
-
-        private Runnable doUpdateConfiguration;
-        private Runnable sendChangeEvent;
-
-        /**
-         * The key of the map is the parent path, while the value is a map 
with the the resource name as key and the actual aliases as values)
-         */
-        private Map<String, Map<String, Collection<String>>> aliasMapsMap;
-
-        final AtomicLong aliasResourcesOnStartup;
-        final AtomicLong detectedConflictingAliases;
-        final AtomicLong detectedInvalidAliases;
-
-        public AliasHandler(
-                MapConfigurationProvider factory,
-                ReentrantLock initializing,
-                Runnable doUpdateConfiguration,
-                Runnable sendChangeEvent) {
-            this.factory = factory;
-            this.initializing = initializing;
-            this.aliasMapsMap = new ConcurrentHashMap<>();
-            this.doUpdateConfiguration = doUpdateConfiguration;
-            this.sendChangeEvent = sendChangeEvent;
-
-            this.aliasResourcesOnStartup = new AtomicLong(0);
-            this.detectedConflictingAliases = new AtomicLong(0);
-            this.detectedInvalidAliases = new AtomicLong(0);
-        }
-
-        public void dispose() {
-            this.factory = null;
-        }
-
-        /**
-         * Actual initializer. Guards itself against concurrent use by using a
-         * ReentrantLock. Does nothing if the resource resolver has already 
been
-         * null-ed.
-         * @return true if the optimizedAliasResolution is enabled, false 
otherwise
-         */
-        protected boolean initializeAliases() {
-
-            this.initializing.lock();
-            try {
-                // already disposed?
-                if (this.factory == null) {
-                    return false;
-                }
-
-                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(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);
-
-                        // disable optimize alias resolution
-                        isOptimizeAliasResolutionEnabled = false;
-                    }
-                }
-
-                doUpdateConfiguration.run();
-                sendChangeEvent.run();
-
-                return isOptimizeAliasResolutionEnabled;
-
-            } finally {
-
-                this.initializing.unlock();
-            }
-        }
-
-        private boolean doAddAlias(final Resource resource) {
-            return loadAlias(resource, this.aliasMapsMap, null, null);
-        }
-
-        /**
-         * Remove all aliases for the content path
-         * @param contentPath The content path
-         * @param path Optional sub path of the vanity path
-         * @return {@code true} if a change happened
-         */
-        private boolean removeAlias(
-                ResourceResolver resolver, final String contentPath, final 
String path, final Runnable notifyOfChange) {
-            // if path is specified we first need to find out if it is
-            // a direct child of vanity path but not jcr:content, or a 
jcr:content child of a direct child
-            // otherwise we can discard the event
-            boolean handle = true;
-            final String resourcePath;
-            if (path != null && path.length() > contentPath.length()) {
-                final String subPath = path.substring(contentPath.length() + 
1);
-                final int firstSlash = subPath.indexOf('/');
-                if (firstSlash == -1) {
-                    if (subPath.equals(JCR_CONTENT)) {
-                        handle = false;
-                    }
-                    resourcePath = path;
-                } else if (subPath.lastIndexOf('/') == firstSlash) {
-                    if (subPath.startsWith(JCR_CONTENT_PREFIX) || 
!subPath.endsWith(JCR_CONTENT_SUFFIX)) {
-                        handle = false;
-                    }
-                    resourcePath = ResourceUtil.getParent(path);
-                } else {
-                    handle = false;
-                    resourcePath = null;
-                }
-            } else {
-                resourcePath = contentPath;
-            }
-            if (!handle) {
-                return false;
-            }
-
-            this.initializing.lock();
-            try {
-                final Map<String, Collection<String>> aliasMapEntry = 
aliasMapsMap.get(contentPath);
-                if (aliasMapEntry != null) {
-                    notifyOfChange.run();
-
-                    String prefix = contentPath.endsWith("/") ? contentPath : 
contentPath + "/";
-                    if (aliasMapEntry.entrySet().removeIf(e -> (prefix + 
e.getKey()).startsWith(resourcePath))
-                            && (aliasMapEntry.isEmpty())) {
-                        this.aliasMapsMap.remove(contentPath);
-                    }
-
-                    Resource containingResource = resolver != null ? 
resolver.getResource(resourcePath) : null;
-
-                    if (containingResource != null) {
-                        if 
(containingResource.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) 
{
-                            doAddAlias(containingResource);
-                        }
-                        final Resource child = 
containingResource.getChild(JCR_CONTENT);
-                        if (child != null && 
child.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) {
-                            doAddAlias(child);
-                        }
-                    }
-                }
-                return aliasMapEntry != null;
-            } finally {
-                this.initializing.unlock();
-            }
-        }
-
-        /**
-         * Update alias from a resource
-         * @param resource The resource
-         * @return {@code true} if any change
-         */
-        private boolean doUpdateAlias(final Resource resource) {
-
-            // resource containing the alias
-            final Resource containingResource = 
getResourceToBeAliased(resource);
-
-            if (containingResource != null) {
-                final String containingResourceName = 
containingResource.getName();
-                final String parentPath = 
ResourceUtil.getParent(containingResource.getPath());
-
-                final Map<String, Collection<String>> aliasMapEntry =
-                        parentPath == null ? null : 
aliasMapsMap.get(parentPath);
-                if (aliasMapEntry != null) {
-                    aliasMapEntry.remove(containingResourceName);
-                    if (aliasMapEntry.isEmpty()) {
-                        this.aliasMapsMap.remove(parentPath);
-                    }
-                }
-
-                boolean changed = aliasMapEntry != null;
-
-                if 
(containingResource.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) 
{
-                    changed |= doAddAlias(containingResource);
-                }
-                final Resource child = 
containingResource.getChild(JCR_CONTENT);
-                if (child != null && 
child.getValueMap().containsKey(ResourceResolverImpl.PROP_ALIAS)) {
-                    changed |= doAddAlias(child);
-                }
-
-                return changed;
-            } else {
-                log.warn("containingResource is null for alias on {}, 
skipping.", resource.getPath());
-            }
-
-            return false;
-        }
-
-        public @NotNull Map<String, Collection<String>> getAliasMap(final 
String parentPath) {
-            Map<String, Collection<String>> aliasMapForParent = 
aliasMapsMap.get(parentPath);
-            return aliasMapForParent != null ? aliasMapForParent : 
Collections.emptyMap();
-        }
-
-        /**
-         * 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(
-                List<String> conflictingAliases, List<String> invalidAliases) 
throws LoginException {
-
-            final ResourceResolver resolver =
-                    
factory.getServiceResourceResolver(factory.getServiceUserAuthenticationInfo("mapping"));
-
-            final Map<String, Map<String, Collection<String>>> map = new 
ConcurrentHashMap<>();
-            final String baseQueryString = generateAliasQuery();
-
-            Iterator<Resource> it;
-            try {
-                final String queryStringWithSort =
-                        baseQueryString + " AND FIRST([sling:alias]) >= '%s' 
ORDER BY FIRST([sling:alias])";
-                it = new PagedQueryIterator("alias", "sling:alias", resolver, 
queryStringWithSort, 2000);
-            } catch (QuerySyntaxException ex) {
-                log.debug("sort with first() not supported, falling back to 
base query", ex);
-                it = queryUnpaged("alias", baseQueryString);
-            } catch (UnsupportedOperationException ex) {
-                log.debug("query failed as unsupported, retrying without 
paging/sorting", ex);
-                it = queryUnpaged("alias", baseQueryString);
-            }
-
-            log.debug("alias initialization - start");
-            long count = 0;
-            long processStart = System.nanoTime();
-            while (it.hasNext()) {
-                count += 1;
-                loadAlias(it.next(), map, conflictingAliases, invalidAliases);
-            }
-            long processElapsed = System.nanoTime() - processStart;
-            long resourcePerSecond = (count * TimeUnit.SECONDS.toNanos(1) / 
(processElapsed == 0 ? 1 : processElapsed));
-
-            String diagnostics = "";
-            if (it instanceof PagedQueryIterator) {
-                PagedQueryIterator pit = (PagedQueryIterator) it;
-
-                if (!pit.getWarning().isEmpty()) {
-                    log.warn(pit.getWarning());
-                }
-
-                diagnostics = pit.getStatistics();
-            }
-
-            log.info(
-                    "alias initialization - completed, processed {} resources 
with sling:alias properties in {}ms (~{} resource/s){}",
-                    count,
-                    TimeUnit.NANOSECONDS.toMillis(processElapsed),
-                    resourcePerSecond,
-                    diagnostics);
-
-            this.aliasResourcesOnStartup.set(count);
-
-            return map;
-        }
-
-        /*
-         * generate alias query based on configured alias locations
-         */
-        private String generateAliasQuery() {
-            final Set<String> allowedLocations = 
this.factory.getAllowedAliasLocations();
-
-            StringBuilder baseQuery = new StringBuilder("SELECT [sling:alias] 
FROM [nt:base] WHERE");
-
-            if (allowedLocations.isEmpty()) {
-                baseQuery.append(" 
").append(QueryBuildHelper.excludeSystemPath());
-            } else {
-                Iterator<String> pathIterator = allowedLocations.iterator();
-                baseQuery.append(" (");
-                String sep = "";
-                while (pathIterator.hasNext()) {
-                    String prefix = pathIterator.next();
-                    baseQuery
-                            .append(sep)
-                            .append("isdescendantnode('")
-                            .append(QueryBuildHelper.escapeString(prefix))
-                            .append("')");
-                    sep = " OR ";
-                }
-                baseQuery.append(")");
-            }
-
-            baseQuery.append(" AND [sling:alias] IS NOT NULL");
-            return baseQuery.toString();
-        }
-
-        /**
-         * Load alias given a resource
-         */
-        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);
-
-            if (containingResource == null) {
-                log.warn("containingResource is null for alias on {}, 
skipping.", resource.getPath());
-                return false;
-            } else {
-                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 {
-                    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());
-                    }
-                }
-            }
-        }
-
-        /**
-         * 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
-         */
-        @Nullable
-        private Resource getResourceToBeAliased(Resource resource) {
-            if (JCR_CONTENT.equals(resource.getName())) {
-                return resource.getParent();
-            } else {
-                return resource;
-            }
-        }
-
-        /**
-         * Check alias syntax
-         */
-        private boolean isAliasInvalid(String alias) {
-            boolean invalid = alias.equals("..") || alias.equals(".") || 
alias.isEmpty();
-            if (!invalid) {
-                for (final char c : alias.toCharArray()) {
-                    // invalid if / or # or a ?
-                    if (c == '/' || c == '#' || c == '?') {
-                        invalid = true;
-                        break;
-                    }
-                }
-            }
-            return invalid;
-        }
-
-        private Iterator<Resource> queryUnpaged(String subject, String query) {
-            log.debug("start {} query: {}", subject, query);
-            long queryStart = System.nanoTime();
-            final Iterator<Resource> it = resolver.findResources(query, 
"JCR-SQL2");
-            long queryElapsed = System.nanoTime() - queryStart;
-            log.debug("end {} query; elapsed {}ms", subject, 
TimeUnit.NANOSECONDS.toMillis(queryElapsed));
-            return it;
-        }
-
-        private final AtomicLong lastTimeLogged = new AtomicLong(-1);
-
-        private final long LOGGING_ERROR_PERIOD = 1000 * 60 * 5;
-
-        private void logDisableAliasOptimization(final Exception e) {
-            if (e != null) {
-                log.error(
-                        "Unexpected problem during initialization of optimize 
alias resolution. Therefore disabling optimize alias resolution. Please fix the 
problem.",
-                        e);
-            } else {
-                final long now = System.currentTimeMillis();
-                if (now - lastTimeLogged.getAndSet(now) > 
LOGGING_ERROR_PERIOD) {
-                    log.error(
-                            "A problem occured during initialization of 
optimize alias resolution. Optimize alias resolution is disabled. Check the 
logs for the reported problem.",
-                            e);
-                }
-            }
-        }
-    }
 }
diff --git 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
index 9472e0c3..35d0dd7d 100644
--- 
a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
+++ 
b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java
@@ -123,7 +123,7 @@ public abstract class AbstractMappingMapEntriesTest {
         mapEntries = new MapEntries(
                 resourceResolverFactory, bundleContext, eventAdmin, 
stringInterpolationProvider, metrics);
 
-        final Field aliasMapField = 
MapEntries.AliasHandler.class.getDeclaredField("aliasMapsMap");
+        final Field aliasMapField = 
AliasHandler.class.getDeclaredField("aliasMapsMap");
         aliasMapField.setAccessible(true);
         this.aliasMap = (Map<String, Map<String, String>>) 
aliasMapField.get(mapEntries.ah);
     }
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 bba9c33e..ed410fde 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
@@ -116,17 +116,15 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
         mapEntries = new MapEntries(
                 resourceResolverFactory, bundleContext, eventAdmin, 
stringInterpolationProvider, metrics);
 
-        final Field aliasMapField = 
MapEntries.AliasHandler.class.getDeclaredField("aliasMapsMap");
+        final Field aliasMapField = 
AliasHandler.class.getDeclaredField("aliasMapsMap");
         aliasMapField.setAccessible(true);
         this.aliasMap = (Map<String, Map<String, String>>) 
aliasMapField.get(mapEntries.ah);
 
-        final Field detectedInvalidAliasesField =
-                
MapEntries.AliasHandler.class.getDeclaredField("detectedInvalidAliases");
+        final Field detectedInvalidAliasesField = 
AliasHandler.class.getDeclaredField("detectedInvalidAliases");
         detectedInvalidAliasesField.setAccessible(true);
         this.detectedInvalidAliases = (AtomicLong) 
detectedInvalidAliasesField.get(mapEntries.ah);
 
-        final Field detectedConflictingAliasesField =
-                
MapEntries.AliasHandler.class.getDeclaredField("detectedConflictingAliases");
+        final Field detectedConflictingAliasesField = 
AliasHandler.class.getDeclaredField("detectedConflictingAliases");
         detectedConflictingAliasesField.setAccessible(true);
         this.detectedConflictingAliases = (AtomicLong) 
detectedConflictingAliasesField.get(mapEntries.ah);
     }
@@ -159,7 +157,7 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
             String path,
             Runnable callback)
             throws IllegalAccessException, NoSuchMethodException, 
InvocationTargetException {
-        Method method = MapEntries.AliasHandler.class.getDeclaredMethod(
+        Method method = AliasHandler.class.getDeclaredMethod(
                 "removeAlias", ResourceResolver.class, String.class, 
String.class, Runnable.class);
         method.setAccessible(true);
         method.invoke(mapEntries.ah, resourceResolver, contentPath, path, 
callback);
@@ -1176,7 +1174,7 @@ public class AliasMapEntriesTest extends 
AbstractMappingMapEntriesTest {
 
     @Test
     public void test_initAliasesAfterDispose() {
-        MapEntries.AliasHandler ah = mapEntries.ah;
+        AliasHandler ah = mapEntries.ah;
         mapEntries.dispose();
         boolean enabled = ah.initializeAliases();
         assertFalse("return value (isOptimizeAliasResolutionEnabled) should be 
false", enabled);
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 4f650f45..d5d37ed6 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
@@ -110,7 +110,7 @@ public class MapEntriesTest extends 
AbstractMappingMapEntriesTest {
         mapEntries = new MapEntries(
                 resourceResolverFactory, bundleContext, eventAdmin, 
stringInterpolationProvider, metrics);
 
-        final Field aliasMapField = 
MapEntries.AliasHandler.class.getDeclaredField("aliasMapsMap");
+        final Field aliasMapField = 
AliasHandler.class.getDeclaredField("aliasMapsMap");
         aliasMapField.setAccessible(true);
         this.aliasMap = (Map<String, Map<String, String>>) 
aliasMapField.get(mapEntries.ah);
     }

Reply via email to