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

pauls pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-servlets-resolver.git


The following commit(s) were added to refs/heads/master by this push:
     new 3636ce5  SLING-9365: Optionally don't create resource providers for 
servlets
3636ce5 is described below

commit 3636ce54b218cef6f017e0cf26e047c60f88a921
Author: Karl Pauls <[email protected]>
AuthorDate: Wed Apr 29 10:49:39 2020 +0200

    SLING-9365: Optionally don't create resource providers for servlets
---
 .../servlets/resolver/internal/ResolverConfig.java |   5 +
 .../servlets/resolver/internal/ScriptResource.java |  14 +-
 .../resolver/internal/ScriptResourceDecorator.java |  98 ++++++++++++
 .../resolver/internal/ScriptResourceResolver.java  | 175 +++++++++++++++++++++
 .../resolver/internal/SlingServletResolver.java    |  37 +++--
 .../internal/helper/AbstractResourceCollector.java |   3 +-
 .../resource/MergingServletResourceProvider.java   | 163 +++++++++++++++++++
 .../resolver/internal/resource/ServletMounter.java |  71 +++++++--
 .../internal/resource/ServletResource.java         |  17 +-
 .../resource/ServletResourceProviderFactory.java   |   8 +-
 .../internal/SlingServletResolverTestBase.java     |   2 +-
 11 files changed, 556 insertions(+), 37 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/ResolverConfig.java 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ResolverConfig.java
