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

cziegeler pushed a commit to branch SLING-9622
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-auth-core.git


The following commit(s) were added to refs/heads/SLING-9622 by this push:
     new 60e6e22  SLING-9622 : Handle mapping within auth.core bundle and 
update auth requirements when mapping changes
60e6e22 is described below

commit 60e6e221226db80a36923cb4c9a2b75910b9ea86
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Tue Aug 4 17:11:54 2020 +0200

    SLING-9622 : Handle mapping within auth.core bundle and update auth 
requirements when mapping changes
---
 pom.xml                                            |   2 +-
 .../sling/auth/core/impl/PathBasedHolderCache.java |   6 +-
 .../sling/auth/core/impl/SlingAuthenticator.java   | 235 +++++++++-----------
 .../impl/SlingAuthenticatorServiceListener.java    | 237 +++++++++++++++++----
 .../SlingAuthenticatorServiceListenerTest.java     |  70 +++++-
 5 files changed, 364 insertions(+), 186 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5dd5b1b..f7d1a1a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,7 +71,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.18.0</version>
+            <version>2.20.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git 
a/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolderCache.java 
b/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolderCache.java
index 24dcf5a..ab0982d 100644
--- a/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolderCache.java
+++ b/src/main/java/org/apache/sling/auth/core/impl/PathBasedHolderCache.java
@@ -49,11 +49,7 @@ public class PathBasedHolderCache<Type extends 
PathBasedHolder> {
         this.rwLock.writeLock().lock();
         try {
 
-            Map<String, SortedSet<Type>> byHostMap = 
cache.get(holder.protocol);
-            if (byHostMap == null) {
-                byHostMap = new HashMap<String, SortedSet<Type>>();
-                cache.put(holder.protocol, byHostMap);
-            }
+            final Map<String, SortedSet<Type>> byHostMap = 
cache.computeIfAbsent(holder.protocol, protocol -> new HashMap<>());
 
             final SortedSet<Type> byPathSet = new TreeSet<Type>();
 
diff --git 
a/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java 
b/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
index 0e304a6..914099c 100644
--- a/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
+++ b/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticator.java
@@ -30,6 +30,7 @@ import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Executors;
 
 import javax.jcr.SimpleCredentials;
 import javax.security.auth.login.AccountLockedException;
@@ -47,10 +48,8 @@ import org.apache.sling.api.SlingConstants;
 import org.apache.sling.api.auth.Authenticator;
 import org.apache.sling.api.auth.NoAuthenticationHandlerException;
 import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.auth.core.AuthConstants;
 import org.apache.sling.auth.core.AuthUtil;
 import org.apache.sling.auth.core.AuthenticationSupport;
@@ -258,11 +257,6 @@ public class SlingAuthenticator implements Authenticator,
      */
     private static final String AUTH_INFO_PROP_FEEDBACK_HANDLER = 
"$$sling.auth.AuthenticationFeedbackHandler$$";
 
-    /**
-     * Request attribute holding the resolved path
-     */
-    private static final String ATTR_RESOLVED_PATH = 
SlingAuthenticator.class.getName().concat("/resolvedPath");
-
     @Reference
     private ResourceResolverFactory resourceResolverFactory;
 
@@ -339,15 +333,12 @@ public class SlingAuthenticator implements Authenticator,
     @Reference(policy=ReferencePolicy.DYNAMIC, cardinality = 
ReferenceCardinality.OPTIONAL)
     private volatile EventAdmin eventAdmin;
 
-    private ResourceResolver serviceResolver;
-
     // ---------- SCR integration
 
     @Activate
     private void activate(final BundleContext bundleContext,
             final Config config) throws LoginException {
         modified(config);
-        this.serviceResolver = 
resourceResolverFactory.getServiceResourceResolver(null);
 
         AuthenticatorWebConsolePlugin plugin = new 
AuthenticatorWebConsolePlugin(
             this);
@@ -364,7 +355,7 @@ public class SlingAuthenticator implements Authenticator,
             Servlet.class, plugin, props);
 
         serviceListener = SlingAuthenticatorServiceListener.createListener(
-            bundleContext, this.authRequiredCache);
+            bundleContext, Executors.newSingleThreadExecutor(), 
resourceResolverFactory, this.authRequiredCache);
 
         authHandlerTracker = new AuthenticationHandlerTracker(bundleContext,
             authHandlerCache);
@@ -458,7 +449,7 @@ public class SlingAuthenticator implements Authenticator,
         }
 
         if (serviceListener != null) {
-            bundleContext.removeServiceListener(serviceListener);
+            serviceListener.stop(bundleContext);
             serviceListener = null;
         }
 
