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 4085489b9b861abb7d5defa1c6c45f6eace8a940 Author: Karl Pauls <[email protected]> AuthorDate: Mon Apr 13 23:17:25 2020 +0200 SLING-9365: add merging resource provider for servlets. --- .../servlets/resolver/internal/ResolverConfig.java | 5 + .../servlets/resolver/internal/ScriptResource.java | 14 +- .../resolver/internal/ScriptResourceDecorator.java | 98 ++++++++++++ .../resolver/internal/ScriptResourceResolver.java | 174 +++++++++++++++++++++ .../resolver/internal/SlingServletResolver.java | 37 +++-- .../internal/helper/AbstractResourceCollector.java | 3 +- .../resource/MergingServletResourceProvider.java | 166 ++++++++++++++++++++ .../resolver/internal/resource/ServletMounter.java | 71 +++++++-- .../internal/resource/ServletResource.java | 17 +- .../resource/ServletResourceProviderFactory.java | 8 +- .../internal/SlingServletResolverTestBase.java | 2 +- 11 files changed, 558 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..7d5b9d5 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 Internal", description = "Should servlets be mounted internally and not as a resource provider. If true," + + " an internal resource provider will be used for servlet resolving and the servlets be decorated back into the resource tree instead of " + + "serveral resource provider per servlet. ") + boolean servletresolver_mountInternal() default false; } 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..fb4c9bb --- /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, null, null); + } +} 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..84be911 --- /dev/null +++ b/src/main/java/org/apache/sling/servlets/resolver/internal/ScriptResourceResolver.java @@ -0,0 +1,174 @@ +/* + * 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.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<ResourceProvider> provider; + + public ScriptResourceResolver(ResourceResolver resolver, Supplier<ResourceProvider> provider) { + super(resolver); + this.resolver = resolver; + this.provider = provider; + } + + public static ScriptResourceResolver wrap(ResourceResolver scriptResourceResolver, Supplier<ResourceProvider> provider) { + return new ScriptResourceResolver(scriptResourceResolver, provider); + } + + @Override + public Iterable<Resource> getChildren(Resource parent) { + return () -> listChildren(parent); + } + + public Resource getResource(String scriptPath) { + ResourceProvider 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, null, null)); + } + } + + @Override + public Iterator<Resource> listChildren(Resource parent) { + ResourceProvider 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..82f6249 --- /dev/null +++ b/src/main/java/org/apache/sling/servlets/resolver/internal/resource/MergingServletResourceProvider.java @@ -0,0 +1,166 @@ +/* + * 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.ResourceContext; +import org.apache.sling.spi.resource.provider.ResourceProvider; +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceReference; + +public class MergingServletResourceProvider extends ResourceProvider<Void> { + 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); + } + } + } + } + } + + @Override + public Resource getResource(ResolveContext resolveContext, String path, ResourceContext resourceContext, Resource parent) { + Resource wrapped = null; + final ResourceProvider parentProvider = resolveContext.getParentResourceProvider(); + if (parentProvider != null) { + wrapped = parentProvider.getResource(resolveContext.getParentResolveContext(), path, resourceContext, parent); + } + Resource result; + Pair<ServletResourceProvider, ServiceReference<?>> provider = providers.get(path); + + if (provider != null) { + result = provider.getLeft().getResource(resolveContext, path, resourceContext, parent); + 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; + } + + @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 = 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..71606ef 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_mountInternal()) { + 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); }
