You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by pa...@apache.org on 2020/04/13 21:17:48 UTC

[sling-org-apache-sling-servlets-resolver] 01/01: SLING-9365: add merging resource provider for servlets.

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

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

commit 243c7c20635af87b53913d53b06f481adbefbc71
Author: Karl Pauls <ka...@gmail.com>
AuthorDate: Mon Apr 13 23:17:25 2020 +0200

    SLING-9365: add merging resource provider for servlets.
---
 .../servlets/resolver/internal/ResolverConfig.java |   4 +
 .../resource/MergingServletResourceProvider.java   | 181 +++++++++++++++++++++
 .../resolver/internal/resource/ServletMounter.java |  97 +++++++++--
 .../internal/resource/ServletResource.java         |  14 +-
 4 files changed, 285 insertions(+), 11 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..100c3d0 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,8 @@ 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 = "Merge Mounts", description = "Should a dispatching servlet be mounted at the highest level of the tree. If true,"
+        + " a resource provider will be merged at the top level that dispatches internally instead of serveral resource provider per servlet. ")
+    boolean servletresolver_mergeMounts() default true;
 }
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..493cc8a
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/MergingServletResourceProvider.java
@@ -0,0 +1,181 @@
+/*
+ * 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.PersistenceException;
+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.ResourceContext;
+import org.apache.sling.spi.resource.provider.ResourceProvider;
+import org.osgi.framework.ServiceReference;
+
+public class MergingServletResourceProvider extends ResourceProvider<Void> {
+    private final List<Pair<ServletResourceProvider, ServiceReference<?>>> m_registrations = new ArrayList<>();
+
+    private final ConcurrentHashMap<String, Set<String>> m_tree = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<String, Pair<ServletResourceProvider, ServiceReference<?>>> m_providers = new ConcurrentHashMap<>();
+
+    synchronized Set<String> add(ServletResourceProvider provider, ServiceReference<?> reference) {
+        m_registrations.add(Pair.of(provider, reference));
+        return index(Arrays.asList(m_registrations.get(m_registrations.size() - 1)));
+    }
+
+    synchronized Set<String> remove(ServletResourceProvider provider) {
+        for (Iterator<Pair<ServletResourceProvider, ServiceReference<?>>> regIter = m_registrations.iterator(); regIter.hasNext(); ) {
+            if (regIter.next().getLeft() == provider) {
+                regIter.remove();
+                break;
+            }
+        }
+        m_tree.clear();
+        m_providers.clear();
+        return index(m_registrations);
+    }
+
+    private Set<String> index(List<Pair<ServletResourceProvider, ServiceReference<?>>> registrations) {
+        Set<String> mounts = new LinkedHashSet<>();
+
+        for (Pair<ServletResourceProvider, ServiceReference<?>> reference : registrations) {
+            for (String path : reference.getLeft().getServletPaths()) {
+                String current = "";
+                int count = 0;
+                for (String part : path.split("/")) {
+                    Set<String> childs = m_tree.get(current);
+                    if (childs == null) {
+                        childs = Collections.synchronizedSet(new LinkedHashSet<>());
+                        m_tree.put(current, childs);
+                    }
+                    current += "/" + part;
+                    current = current.trim().replace("//", "/");
+
+                    childs.add(current);
+
+                    if (count++ == 1)
+                    {
+                        mounts.add(current);
+                    }
+                }
+
+                Pair<ServletResourceProvider, ServiceReference<?>> old = m_providers.put(path, reference);
+                if (old != null) {
+                    if (reference.getRight().compareTo(old.getRight()) < 0) {
+                        m_providers.put(path, old);
+                    }
+                }
+            }
+        }
+        return mounts;
+    }
+
+    @Override
+    public Resource getResource(ResolveContext resolveContext, String path, ResourceContext resourceContext, Resource parent) {
+        Resource fromParent = null;
+        final ResourceProvider parentProvider = resolveContext.getParentResourceProvider();
+        if (parentProvider != null) {
+            fromParent = parentProvider.getResource(resolveContext.getParentResolveContext(), path, resourceContext, parent);
+        }
+        Resource result;
+        Pair<ServletResourceProvider, ServiceReference<?>> provider = m_providers.get(path);
+
+        if (provider != null) {
+            result = provider.getLeft().getResource(resolveContext, path, resourceContext, parent);
+            if (result instanceof ServletResource) {
+                ((ServletResource) result).setFromParent(fromParent);
+            }
+        }
+        else {
+            if (fromParent != null) {
+                result = fromParent;
+            }
+            else {
+                result = null;
+            }
+            if (result == null && m_tree.containsKey(path)) {
+                result = new SyntheticResource(resolveContext.getResourceResolver(), path, ResourceProvider.RESOURCE_TYPE_SYNTHETIC);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    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 = m_tree.get(parent.getPath());
+
+        if (paths != null) {
+            for (String path : paths) {
+                Pair<ServletResourceProvider, ServiceReference<?>> provider = m_providers.get(path);
+
+                if (provider != null) {
+                    Resource resource = provider.getLeft().getResource(ctx, path, null, parent);
+                    if (resource != null) {
+                        Resource fromParent = result.put(path, resource);
+                        if (resource instanceof ServletResource) {
+                            ((ServletResource) resource).setFromParent(fromParent);
+                        }
+                    }
+                }
+                else if (!result.containsKey(path)) {
+                    result.put(path, new SyntheticResource(ctx.getResourceResolver(), path, ResourceProvider.RESOURCE_TYPE_SYNTHETIC));
+                }
+            }
+        }
+        return result.values().iterator();
+    }
+
+    @Override
+    public Resource create(ResolveContext ctx, String path, Map properties) throws PersistenceException
+    {
+        return ctx.getParentResourceProvider().create(ctx.getParentResolveContext(), path, properties);
+    }
+
+    @Override
+    public void delete(ResolveContext ctx, Resource resource) throws PersistenceException
+    {
+        ctx.getParentResourceProvider().delete(ctx.getParentResolveContext(), resource);
+    }
+
+    @Override
+    public <AdapterType> AdapterType adaptTo(ResolveContext<Void> ctx, Class<AdapterType> type)
+    {
+        return super.adaptTo(ctx, type);
+    }
+}
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..fc3341e 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
@@ -25,11 +25,16 @@ import static org.osgi.service.component.ComponentConstants.COMPONENT_NAME;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Dictionary;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.servlet.Servlet;
 import javax.servlet.ServletContext;
@@ -40,6 +45,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 +77,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 +87,14 @@ public class ServletMounter {
 
     private final ServletResourceProviderFactory servletResourceProviderFactory;
 
+    private final MergingServletResourceProvider m_provider;
+    private final Set<ServiceRegistration<ResourceProvider>> m_providerRegs;
+
+    private final BundleContext m_context;
+
+    private final Set<ResolutionCache> resolutionCaches = Collections.synchronizedSet(new HashSet<>());
+
+    private final ConcurrentHashMap<ResolutionCache, ResolutionCache> m_resolutionCaches = new ConcurrentHashMap<>();
     /**
      * Activate this component.
      */