@@ -466,10 +457,6 @@ public class SlingAuthenticator implements Authenticator,
             webConsolePlugin.unregister();
             webConsolePlugin = null;
         }
-        if ( this.serviceResolver != null ) {
-            this.serviceResolver.close();
-            this.serviceResolver = null;
-        }
     }
 
     // --------- AuthenticationSupport interface
@@ -492,34 +479,29 @@ public class SlingAuthenticator implements Authenticator,
     @Override
     public boolean handleSecurity(HttpServletRequest request,
             HttpServletResponse response) {
+        // 0. Nothing to do, if the session is also in the request
+        // this might be the case if the request is handled as a result
+        // of a servlet container include inside another Sling request
+        Object sessionAttr = request.getAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+        if (sessionAttr instanceof ResourceResolver) {
+            log.debug("handleSecurity: Request already authenticated, nothing 
to do");
+            return true;
+        } else if (sessionAttr != null) {
+            // warn and remove existing non-session
+            log.warn("handleSecurity: Overwriting existing ResourceResolver 
attribute ({})", sessionAttr);
+            request.removeAttribute(REQUEST_ATTRIBUTE_RESOLVER);
+        }
 
-        try {
-            // 0. Nothing to do, if the session is also in the request
-            // this might be the case if the request is handled as a result
-            // of a servlet container include inside another Sling request
-            Object sessionAttr = 
request.getAttribute(REQUEST_ATTRIBUTE_RESOLVER);
-            if (sessionAttr instanceof ResourceResolver) {
-                log.debug("handleSecurity: Request already authenticated, 
nothing to do");
-                return true;
-            } else if (sessionAttr != null) {
-                // warn and remove existing non-session
-                log.warn("handleSecurity: Overwriting existing 
ResourceResolver attribute ({})", sessionAttr);
-                request.removeAttribute(REQUEST_ATTRIBUTE_RESOLVER);
-            }
-
-            boolean process = doHandleSecurity(request, response);
-            if (process && expectAuthenticationHandler(request)) {
-                log.warn("handleSecurity: AuthenticationHandler did not block 
request; access denied");
-                request.removeAttribute(AuthenticationHandler.FAILURE_REASON);
-                
request.removeAttribute(AuthenticationHandler.FAILURE_REASON_CODE);
-                AuthUtil.sendInvalid(request, response);
-                return false;
-            }
-
-            return process;
-        } finally {
-            request.removeAttribute(ATTR_RESOLVED_PATH);
+        boolean process = doHandleSecurity(request, response);
+        if (process && expectAuthenticationHandler(request)) {
+            log.warn("handleSecurity: AuthenticationHandler did not block 
request; access denied");
+            request.removeAttribute(AuthenticationHandler.FAILURE_REASON);
+            request.removeAttribute(AuthenticationHandler.FAILURE_REASON_CODE);
+            AuthUtil.sendInvalid(request, response);
+            return false;
         }
+
+        return process;
     }
 
     private boolean doHandleSecurity(HttpServletRequest request, 
HttpServletResponse response) {
@@ -597,54 +579,50 @@ public class SlingAuthenticator implements Authenticator,
             throw new IllegalStateException("Response already committed");
         }
 
-        try {
-            // select path used for authentication handler selection
-            final Collection<AbstractAuthenticationHandlerHolder>[] 
holdersArray = this.authHandlerCache
-                    .findApplicableHolders(request);
-            final String path = getHandlerSelectionPath(request);
-            boolean done = false;
-            for (int m = 0; !done && m < holdersArray.length; m++) {
-                final Collection<AbstractAuthenticationHandlerHolder> 
holderList = holdersArray[m];
-                if ( holderList != null ) {
-                    for (AbstractAuthenticationHandlerHolder holder : 
holderList) {
-                        if (isNodeRequiresAuthHandler(path, holder.path)) {
-                            log.debug("login: requesting authentication using 
handler: {}",
-                                holder);
-
-                            try {
-                                done = holder.requestCredentials(request, 
response);
-                            } catch (IOException ioe) {
-                                log.error(
-                                    "login: Failed sending authentication 
request through handler "
-                                        + holder + ", access forbidden", ioe);
-                                done = true;
-                            }
-                            if (done) {
-                                break;
-                            }
+        // select path used for authentication handler selection
+        final Collection<AbstractAuthenticationHandlerHolder>[] holdersArray = 
this.authHandlerCache
+                .findApplicableHolders(request);
+        final String path = getHandlerSelectionPath(request);
+        boolean done = false;
+        for (int m = 0; !done && m < holdersArray.length; m++) {
+            final Collection<AbstractAuthenticationHandlerHolder> holderList = 
holdersArray[m];
+            if ( holderList != null ) {
+                for (AbstractAuthenticationHandlerHolder holder : holderList) {
+                    if (isNodeRequiresAuthHandler(path, holder.path)) {
+                        log.debug("login: requesting authentication using 
handler: {}",
+                            holder);
+
+                        try {
+                            done = holder.requestCredentials(request, 
response);
+                        } catch (IOException ioe) {
+                            log.error(
+                                "login: Failed sending authentication request 
through handler "
+                                    + holder + ", access forbidden", ioe);
+                            done = true;
+                        }
+                        if (done) {
+                            break;
                         }
                     }
                 }
             }
+        }
 
-            // fall back to HTTP Basic handler (if not done already)
-            if (!done && httpBasicHandler != null) {
-                done = httpBasicHandler.requestCredentials(request, response);
-            }
+        // fall back to HTTP Basic handler (if not done already)
+        if (!done && httpBasicHandler != null) {
+            done = httpBasicHandler.requestCredentials(request, response);
+        }
 
-            // no handler could send an authentication request, throw
-            if (!done) {
-                int size = 0;
-                for (int m = 0; m < holdersArray.length; m++) {
-                    if (holdersArray[m] != null) {
-                        size += holdersArray[m].size();
-                    }
+        // no handler could send an authentication request, throw
+        if (!done) {
+            int size = 0;
+            for (int m = 0; m < holdersArray.length; m++) {
+                if (holdersArray[m] != null) {
+                    size += holdersArray[m].size();
                 }
-                log.info("login: No handler for request ({} handlers 
available)", size);
-                throw new NoAuthenticationHandlerException();
             }
-        } finally {
-            request.removeAttribute(ATTR_RESOLVED_PATH);
+            log.info("login: No handler for request ({} handlers available)", 
size);
+            throw new NoAuthenticationHandlerException();
         }
     }
 
@@ -664,31 +642,27 @@ public class SlingAuthenticator implements Authenticator,
         // make sure impersonation is dropped
         setSudoCookie(request, response, new AuthenticationInfo("dummy", 
request.getRemoteUser()));
 
-        try {
-            final String path = getHandlerSelectionPath(request);
-            final Collection<AbstractAuthenticationHandlerHolder>[] 
holdersArray = this.authHandlerCache
-                    .findApplicableHolders(request);
-            for (int m = 0; m < holdersArray.length; m++) {
-                final Collection<AbstractAuthenticationHandlerHolder> 
holderSet = holdersArray[m];
-                if (holderSet != null) {
-                    for (AbstractAuthenticationHandlerHolder holder : 
holderSet) {
-                        if (isNodeRequiresAuthHandler(path, holder.path)) {
-                            log.debug("logout: dropping authentication using 
handler: {}",
-                                holder);
-
-                            try {
-                                holder.dropCredentials(request, response);
-                            } catch (IOException ioe) {
-                                log.error(
-                                    "logout: Failed dropping authentication 
through handler "
-                                        + holder, ioe);
-                            }
+        final String path = getHandlerSelectionPath(request);
+        final Collection<AbstractAuthenticationHandlerHolder>[] holdersArray = 
this.authHandlerCache
+                .findApplicableHolders(request);
+        for (int m = 0; m < holdersArray.length; m++) {
+            final Collection<AbstractAuthenticationHandlerHolder> holderSet = 
holdersArray[m];
+            if (holderSet != null) {
+                for (AbstractAuthenticationHandlerHolder holder : holderSet) {
+                    if (isNodeRequiresAuthHandler(path, holder.path)) {
+                        log.debug("logout: dropping authentication using 
handler: {}",
+                            holder);
+
+                        try {
+                            holder.dropCredentials(request, response);
+                        } catch (IOException ioe) {
+                            log.error(
+                                "logout: Failed dropping authentication 
through handler "
+                                    + holder, ioe);
                         }
                     }
                 }
             }
-        } finally {
-            request.removeAttribute(ATTR_RESOLVED_PATH);
         }
 
         if (httpBasicHandler != null) {
@@ -767,32 +741,27 @@ public class SlingAuthenticator implements Authenticator,
 
     // ---------- internal
 
-    private String getPath(HttpServletRequest request) {
-        String path = (String)request.getAttribute(ATTR_RESOLVED_PATH);
-        if ( path == null ) {
-            final StringBuilder sb = new StringBuilder();
-            if (request.getServletPath() != null) {
-                sb.append(request.getServletPath());
-            }
-            if (request.getPathInfo() != null) {
-                sb.append(request.getPathInfo());
-            }
-            path = sb.toString();
-            // Get the path used to select the authenticator, if the 
SlingServlet
-            // itself has been requested without any more info, this will be 
empty
-            // and we assume the root (SLING-722)
-            if (path.length() == 0) {
-                path = "/";
-            }
-
-            if ( !path.equals("/") && serviceResolver != null ) { // NULL 
check to let unit tests pass
-                final Resource mappedResource = serviceResolver.resolve(path);
-                if ( !ResourceUtil.isNonExistingResource(mappedResource) ) {
-                    path = mappedResource.getPath();
-                }
-            }
-            request.setAttribute(ATTR_RESOLVED_PATH, path);
+    /**
+     * Get the request path from the request
+     * @param request The request
+     * @return The path
+     */
+    private String getPath(final HttpServletRequest request) {
+        final StringBuilder sb = new StringBuilder();
+        if (request.getServletPath() != null) {
+            sb.append(request.getServletPath());
         }
+        if (request.getPathInfo() != null) {
+            sb.append(request.getPathInfo());
+        }
+        String path = sb.toString();
+        // Get the path used to select the authenticator, if the SlingServlet
+        // itself has been requested without any more info, this will be empty
+        // and we assume the root (SLING-722)
+        if (path.length() == 0) {
+            path = "/";
+        }
+
         return path;
     }
 
@@ -1010,10 +979,6 @@ public class SlingAuthenticator implements Authenticator,
     }
 
    private boolean isNodeRequiresAuthHandler(String path, String holderPath) {
-        if (path == null || holderPath == null) {
-            return false;
-        }
-
         if (("/").equals(holderPath)) {
             return true;
         }
@@ -1028,10 +993,8 @@ public class SlingAuthenticator implements Authenticator,
             return true;
         }
 
-        if (path.startsWith(holderPath)) {
-            if (path.charAt(holderPathLength) == '/' || 
path.charAt(holderPathLength) == '.') {
-                return true;
-            }
+        if (path.startsWith(holderPath) && (path.charAt(holderPathLength) == 
'/' || path.charAt(holderPathLength) == '.')) {
+            return true;
         }
         return false;
     }
diff --git 
a/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListener.java
 
b/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListener.java
index 45a0bba..d991580 100644
--- 
a/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListener.java
+++ 
b/src/main/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListener.java
@@ -19,11 +19,22 @@
 package org.apache.sling.auth.core.impl;
 
 import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Dictionary;
 import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.sling.api.SlingConstants;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.mapping.ResourceMapper;
 import org.apache.sling.auth.core.AuthConstants;
 import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.osgi.framework.AllServiceListener;
@@ -32,62 +43,195 @@ import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceEvent;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
 
-public class SlingAuthenticatorServiceListener implements AllServiceListener {
+/**
+ * Service listener keeping track of all auth requirements registered in
+ * the service registry.
+ *
+ */
+public class SlingAuthenticatorServiceListener implements AllServiceListener, 
EventHandler {
+
+    private static String FILTER_EXPR = 
"(".concat(AuthConstants.AUTH_REQUIREMENTS).concat("=*)");
+
+    private static final Long UPDATE = 0L;
+
+    private static final Long CLEAR = -1L;
+
+    private final ResourceResolverFactory resolverFactory;
 
     private final PathBasedHolderCache<AuthenticationRequirementHolder> 
authRequiredCache;
 
-    private final HashMap<Object, Set<String>> regProps = new HashMap<Object, 
Set<String>>();
+    private final Map<Long, Set<String>> regProps = new ConcurrentHashMap<>();
+
+    private final Map<Long, List<AuthenticationRequirementHolder>> props = new 
ConcurrentHashMap<>();
+
+    private ServiceRegistration<EventHandler> serviceRegistration;
 
-    private final HashMap<Object, List<AuthenticationRequirementHolder>> props 
= new HashMap<Object, List<AuthenticationRequirementHolder>>();
+    private final Map<Long, Action> processingQueue = new LinkedHashMap<>();
+
+    private final Executor executor;
+
+    private final AtomicBoolean backgroundJobRunning = new 
AtomicBoolean(false);
 
     static SlingAuthenticatorServiceListener createListener(
         final BundleContext context,
+        final Executor executor,
+        final ResourceResolverFactory factory,
         final PathBasedHolderCache<AuthenticationRequirementHolder> 
authRequiredCache) {
-        SlingAuthenticatorServiceListener listener = new 
SlingAuthenticatorServiceListener(authRequiredCache);
+
+        final SlingAuthenticatorServiceListener listener = new 
SlingAuthenticatorServiceListener(authRequiredCache,
+                executor,
+                factory);
         try {
-            final String filter = "(" + AuthConstants.AUTH_REQUIREMENTS + 
"=*)";
-            context.addServiceListener(listener, filter);
-            ServiceReference<?>[] refs = context.getAllServiceReferences(null,
-                filter);
+            context.addServiceListener(listener, FILTER_EXPR);
+            final Dictionary<String, Object> props = new Hashtable<>();
+            props.put(EventConstants.EVENT_TOPIC, 
SlingConstants.TOPIC_RESOURCE_RESOLVER_MAPPING_CHANGED);
+            
listener.setServiceRegistration(context.registerService(EventHandler.class, 
listener, props));
+            ServiceReference<?>[] refs = context.getAllServiceReferences(null, 
FILTER_EXPR);
             if (refs != null) {
-                for (ServiceReference<?> ref : refs) {
-                    listener.addService(ref);
+                for (final ServiceReference<?> ref : refs) {
+                    final Long id = 
(Long)ref.getProperty(Constants.SERVICE_ID);
+                    listener.queue(id, new Action(ActionType.ADDED, ref));
                 }
             }
+
+            listener.schedule();
+
             return listener;
         } catch (InvalidSyntaxException ise) {
         }
         return null;
     }
 
-    private SlingAuthenticatorServiceListener(final 
PathBasedHolderCache<AuthenticationRequirementHolder> authRequiredCache) {
+    private SlingAuthenticatorServiceListener(final 
PathBasedHolderCache<AuthenticationRequirementHolder> authRequiredCache,
+            final Executor executor,
+            final ResourceResolverFactory factory) {
         this.authRequiredCache = authRequiredCache;
+        this.executor = executor;
+        this.resolverFactory = factory;
+    }
+
+    void setServiceRegistration(final ServiceRegistration<EventHandler> reg) {
+        this.serviceRegistration = reg;
+    }
+
+    public void stop(final BundleContext bundleContext) {
+        bundleContext.removeServiceListener(this);
+        if ( this.serviceRegistration != null ) {
+            this.serviceRegistration.unregister();
+            this.serviceRegistration = null;
+        }
+        queue(CLEAR, null);
+        backgroundJobRunning.set(false);
+    }
+
+    private void schedule() {
+        if ( this.backgroundJobRunning.compareAndSet(false, true) ) {
+            this.executor.execute(() -> { processQueue(); });
+        }
     }
 
     @Override
     public void serviceChanged(final ServiceEvent event) {
-        synchronized ( props ) {
-            // modification of service properties, unregistration of the
-            // service or service properties does not contain requirements
-            // property any longer (new event with type 8 added in OSGi Core
-            // 4.2)
-            if ((event.getType() & (ServiceEvent.UNREGISTERING | 
ServiceEvent.MODIFIED_ENDMATCH)) != 0) {
-                removeService(event.getServiceReference());
+        // modification of service properties, unregistration of the
+        // service or service properties does not contain requirements
+        // property any longer (new event with type 8 added in OSGi Core
+        // 4.2)
+        final Long id = 
(Long)event.getServiceReference().getProperty(Constants.SERVICE_ID);
+        if ((event.getType() & (ServiceEvent.UNREGISTERING | 
ServiceEvent.MODIFIED_ENDMATCH)) != 0) {
+            queue(id, new Action(ActionType.REMOVED, 
event.getServiceReference()));
+        }
+
+        if ((event.getType() & ServiceEvent.MODIFIED ) != 0) {
+            queue(id, new Action(ActionType.MODIFIED, 
event.getServiceReference()));
+        }
+
+        // add requirements for newly registered services and for
+        // updated services
+        if ((event.getType() & ServiceEvent.REGISTERED ) != 0) {
+            queue(id, new Action(ActionType.ADDED, 
event.getServiceReference()));
+        }
+        schedule();
+    }
+
+    @Override
+    public void handleEvent(final Event event) {
+        queue(UPDATE, null);
+        schedule();
+    }
+
+    /**
+     * Queue a new action
+     * @param id The id of the service
+     * @param action The action to take
+     */
+    private void queue(final Long id, final Action action) {
+        synchronized ( this.processingQueue ) {
+            if ( id == CLEAR ) {
+                this.processingQueue.clear();
+            } else if ( id == UPDATE ) {
+                for(final Long updateId: this.props.keySet()) {
+                    this.processingQueue.putIfAbsent(updateId, new 
Action(ActionType.UPDATE, null));
+                }
+            } else {
+                this.processingQueue.remove(id);
+                this.processingQueue.put(id, action);
             }
+        }
+    }
 
-            if ((event.getType() & ServiceEvent.MODIFIED ) != 0) {
-                modifiedService(event.getServiceReference());
+    private void processQueue() {
+        ResourceResolver resolver = null;
+        ResourceMapper mapper = null;
+        try {
+            while ( this.backgroundJobRunning.get() ) {
+                Action action = null;
+                synchronized ( this.processingQueue ) {
+                    final Iterator<Action> iter = 
this.processingQueue.values().iterator();
+                    if ( iter.hasNext() ) {
+                        action = iter.next();
+                        iter.remove();
+                    }
+                }
+                if ( action == null ) {
+                    synchronized ( this.processingQueue ) {
+                        this.backgroundJobRunning.compareAndSet(true, 
!this.processingQueue.isEmpty());
+                    }
+                } else {
+                    if ( action.type != ActionType.REMOVED ) {
+                        if ( mapper == null ) {
+                            try {
+                                resolver = 
this.resolverFactory.getServiceResourceResolver(null);
+                                mapper = 
resolver.adaptTo(ResourceMapper.class);
+                            } catch ( final 
org.apache.sling.api.resource.LoginException le) {
+                                // ignore
+                            }
+                        }
+                    }
+                    process(mapper, action);
+                }
             }
 
-            // add requirements for newly registered services and for
-            // updated services
-            if ((event.getType() & ServiceEvent.REGISTERED ) != 0) {
-                addService(event.getServiceReference());
+        } finally {
+            if ( resolver != null ) {
+                resolver.close();
             }
         }
     }
 
+    private void process(final ResourceMapper mapper, final Action action) {
+        switch ( action.type ) {
+            case ADDED : this.addService(mapper, action.reference); break;
+            case REMOVED : this.removeService(action.reference); break;
+            case MODIFIED : this.modifiedService(mapper, action.reference); 
break;
+            case UPDATE: // TODO
+        }
+    }
+
     /**
      * Register all known services.
      */
@@ -107,11 +251,17 @@ public class SlingAuthenticatorServiceListener implements 
AllServiceListener {
         }
     }
 
-    private Set<String> buildPathsSet(final String[] authReqPaths) {
+    private Set<String> buildPathsSet(final ResourceMapper mapper, final 
String[] authReqPaths) {
         final Set<String> paths = new HashSet<>();
         for(final String authReq : authReqPaths) {
             if (authReq != null && authReq.length() > 0) {
                 paths.add(authReq);
+
+                if ( mapper != null ) {
+                    for(final String mappedPath : 
mapper.getAllMappings(authReq)) {
+                        paths.add(mappedPath);
+                    }
+                }
             }
         }
         return paths;
@@ -121,22 +271,21 @@ public class SlingAuthenticatorServiceListener implements 
AllServiceListener {
      * Process a new service with auth requirements
      * @param ref The service reference
      */
-    private void addService(final ServiceReference<?> ref) {
+    private void addService(final ResourceMapper mapper, final 
ServiceReference<?> ref) {
         final String[] authReqPaths = 
PropertiesUtil.toStringArray(ref.getProperty(AuthConstants.AUTH_REQUIREMENTS));
         if ( authReqPaths != null ) {
-            final Set<String> paths = buildPathsSet(authReqPaths);
+            final Set<String> paths = buildPathsSet(mapper, authReqPaths);
 
             if ( !paths.isEmpty() ) {
                 final List<AuthenticationRequirementHolder> authReqList = new 
ArrayList<AuthenticationRequirementHolder>();
                 for (final String authReq : paths) {
-                    authReqList.add(AuthenticationRequirementHolder.fromConfig(
-                        authReq, ref));
+                    
authReqList.add(AuthenticationRequirementHolder.fromConfig(authReq, ref));
                 }
 
                 // keep original set for modified
-                regProps.put(ref.getProperty(Constants.SERVICE_ID), paths);
+                regProps.put((Long)ref.getProperty(Constants.SERVICE_ID), 
paths);
                 registerService(authReqList);
-                props.put(ref.getProperty(Constants.SERVICE_ID), authReqList);
+                props.put((Long)ref.getProperty(Constants.SERVICE_ID), 
authReqList);
             }
         }
     }
@@ -145,14 +294,14 @@ public class SlingAuthenticatorServiceListener implements 
AllServiceListener {
      * Process a modified service with auth requirements
      * @param ref The service reference
      */
-    private void modifiedService(final ServiceReference<?> ref) {
+    private void modifiedService(final ResourceMapper mapper, final 
ServiceReference<?> ref) {
         final String[] authReqPaths = 
PropertiesUtil.toStringArray(ref.getProperty(AuthConstants.AUTH_REQUIREMENTS));
         if ( authReqPaths != null ) {
             final Set<String> oldPaths = 
regProps.get(ref.getProperty(Constants.SERVICE_ID));
             if ( oldPaths == null ) {
-                addService(ref);
+                addService(mapper, ref);
             } else {
-                final Set<String> paths = buildPathsSet(authReqPaths);
+                final Set<String> paths = buildPathsSet(mapper, authReqPaths);
                 if ( paths.isEmpty() ) {
                     removeService(ref);
                 } else {
@@ -174,7 +323,7 @@ public class SlingAuthenticatorServiceListener implements 
AllServiceListener {
                             authRequiredCache.addHolder(holder);
                         }
                     }
-                    regProps.put(ref.getProperty(Constants.SERVICE_ID), paths);
+                    regProps.put((Long)ref.getProperty(Constants.SERVICE_ID), 
paths);
                 }
             }
         } else {
@@ -195,4 +344,20 @@ public class SlingAuthenticatorServiceListener implements 
AllServiceListener {
         }
         regProps.remove(ref.getProperty(Constants.SERVICE_ID));
     }
+
+    public enum ActionType {
+        ADDED, MODIFIED, REMOVED, UPDATE
+    }
+
+    public static final class Action {
+
+        public final ActionType type;
+
+        public final ServiceReference<?> reference;
+
+        public Action(final ActionType type, final ServiceReference<?> ref) {
+            this.type = type;
+            this.reference = ref;
+        }
+    }
 }
diff --git 
a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListenerTest.java
 
b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListenerTest.java
index c8d36dc..a5724c3 100644
--- 
a/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListenerTest.java
+++ 
b/src/test/java/org/apache/sling/auth/core/impl/SlingAuthenticatorServiceListenerTest.java
@@ -25,8 +25,13 @@ import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.mapping.ResourceMapper;
 import org.apache.sling.auth.core.AuthConstants;
 import org.junit.Test;
 import org.osgi.framework.BundleContext;
@@ -75,10 +80,24 @@ public class SlingAuthenticatorServiceListenerTest {
         return ref;
     }
 
-    @Test public void testAddRemoveRegistration() {
+    private ResourceResolverFactory createFactoryForMapper(final 
ResourceMapper mapper) throws LoginException {
+        final ResourceResolverFactory factory = 
mock(ResourceResolverFactory.class);
+
+        final ResourceResolver resolver = mock(ResourceResolver.class);
+
+        when(factory.getServiceResourceResolver(null)).thenReturn(resolver);
+
+        when(resolver.adaptTo(ResourceMapper.class)).thenReturn(mapper);
+
+        return factory;
+    }
+
+    @Test public void testAddRemoveRegistration() throws LoginException {
         final PathBasedHolderCache<AuthenticationRequirementHolder> cache = 
new PathBasedHolderCache<AuthenticationRequirementHolder>();
         final BundleContext context = mock(BundleContext.class);
-        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, cache);
+        final ResourceMapper mapper = mock(ResourceMapper.class);
+        
when(mapper.getAllMappings("/path1")).thenReturn(Collections.singleton("/path1"));
+        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, callable -> 
callable.run(), createFactoryForMapper(mapper), cache);
 
         assertTrue(cache.getHolders().isEmpty());
 
@@ -95,10 +114,14 @@ public class SlingAuthenticatorServiceListenerTest {
         assertTrue(cache.getHolders().isEmpty());
     }
 
-    @Test public void testDuplicateRegistration() {
+    @Test public void testDuplicateRegistration() throws LoginException {
         final PathBasedHolderCache<AuthenticationRequirementHolder> cache = 
new PathBasedHolderCache<AuthenticationRequirementHolder>();
         final BundleContext context = mock(BundleContext.class);
-        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, cache);
+        final ResourceMapper mapper = mock(ResourceMapper.class);
+        
when(mapper.getAllMappings("/path1")).thenReturn(Collections.singleton("/path1"));
+        
when(mapper.getAllMappings("/path2")).thenReturn(Collections.singleton("/path2"));
+        
when(mapper.getAllMappings("/path3")).thenReturn(Collections.singleton("/path3"));
+        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, callable -> 
callable.run(), createFactoryForMapper(mapper), cache);
 
         final ServiceReference<?> ref1 = createServiceReference(new String[] 
{"/path1", "/path1", "/path2"});
         listener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, 
ref1));
@@ -117,10 +140,16 @@ public class SlingAuthenticatorServiceListenerTest {
         assertTrue(cache.getHolders().isEmpty());
     }
 
-    @Test public void testAddRemoveRegistrations() {
+    @Test public void testAddRemoveRegistrations() throws LoginException {
         final PathBasedHolderCache<AuthenticationRequirementHolder> cache = 
new PathBasedHolderCache<AuthenticationRequirementHolder>();
         final BundleContext context = mock(BundleContext.class);
-        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, cache);
+        final ResourceMapper mapper = mock(ResourceMapper.class);
+        
when(mapper.getAllMappings("/path1")).thenReturn(Collections.singleton("/path1"));
+        
when(mapper.getAllMappings("/path2")).thenReturn(Collections.singleton("/path2"));
+        
when(mapper.getAllMappings("/path3")).thenReturn(Collections.singleton("/path3"));
+        
when(mapper.getAllMappings("/path4")).thenReturn(Collections.singleton("/path4"));
+        
when(mapper.getAllMappings("/path5")).thenReturn(Collections.singleton("/path5"));
+        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, callable -> 
callable.run(), createFactoryForMapper(mapper), cache);
 
         final ServiceReference<?> ref1 = createServiceReference(new String[] 
{"/path1"});
         listener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, 
ref1));
@@ -147,10 +176,16 @@ public class SlingAuthenticatorServiceListenerTest {
         assertTrue(cache.getHolders().isEmpty());
     }
 
-    @Test public void testModifyRegistration() {
+    @Test public void testModifyRegistration() throws LoginException {
         final PathBasedHolderCache<AuthenticationRequirementHolder> cache = 
new PathBasedHolderCache<AuthenticationRequirementHolder>();
         final BundleContext context = mock(BundleContext.class);
-        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, cache);
+        final ResourceMapper mapper = mock(ResourceMapper.class);
+        
when(mapper.getAllMappings("/path1")).thenReturn(Collections.singleton("/path1"));
+        
when(mapper.getAllMappings("/path2")).thenReturn(Collections.singleton("/path2"));
+        
when(mapper.getAllMappings("/path3")).thenReturn(Collections.singleton("/path3"));
+        
when(mapper.getAllMappings("/path4")).thenReturn(Collections.singleton("/path4"));
+        
when(mapper.getAllMappings("/path5")).thenReturn(Collections.singleton("/path5"));
+        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, callable -> 
callable.run(), createFactoryForMapper(mapper), cache);
 
         final ServiceReference<?> ref1 = createServiceReference(new String[] 
{"/path1", "/path2", "/path3"});
         listener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, 
ref1));
@@ -169,4 +204,23 @@ public class SlingAuthenticatorServiceListenerTest {
         assertTrue(cache.getHolders().isEmpty());
 
     }
+
+    @Test public void testRegistrationWithMapping() throws LoginException {
+        final PathBasedHolderCache<AuthenticationRequirementHolder> cache = 
new PathBasedHolderCache<AuthenticationRequirementHolder>();
+        final BundleContext context = mock(BundleContext.class);
+        final ResourceMapper mapper = mock(ResourceMapper.class);
+        
when(mapper.getAllMappings("/path1")).thenReturn(Arrays.asList("/path1", 
"/path2", "/path3"));
+        final SlingAuthenticatorServiceListener listener = 
SlingAuthenticatorServiceListener.createListener(context, callable -> 
callable.run(), createFactoryForMapper(mapper), cache);
+
+        final ServiceReference<?> ref = createServiceReference(new String[] 
{"/path1"});
+        listener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, 
ref));
+
+        assertPaths(cache, new String[] {"/path1", "/path2", "/path3"},
+                           new ServiceReference<?>[] {ref, ref, ref});
+
+        listener.serviceChanged(new ServiceEvent(ServiceEvent.UNREGISTERING, 
ref));
+
+        assertTrue(cache.getHolders().isEmpty());
+    }
+
 }

Reply via email to