index 9159970..21941d8 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/ResolverConfig.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ResolverConfig.java
@@ -58,4 +58,9 @@ public @interface ResolverConfig {
     @AttributeDefinition(name = "Default Extensions", description = "The list 
of extensions for which the default behavior "
             + "will be used. This means that the last path segment of the 
resource type can be used as the script name.")
     String[] servletresolver_defaultExtensions() default "html";
+
+    @AttributeDefinition(name = "Mount Providers", description = "Should 
servlets be mounted as resource providers?" +
+        " If true (the default), servlets will be represented in the content 
tree using resource provider -" +
+        " otherwise, servlets will be decorated back into the content tree 
using a decorator.")
+    boolean servletresolver_mountProviders() default true;
 }
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResource.java 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResource.java
index 36842f2..ff995bb 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResource.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResource.java
@@ -19,6 +19,7 @@
 package org.apache.sling.servlets.resolver.internal;
 
 import java.util.Iterator;
+import java.util.function.Supplier;
 
 import javax.servlet.Servlet;
 
@@ -28,6 +29,7 @@ import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.scripting.SlingScript;
+import org.apache.sling.servlets.resolver.internal.resource.ServletResource;
 
 /**
  * ScriptResource is a resource wrapper of a resource fetched by a
@@ -45,12 +47,12 @@ public class ScriptResource extends AbstractResource {
 
     private final ResourceResolver sharedResourceResolver;
 
-    private final ThreadLocal<ResourceResolver> perThreadResourceResolver;
+    private final Supplier<ResourceResolver> perThreadResourceResolver;
 
     private final String path;
 
     public ScriptResource(final Resource resource,
-            final ThreadLocal<ResourceResolver> perThreadScriptResolver,
+            final Supplier<ResourceResolver> perThreadScriptResolver,
             final ResourceResolver sharedResourceResolver) {
         this.path = resource.getPath();
         this.sharedResourceResolver = sharedResourceResolver;
@@ -99,9 +101,11 @@ public class ScriptResource extends AbstractResource {
     @Override
     public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
         if ( type == Servlet.class ) {
-            final Servlet s = (Servlet)super.adaptTo(type);
-            if ( s != null ) {
-                return (AdapterType)s;
+            if (! (this.getActiveResource() instanceof ServletResource)) {
+                final Servlet s = (Servlet) super.adaptTo(type);
+                if ( s != null ) {
+                    return (AdapterType)s;
+                }
             }
         } else if ( type == SlingScript.class ) {
             final SlingScript s = (SlingScript)super.adaptTo(type);
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResourceDecorator.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResourceDecorator.java
new file mode 100644
index 0000000..5034cdc
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResourceDecorator.java
@@ -0,0 +1,98 @@
+/*
+ * 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.servlets.resolver.internal;
+
+import java.util.Iterator;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceDecorator;
+import org.apache.sling.api.resource.ResourceResolver;
+import 
org.apache.sling.servlets.resolver.internal.resource.MergingServletResourceProvider;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component
+public class ScriptResourceDecorator implements ResourceDecorator {
+    private final MergingServletResourceProvider provider;
+
+    @Activate
+    public ScriptResourceDecorator(@Reference MergingServletResourceProvider 
provider) {
+        this.provider = provider;
+    }
+
+    @Override
+    public Resource decorate(Resource resource) {
+        String path = resource.getPath();
+        Resource script = getResource(resource, path);
+        if (script == resource && 
Resource.RESOURCE_TYPE_NON_EXISTING.equals(resource.getResourceType())) {
+            int idx = path.indexOf('.');
+            if (idx != -1) {
+                path = path.substring(0, idx);
+                script = getResource(resource, path);
+            }
+        }
+        script.getResourceMetadata().putAll(resource.getResourceMetadata());
+
+        return script;
+    }
+
+    @Override
+    public Resource decorate(Resource resource, HttpServletRequest request) {
+        return decorate(resource);
+    }
+
+    private Resource getResource(Resource resource, String path) {
+        return provider.getResource(new ResolveContext<Void>() {
+            @Override
+            public ResourceResolver getResourceResolver() {
+                return new 
ScriptResourceResolver(resource.getResourceResolver(), () -> provider);
+            }
+
+            @Override
+            public Void getProviderState() {
+                return null;
+            }
+
+            @Override
+            public ResolveContext<?> getParentResolveContext() {
+                return null;
+            }
+
+            @Override
+            public ResourceProvider<?> getParentResourceProvider() {
+                return new ResourceProvider<Object>() {
+                    @Override
+                    public Resource getResource(ResolveContext<Object> ctx, 
String path, ResourceContext resourceContext, Resource parent) {
+                        return resource;
+                    }
+
+                    @Override
+                    public Iterator<Resource> 
listChildren(ResolveContext<Object> ctx, Resource parent) {
+                        return null;
+                    }
+                };
+            }
+        }, path);
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResourceResolver.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResourceResolver.java
new file mode 100644
index 0000000..c6ff904
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResourceResolver.java
@@ -0,0 +1,175 @@
+/*
+ * 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.servlets.resolver.internal;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.function.Supplier;
+
+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.ResourceWrapper;
+import org.apache.sling.api.wrappers.IteratorWrapper;
+import org.apache.sling.api.wrappers.ResourceResolverWrapper;
+import 
org.apache.sling.servlets.resolver.internal.resource.MergingServletResourceProvider;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+
+public class ScriptResourceResolver extends ResourceResolverWrapper {
+    private final ResourceResolver resolver;
+    private final Supplier<MergingServletResourceProvider> provider;
+
+    public ScriptResourceResolver(ResourceResolver resolver, 
Supplier<MergingServletResourceProvider> provider) {
+        super(resolver);
+        this.resolver = resolver;
+        this.provider = provider;
+    }
+
+    public static ScriptResourceResolver wrap(ResourceResolver 
scriptResourceResolver, Supplier<MergingServletResourceProvider> provider) {
+        return new ScriptResourceResolver(scriptResourceResolver, provider);
+    }
+
+    @Override
+    public Iterable<Resource> getChildren(Resource parent) {
+        return () -> listChildren(parent);
+    }
+
+    public Resource getResource(String scriptPath) {
+        MergingServletResourceProvider provider = this.provider.get();
+
+        if (provider == null) {
+            return super.getResource(scriptPath);
+        }
+        else {
+            return wrap(provider.getResource(new ResolveContext() {
+                @Override
+                public ResourceResolver getResourceResolver() {
+                    return ScriptResourceResolver.this;
+                }
+
+                @Override
+                public Object getProviderState() {
+                    return null;
+                }
+
+                @Override
+                public ResolveContext<?> getParentResolveContext() {
+                    return null;
+                }
+
+                @Override
+                public ResourceProvider<?> getParentResourceProvider() {
+                    return new ResourceProvider() {
+                        @Override
+                        public Resource getResource(ResolveContext ctx, String 
path, ResourceContext resourceContext, Resource parent) {
+                            return resolver.getResource(path);
+                        }
+
+                        @Override
+                        public Iterator<Resource> listChildren(ResolveContext 
ctx, Resource parent) {
+                            return resolver.listChildren(parent);
+                        }
+                    };
+                }
+            }, scriptPath));
+        }
+    }
+
+    @Override
+    public Iterator<Resource> listChildren(Resource parent) {
+        MergingServletResourceProvider provider = this.provider.get();
+        if (provider == null) {
+            return super.listChildren(parent);
+        }
+        else {
+            return wrap(provider.listChildren(new ResolveContext() {
+                @Override
+                public ResourceResolver getResourceResolver() {
+                    return ScriptResourceResolver.this;
+                }
+
+                @Override
+                public Object getProviderState() {
+                    return null;
+                }
+
+                public ResolveContext<?> getParentResolveContext() {
+                    return null;
+                }
+
+                public ResourceProvider<?> getParentResourceProvider() {
+                    return new ResourceProvider() {
+                        @Override
+                        public Resource getResource(ResolveContext ctx, String 
path, ResourceContext resourceContext, Resource parent) {
+                            return resolver.getResource(path);
+                        }
+
+                        @Override
+                        public Iterator<Resource> listChildren(ResolveContext 
ctx, Resource parent) {
+                            return resolver.listChildren(parent);
+                        }
+                    };
+                }
+            }, unwrap(parent)));
+        }
+    }
+
+    private Resource wrap(Resource resource) {
+        if (resource != null && !(resource.getResourceResolver() instanceof 
ScriptResourceResolver)) {
+            resource = new ScriptResourceResolverResourceWrapper(resource);
+        }
+        return resource;
+    }
+
+    private Iterator<Resource> wrap(Iterator<Resource> iter) {
+        if (iter != null) {
+            iter = new IteratorWrapper<Resource>(iter){
+                @Override
+                public Resource next() {
+                    return wrap(super.next());
+                }
+            };
+        }
+        return iter;
+    }
+
+    private Resource unwrap(Resource resource) {
+        if (resource instanceof ScriptResourceResolverResourceWrapper) {
+            resource = ((ResourceWrapper) resource).getResource();
+        }
+        return resource;
+    }
+
+    public ScriptResourceResolver clone(Map o) throws LoginException {
+        return ScriptResourceResolver.wrap(resolver.clone(o), provider);
+    }
+
+    private class ScriptResourceResolverResourceWrapper extends 
ResourceWrapper {
+        public ScriptResourceResolverResourceWrapper(Resource resource) {
+            super(resource);
+        }
+
+        @Override
+        public ResourceResolver getResourceResolver() {
+            return ScriptResourceResolver.this;
+        }
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
index 97c41ee..170bc32 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
@@ -27,6 +27,7 @@ import static 
org.apache.sling.api.servlets.ServletResolverConstants.DEFAULT_ERR
 import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.function.Supplier;
 
 import javax.servlet.Servlet;
 import javax.servlet.ServletContext;
@@ -59,7 +60,9 @@ import 
org.apache.sling.servlets.resolver.internal.helper.AbstractResourceCollec
 import 
org.apache.sling.servlets.resolver.internal.helper.NamedScriptResourceCollector;
 import org.apache.sling.servlets.resolver.internal.helper.ResourceCollector;
 import org.apache.sling.servlets.resolver.internal.resolution.ResolutionCache;
+import 
org.apache.sling.servlets.resolver.internal.resource.MergingServletResourceProvider;
 import org.apache.sling.servlets.resolver.internal.resource.SlingServletConfig;
+import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
@@ -67,6 +70,7 @@ import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Modified;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.util.tracker.ServiceTracker;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -117,6 +121,8 @@ public class SlingServletResolver
 
     private volatile ResourceResolver sharedScriptResolver;
 
+    private final ThreadLocal<ResourceResolver> perThreadScriptResolver = new 
ThreadLocal<>();
+
     /**
      * The allowed execution paths.
      */
@@ -141,7 +147,7 @@ public class SlingServletResolver
     // ---------- ServletResolver interface -----------------------------------
 
     /**
-     * @see 
org.apache.sling.api.servlets.ServletResolver#resolveServlet(org.apache.sling.api.SlingHttpServletRequest)
+     * @see ServletResolver#resolveServlet(SlingHttpServletRequest)
      */
     @Override
     public Servlet resolveServlet(final SlingHttpServletRequest request) {
@@ -192,7 +198,7 @@ public class SlingServletResolver
     }
 
     /**
-     * @see 
org.apache.sling.api.servlets.ServletResolver#resolveServlet(org.apache.sling.api.resource.Resource,
 java.lang.String)
+     * @see ServletResolver#resolveServlet(Resource, java.lang.String)
      */
     @Override
     public Servlet resolveServlet(final Resource resource, final String 
scriptName) {
@@ -219,7 +225,7 @@ public class SlingServletResolver
     }
 
     /**
-     * @see 
org.apache.sling.api.servlets.ServletResolver#resolveServlet(org.apache.sling.api.resource.ResourceResolver,
 java.lang.String)
+     * @see ServletResolver#resolveServlet(ResourceResolver, java.lang.String)
      */
     @Override
     public Servlet resolveServlet(final ResourceResolver resolver, final 
String scriptName) {
@@ -255,14 +261,14 @@ public class SlingServletResolver
         }
         // if resource is fetched using shared resource resolver
         // or resource is a servlet resource, just adapt to servlet
-        if ( scriptResource.getResourceResolver() == this.sharedScriptResolver
+        if (scriptResource.getResourceResolver() == this.sharedScriptResolver
              || 
"sling/bundle/resource".equals(scriptResource.getResourceSuperType()) ) {
             return scriptResource.adaptTo(Servlet.class);
         }
         // return a resource wrapper to make sure the implementation
         // switches from the per thread resource resolver to the shared once
         // the per thread resource resolver is closed
-        return new ScriptResource(scriptResource, perThreadScriptResolver, 
this.sharedScriptResolver).adaptTo(Servlet.class);
+        return new ScriptResource(scriptResource, 
perThreadScriptResolver::get, this.sharedScriptResolver).adaptTo(Servlet.class);
     }
 
     // ---------- ErrorHandler interface --------------------------------------
@@ -326,7 +332,7 @@ public class SlingServletResolver
     }
 
     /**
-     * @see 
org.apache.sling.engine.servlets.ErrorHandler#handleError(java.lang.Throwable, 
org.apache.sling.api.SlingHttpServletRequest, 
org.apache.sling.api.SlingHttpServletResponse)
+     * @see 
org.apache.sling.engine.servlets.ErrorHandler#handleError(java.lang.Throwable, 
SlingHttpServletRequest, SlingHttpServletResponse)
      */
     @Override
     public void handleError(final Throwable throwable, final 
SlingHttpServletRequest request, final SlingHttpServletResponse response)
@@ -392,10 +398,8 @@ public class SlingServletResolver
         return scriptResolver;
     }
 
