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:47 UTC

[sling-org-apache-sling-servlets-resolver] branch issues/SLING-9365 created (now 243c7c2)

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

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


      at 243c7c2  SLING-9365: add merging resource provider for servlets.

This branch includes the following new commits:

     new 243c7c2  SLING-9365: add merging resource provider for servlets.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

Posted by pa...@apache.org.
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;
+    }
+
 }