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;
+ }
+
}