-    private final ThreadLocal<ResourceResolver> perThreadScriptResolver = new 
ThreadLocal<>();
-
     /**
-     * @see 
org.apache.sling.api.request.SlingRequestListener#onEvent(org.apache.sling.api.request.SlingRequestEvent)
+     * @see SlingRequestListener#onEvent(SlingRequestEvent)
      */
     @Override
     public void onEvent(final SlingRequestEvent event) {
@@ -419,7 +423,7 @@ public class SlingServletResolver
      * error handling. If the resource has not yet been set in the request
      * because the error occurred before the resource could be set (e.g. during
      * resource resolution) a synthetic resource is returned whose type is
-     * {@link ServletResolverConstants#ERROR_HANDLER_PATH}.
+     * {@link ServletResolverConstants#DEFAULT_ERROR_HANDLER_RESOURCE_TYPE}.
      *
      * @param request The request whose resource is to be returned.
      */
@@ -653,13 +657,17 @@ public class SlingServletResolver
 
     // ---------- SCR Integration 
----------------------------------------------
 
+    private volatile ServiceTracker tracker;
     /**
      * Activate this component.
      */
     @Activate
-    protected void activate(final ResolverConfig config) throws LoginException 
{
+    protected void activate(final BundleContext context, final ResolverConfig 
config) throws LoginException {
+        this.tracker = new ServiceTracker(context, 
MergingServletResourceProvider.class, null);
+        this.tracker.open();
         this.sharedScriptResolver =
-                
resourceResolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE,
 (Object)SERVICE_USER));
+                
ScriptResourceResolver.wrap(resourceResolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE,
 (Object)SERVICE_USER)),
+                    (Supplier) this.tracker::getService);
 
         this.executionPaths = 
getExecutionPaths(config.servletresolver_paths());
         this.defaultExtensions = config.servletresolver_defaultExtensions();
@@ -669,9 +677,9 @@ public class SlingServletResolver
     }
 
     @Modified