@@ -86,9 +102,19 @@ public class ServletMounter {
     public ServletMounter(final BundleContext context, @Reference final ResourceResolverFactory resourceResolverFactory,
             @Reference(target = "(name=org.apache.sling)") ServletContext servletContext,
             final ResolverConfig config) {
+        m_context = context;
         this.servletContext = servletContext;
         servletResourceProviderFactory = new ServletResourceProviderFactory(config.servletresolver_servletRoot(),
                 resourceResolverFactory.getSearchPath());
+
+        if (config.servletresolver_mergeMounts()) {
+            m_provider = new MergingServletResourceProvider();
+            m_providerRegs = Collections.synchronizedSet(new HashSet<>());
+        }
+        else {
+            m_provider = null;
+            m_providerRegs = null;
+        }
     }
 
     /**
@@ -97,6 +123,9 @@ public class ServletMounter {
     @Deactivate
     protected void deactivate() {
         this.active = false;
+        for (ServiceRegistration<?> reg : m_providerRegs) {
+            reg.unregister();
+        }
         // Copy the list of servlets first, to minimize the need for
         // synchronization
         final Collection<ServiceReference<Servlet>> refs;
@@ -107,6 +136,12 @@ public class ServletMounter {
         destroyAllServlets(refs);
 
         // sanity check: clear array (it should be empty now anyway)
+        if (m_provider != null) {
+            m_providerRegs.stream().forEach(ServiceRegistration::unregister);
+            m_providerRegs.clear();
+        }
+
+        // sanity check: clear array (it should be empty now anyway)
         synchronized ( this.servletsByReference ) {
             this.servletsByReference.clear();
         }
@@ -128,6 +163,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();
+        m_resolutionCaches.put(cache, cache);
+    }
+
+    protected void unbindResolutionCache(ResolutionCache cache) {
+        m_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 +208,28 @@ 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 (m_provider != null) {
+                        for (String mount : m_provider.add(provider, reference)) {
+                            if (m_providerRegs.stream().noneMatch(reg -> mount.equals(reg.getReference().getProperty(ResourceProvider.PROPERTY_ROOT)))) {
+                                final Dictionary<String, Object> params = new Hashtable<>();
+                                params.put(ResourceProvider.PROPERTY_ROOT, mount);
+                                params.put(Constants.SERVICE_DESCRIPTION,
+                                    "ServletResourceProvider for Servlets at " + mount);
+                                params.put(ResourceProvider.PROPERTY_AUTHENTICATE, ResourceProvider.AUTHENTICATE_REQUIRED);
+                                params.put(ResourceProvider.PROPERTY_MODIFIABLE, true);
+                                m_providerRegs.add(m_context.registerService(ResourceProvider.class, m_provider, (Dictionary) params));
+                            }
+                        }
+                        m_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 +240,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 +289,16 @@ public class ServletMounter {
                     // this might happen on shutdown
                 }
             }
+            if (registration.provider != null && m_provider != null) {
+                Set<String> mounts = m_provider.remove(registration.provider);
+                for (Iterator<ServiceRegistration<ResourceProvider>> registrationIterator = m_providerRegs.iterator();((Iterator) registrationIterator).hasNext();) {
+                    ServiceRegistration<ResourceProvider> reg = registrationIterator.next();
+                    if (!mounts.contains(reg.getReference().getProperty(ResourceProvider.PROPERTY_ROOT))) {
+                        reg.unregister();
+                        registrationIterator.remove();
+                    }
+                }
+            }
             final String name = RequestUtil.getServletName(registration.servlet);
             logger.debug("unbindServlet: Servlet {} removed", name);
 
@@ -257,13 +332,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..9ee7266 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,6 +25,7 @@ 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;
@@ -43,6 +44,8 @@ class ServletResource extends AbstractResource {
 
     private final ResourceMetadata metadata;
 
+    private volatile Resource parent;
+
     public ServletResource(ResourceResolver resourceResolver, Servlet servlet, String path) {
         this(resourceResolver, servlet, path, null);
     }
@@ -105,9 +108,14 @@ class ServletResource extends AbstractResource {
     @Override
     @SuppressWarnings("unchecked")
     public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        Resource fromParent = parent;
         if (type == Servlet.class && servlet != null) {
             return (AdapterType) servlet; // unchecked cast
-        } else if ( type == ValueMap.class ) {
+        }
+        else if (fromParent != null) {
+            return fromParent.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());
@@ -128,4 +136,8 @@ class ServletResource extends AbstractResource {
             + ", path=" + getPath();
     }
 
+    void setFromParent(Resource fromParent) {
+        this.parent = fromParent;
+    }
+
 }