-    protected void modified(final ResolverConfig config) throws LoginException 
{
+    protected void modified(final BundleContext context, final ResolverConfig 
config) throws LoginException {
         this.deactivate();
-        this.activate(config);
+        this.activate(context, config);
     }
 
     /**
@@ -679,6 +687,7 @@ public class SlingServletResolver
      */
     @Deactivate
     protected void deactivate() {
+        this.tracker.close();
         this.resolutionCache.flushCache();
         // destroy the fallback error handler servlet
         if (fallbackErrorServlet != null) {
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java
index 9c72f35..9384957 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/helper/AbstractResourceCollector.java
@@ -35,7 +35,7 @@ import org.apache.sling.api.resource.SyntheticResource;
 
 /**
  * The <code>ResourceCollector</code> class provides a single public method -
- * {@link #getServlets(ResourceResolver)} - which is used to find an ordered 
collection
+ * {@link #getServlets(ResourceResolver,List<String>)} - which is used to find 
an ordered collection
  * of <code>Resource</code> instances which may be used to find a servlet or
  * script to handle a request to the given resource.
  */
@@ -138,7 +138,6 @@ public abstract class AbstractResourceCollector {
      *            the name of the resource.
      * @param methodPrefixWeight The method/prefix weight assigned to the
      *            resource according to the resource name.
-     * @param scriptExtensionPriority The priority of the script engine used 
to run this script
      */
     protected final void addWeightedResource(final Set<WeightedResource> 
resources,
             final Resource resource,
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/MergingServletResourceProvider.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/MergingServletResourceProvider.java
new file mode 100644
index 0000000..230bef0
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/MergingServletResourceProvider.java
@@ -0,0 +1,163 @@
+/*
+ * 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.servlets.resolver.internal.resource;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.spi.resource.provider.ResolveContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+public class MergingServletResourceProvider {
+    private final List<Pair<ServletResourceProvider, ServiceReference<?>>> 
registrations = new ArrayList<>();
+
+    private final ConcurrentHashMap<String, Set<String>> tree = new 
ConcurrentHashMap<>();
+    private final ConcurrentHashMap<String, Pair<ServletResourceProvider, 
ServiceReference<?>>> providers = new ConcurrentHashMap<>();
+
+    synchronized void add(ServletResourceProvider provider, 
ServiceReference<?> reference) {
+        registrations.add(Pair.of(provider, reference));
+        index(Arrays.asList(registrations.get(registrations.size() - 1)));
+    }
+
+    synchronized void remove(ServletResourceProvider provider, 
ServiceReference<?> reference) {
+        for (Iterator<Pair<ServletResourceProvider, ServiceReference<?>>> 
regIter = registrations.iterator(); regIter.hasNext(); ) {
+            Pair<ServletResourceProvider, ServiceReference<?>> reg = 
regIter.next();
+            if (reg.getLeft() == provider) {
+                regIter.remove();
+            }
+            else {
+                Bundle bundle = reg.getRight().getBundle();
+                if (bundle == null || bundle.getState() == Bundle.STOPPING) {
+                    regIter.remove();
+                }
+            }
+        }
+        tree.clear();
+        providers.clear();
+        index(registrations);
+    }
+
+    synchronized void clear() {
+        registrations.clear();
+        tree.clear();
+        providers.clear();
+    }
+
+    private void index(List<Pair<ServletResourceProvider, 
ServiceReference<?>>> registrations) {
+        for (Pair<ServletResourceProvider, ServiceReference<?>> reference : 
registrations) {
+            for (String path : reference.getLeft().getServletPaths()) {
+                String current = "";
+                for (String part : path.split("/")) {
+                    Set<String> childs = tree.get(current);
+                    if (childs == null) {
+                        childs = Collections.synchronizedSet(new 
LinkedHashSet<>());
+                        tree.put(current, childs);
+                    }
+                    current += "/" + part;
+                    current = current.trim().replace("//", "/");
+
+                    childs.add(current);
+                }
+
+                Pair<ServletResourceProvider, ServiceReference<?>> old = 
providers.put(path, reference);
+                if (old != null) {
+                    if (reference.getRight().compareTo(old.getRight()) < 0) {
+                        providers.put(path, old);
+                    }
+                }
+            }
+        }
+    }
+
+    public Resource getResource(ResolveContext resolveContext, String path) {
+        Resource wrapped = null;
+        final ResourceProvider parentProvider = 
resolveContext.getParentResourceProvider();
+        if (parentProvider != null) {
+            wrapped = 
parentProvider.getResource(resolveContext.getParentResolveContext(), path, 
null, null);
+        }
+        Resource result;
+        Pair<ServletResourceProvider, ServiceReference<?>> provider = 
providers.get(path);
+
+        if (provider != null) {
+            result = provider.getLeft().getResource(resolveContext, path, 
null, null);
+            if (result instanceof ServletResource) {
+                ((ServletResource) result).setWrappedResource(wrapped);
+            }
+        }
+        else {
+            if (wrapped != null) {
+                result = wrapped;
+            }
+            else {
+                result = null;
+            }
+            if (result == null && tree.containsKey(path)) {
+                result = new 
SyntheticResource(resolveContext.getResourceResolver(), path, 
ResourceProvider.RESOURCE_TYPE_SYNTHETIC);
+            }
+        }
+
+        return result;
+    }
+
+    public Iterator<Resource> listChildren(final ResolveContext ctx, final 
Resource parent) {
+        Map<String, Resource> result = new LinkedHashMap<>();
+
+        final ResourceProvider parentProvider = 
ctx.getParentResourceProvider();
+        if (parentProvider != null) {
+            for (Iterator<Resource> iter = 
parentProvider.listChildren(ctx.getParentResolveContext(), parent); iter != 
null && iter.hasNext(); ) {
+                Resource resource = iter.next();
+                result.put(resource.getPath(), resource);
+            }
+        }
+        Set<String> paths = tree.get(parent.getPath());
+
+        if (paths != null) {
+            for (String path : paths) {
+                Pair<ServletResourceProvider, ServiceReference<?>> provider = 
providers.get(path);
+
+                if (provider != null) {
+                    Resource resource = provider.getLeft().getResource(ctx, 
path, null, parent);
+                    if (resource != null) {
+                        Resource wrapped = result.put(path, resource);
+                        if (resource instanceof ServletResource) {
+                            ((ServletResource) 
resource).setWrappedResource(wrapped);
+                        }
+                    }
+                }
+                else if (!result.containsKey(path)) {
+                    result.put(path, new 
SyntheticResource(ctx.getResourceResolver(), path, 
ResourceProvider.RESOURCE_TYPE_SYNTHETIC));
+                }
+            }
+        }
+        return result.values().iterator();
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java
index 6c7d48a..1eb5866 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletMounter.java
@@ -30,6 +30,7 @@ import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.servlet.Servlet;
 import javax.servlet.ServletContext;
@@ -40,6 +41,7 @@ import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.servlets.ServletResolver;
 import org.apache.sling.api.servlets.ServletResolverConstants;
 import org.apache.sling.servlets.resolver.internal.ResolverConfig;
+import org.apache.sling.servlets.resolver.internal.resolution.ResolutionCache;
 import org.apache.sling.spi.resource.provider.ResourceProvider;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
@@ -71,6 +73,8 @@ public class ServletMounter {
 
     private static final String REF_SERVLET = "Servlet";
 
+    private static final String REF_CACHE = "Cache";
+
     private final ServletContext servletContext;
 
     private final Map<ServiceReference<Servlet>, ServletReg> 
servletsByReference = new HashMap<>();
@@ -79,6 +83,12 @@ public class ServletMounter {
 
     private final ServletResourceProviderFactory 
servletResourceProviderFactory;
 
+    private final MergingServletResourceProvider provider;
+
+    private final ServiceRegistration<MergingServletResourceProvider> 
providerReg;
+
+    private final ConcurrentHashMap<ResolutionCache, ResolutionCache> 
resolutionCaches = new ConcurrentHashMap<>();
+
     /**
      * Activate this component.
      */
@@ -89,6 +99,15 @@ public class ServletMounter {
         this.servletContext = servletContext;
         servletResourceProviderFactory = new 
ServletResourceProviderFactory(config.servletresolver_servletRoot(),
                 resourceResolverFactory.getSearchPath());
+
+        if (!config.servletresolver_mountProviders()) {
+            provider = new MergingServletResourceProvider();
+            providerReg = 
context.registerService(MergingServletResourceProvider.class, provider, null);
+        }
+        else {
+            provider = null;
+            providerReg = null;
+        }
     }
 
     /**
@@ -103,9 +122,16 @@ public class ServletMounter {
         synchronized (this.servletsByReference) {
             refs = new ArrayList<>(servletsByReference.keySet());
         }
+        if (provider != null) {
+            provider.clear();
+        }
         // destroy all servlets
         destroyAllServlets(refs);
 
+        if (providerReg != null) {
+            providerReg.unregister();
+        }
+
         // sanity check: clear array (it should be empty now anyway)
         synchronized ( this.servletsByReference ) {
             this.servletsByReference.clear();
@@ -128,6 +154,21 @@ public class ServletMounter {
         destroyServlet(reference);
     }
 
+    @Reference(
+        name = REF_CACHE,
+        service = ResolutionCache.class,
+        cardinality = ReferenceCardinality.MULTIPLE,
+        policy = ReferencePolicy.DYNAMIC
+    )
+    protected void bindResolutionCache(ResolutionCache cache) {
+        cache.flushCache();
+        resolutionCaches.put(cache, cache);
+    }
+
+    protected void unbindResolutionCache(ResolutionCache cache) {
+        resolutionCaches.remove(cache);
+    }
+
     private boolean createServlet(final Servlet servlet, final 
ServiceReference<Servlet> reference) {
         // check for a name, this is required
         final String name = getName(reference);
@@ -158,13 +199,18 @@ public class ServletMounter {
             if ( bundleContext != null ) {
                 final List<ServiceRegistration<ResourceProvider<Object>>> regs 
= new ArrayList<>();
                 try {
-                    for(final String root : provider.getServletPaths()) {
-                        @SuppressWarnings("unchecked")
-                        final ServiceRegistration<ResourceProvider<Object>> 
reg = (ServiceRegistration<ResourceProvider<Object>>) 
bundleContext.registerService(
-                            ResourceProvider.class.getName(),
-                            provider,
-                            createServiceProperties(reference, root));
-                        regs.add(reg);
+                    if (this.provider != null) {
+                        this.provider.add(provider, reference);
+                        
resolutionCaches.values().forEach(ResolutionCache::flushCache);
+                    }
+                    else {
+                        for (final String root : provider.getServletPaths()) {
+                            @SuppressWarnings("unchecked") final 
ServiceRegistration<ResourceProvider<Object>> reg = 
(ServiceRegistration<ResourceProvider<Object>>) bundleContext.registerService(
+                                ResourceProvider.class.getName(),
+                                provider,
+                                createServiceProperties(reference, root));
+                            regs.add(reg);
+                        }
                     }
                     registered = true;
                 } catch ( final IllegalStateException ise ) {
@@ -175,7 +221,7 @@ public class ServletMounter {
                         logger.debug("Registered {}", provider);
                     }
                     synchronized (this.servletsByReference) {
-                        servletsByReference.put(reference, new 
ServletReg(servlet, regs));
+                        servletsByReference.put(reference, new 
ServletReg(servlet, regs, provider));
                     }
                 }
             }
@@ -224,6 +270,9 @@ public class ServletMounter {
                     // this might happen on shutdown
                 }
             }
+            if (registration.provider != null && provider != null) {
+                provider.remove(registration.provider, reference);
+            }
             final String name = 
RequestUtil.getServletName(registration.servlet);
             logger.debug("unbindServlet: Servlet {} removed", name);
 
@@ -257,13 +306,15 @@ public class ServletMounter {
         return servletName;
     }
 
-    private static final class ServletReg {
+    static final class ServletReg {
         public final Servlet servlet;
         public final List<ServiceRegistration<ResourceProvider<Object>>> 
registrations;
+        private final ServletResourceProvider provider;
 
-        public ServletReg(final Servlet s, final 
List<ServiceRegistration<ResourceProvider<Object>>> srs) {
+        public ServletReg(final Servlet s, final 
List<ServiceRegistration<ResourceProvider<Object>>> srs, final 
ServletResourceProvider provider) {
             this.servlet = s;
             this.registrations = srs;
+            this.provider = provider;
         }
     }
 }
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResource.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResource.java
index 3f3f7c0..90a97e0 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResource.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResource.java
@@ -25,12 +25,13 @@ import javax.servlet.Servlet;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.resource.AbstractResource;
+import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceMetadata;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.api.wrappers.ValueMapDecorator;
 
-class ServletResource extends AbstractResource {
+public class ServletResource extends AbstractResource {
 
     private final ResourceResolver resourceResolver;
 
@@ -43,6 +44,8 @@ class ServletResource extends AbstractResource {
 
     private final ResourceMetadata metadata;
 
+    private volatile Resource wrapped;
+
     public ServletResource(ResourceResolver resourceResolver, Servlet servlet, 
String path) {
         this(resourceResolver, servlet, path, null);
     }
@@ -59,6 +62,10 @@ class ServletResource extends AbstractResource {
         this.metadata = new ResourceMetadata();
     }
 
+    void setWrappedResource(Resource wrapped) {
+        this.wrapped = wrapped;
+    }
+
     @Override
     public ResourceMetadata getResourceMetadata() {
         return metadata;
@@ -105,9 +112,14 @@ class ServletResource extends AbstractResource {
     @Override
     @SuppressWarnings("unchecked")
     public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        Resource wrapped = this.wrapped;
         if (type == Servlet.class && servlet != null) {
             return (AdapterType) servlet; // unchecked cast
-        } else if ( type == ValueMap.class ) {
+        }
+        else if (wrapped != null) {
+            return wrapped.adaptTo(type);
+        }
+        else if ( type == ValueMap.class ) {
             final Map<String, Object> props = new HashMap<>();
             props.put("sling:resourceType", this.getResourceType());
             props.put("sling:resourceSuperType", this.getResourceSuperType());
@@ -127,5 +139,4 @@ class ServletResource extends AbstractResource {
         return getClass().getSimpleName() + ", servlet=" + 
this.getServletName()
             + ", path=" + getPath();
     }
-
 }
diff --git 
a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResourceProviderFactory.java
 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResourceProviderFactory.java
index e4f6995..7e61e31 100644
--- 
a/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResourceProviderFactory.java
+++ 
b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/ServletResourceProviderFactory.java
@@ -34,6 +34,7 @@ import java.util.Set;
 
 import javax.servlet.Servlet;
 
+import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.servlets.HttpConstants;
@@ -220,8 +221,11 @@ public class ServletResourceProviderFactory {
                 // add the unmodified path
                 pathSet.add(path);
 
-                // ensure we have another entry which has the .servlet ext.
-                pathSet.add(ensureServletNameExtension(path));
+                if (StringUtils.isEmpty(FilenameUtils.getExtension(path))) {
+                    // ensure we have another entry which has the .servlet 
ext. if there wasn't one to begin with
+                    // Radu says: this will make sure that scripts are equal 
to servlets in the resolution process
+                    pathSet.add(ensureServletNameExtension(path));
+                }
             }
         }
     }
diff --git 
a/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTestBase.java
 
b/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTestBase.java
index 8e150fe..5ec3674 100644
--- 
a/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTestBase.java
+++ 
b/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTestBase.java
@@ -125,7 +125,7 @@ public abstract class SlingServletResolverTestBase {
         Mockito.when(bundle.getBundleContext()).thenReturn(bundleContext);
 
         defineTestServlets(bundle);
-        servletResolver.activate(config);
+        servletResolver.activate(bundleContext, config);
 
     }
 

Reply via email to