| /* |
| * 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.helper; |
| |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.Set; |
| |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.request.RequestPathInfo; |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceResolver; |
| import org.apache.sling.api.servlets.ServletResolverConstants; |
| import org.apache.sling.servlets.resolver.internal.SlingServletResolver; |
| import org.apache.sling.servlets.resolver.internal.resource.ServletResourceProviderFactory; |
| |
| /** |
| * The <code>ResourceCollector</code> class provides a single public method - |
| * {@link #getServlets(ResourceResolver)} - 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. |
| */ |
| public class ResourceCollector extends AbstractResourceCollector { |
| |
| /** |
| * The special value returned by |
| * {@link #calculatePrefixMethodWeight(Resource, String, boolean)} if the |
| * resource is not suitable to handle the request according to the location |
| * prefix, request selectors and request extension (value is |
| * <code>Integer.MIN_VALUE</code>). |
| */ |
| protected static final int WEIGHT_NO_MATCH = Integer.MIN_VALUE; |
| |
| // the request method name used to indicate the script name |
| private final String methodName; |
| |
| // the request selectors as a string converted to a realtive path or |
| // null if the request has no selectors |
| private final String[] requestSelectors; |
| |
| // the number of request selectors of the request or 0 if none |
| private final int numRequestSelectors; |
| |
| // request is GET or HEAD |
| private final boolean isGet; |
| |
| // request is GET or HEAD and extension is html |
| private final boolean isDefaultExtension; |
| |
| private final String suffExt; |
| |
| private final String suffMethod; |
| |
| private final String suffExtMethod; |
| |
| /** |
| * Creates a <code>ResourceCollector</code> for the given |
| * <code>request</code>. If the request is a GET or HEAD request, a |
| * specialized instance is returned which also takes the request selectors |
| * and request extension into account for finding servlet resources. |
| * Otherwise an instance of this class itself is returned which just takes |
| * the resource type and request method name into account. |
| * |
| * @param request The <code>SlingHttpServletRequest</code> for which to |
| * return a <code>ResourceCollector</code>. |
| * @return The <code>ResourceCollector</code> to find servlets and scripts |
| * suitable for handling the <code>request</code>. |
| */ |
| public static ResourceCollector create( |
| final SlingHttpServletRequest request, |
| final String[] executionPaths, final String[] defaultExtensions) { |
| final RequestPathInfo requestPathInfo = request.getRequestPathInfo(); |
| final boolean isDefaultExtension = ArrayUtils.contains(defaultExtensions, requestPathInfo.getExtension()); |
| return new ResourceCollector(request.getResource(), requestPathInfo.getExtension(), executionPaths, isDefaultExtension, |
| request.getMethod(), requestPathInfo.getSelectors()); |
| } |
| |
| public static ResourceCollector create(final Resource resource, |
| final String extension, |
| final String[] executionPaths, final String[] defaultExtensions, |
| final String methodName, final String[] selectors |
| ) { |
| boolean isDefaultExtension = ArrayUtils.contains(defaultExtensions, extension); |
| return new ResourceCollector(resource, extension, executionPaths, isDefaultExtension, methodName, selectors); |
| } |
| |
| /** |
| * Creates a <code>ResourceCollector</code> finding servlets and scripts for |
| * the given <code>methodName</code>. |
| * |
| * @param methodName The <code>methodName</code> used to find scripts for. |
| * This must not be <code>null</code>. |
| * @param baseResourceType The basic resource type to use as a final |
| * resource super type. If this is <code>null</code> the default |
| * value |
| * {@link org.apache.sling.servlets.resolver.internal.ServletResolverConstants#DEFAULT_SERVLET_NAME} |
| * is assumed. |
| * @param resource the resource to invoke, the resource type and resource |
| * super type are taken from this resource. |
| * @param executionPaths the execution paths to consider |
| * @deprecated use {@link #ResourceCollector(String, String, Resource, String, String[])} instead. |
| */ |
| @Deprecated |
| public ResourceCollector(final String methodName, |
| final String baseResourceType, final Resource resource, |
| final String[] executionPaths) { |
| this(methodName, baseResourceType, resource, null, executionPaths); |
| } |
| |
| /** |
| * Creates a <code>ResourceCollector</code> finding servlets and scripts for |
| * the given <code>methodName</code>. |
| * |
| * @param methodName The <code>methodName</code> used to find scripts for. |
| * This must not be <code>null</code>. |
| * @param baseResourceType The basic resource type to use as a final |
| * resource super type. If this is <code>null</code> the default |
| * value |
| * {@link org.apache.sling.servlets.resolver.internal.ServletResolverConstants#DEFAULT_SERVLET_NAME} |
| * is assumed. |
| * @param resource the resource to invoke, the resource type and resource |
| * super type are taken from this resource. |
| * @param extension the extension of the request being processed |
| * @param executionPaths the execution paths to consider |
| */ |
| public ResourceCollector(final String methodName, |
| final String baseResourceType, final Resource resource, |
| final String extension, |
| final String[] executionPaths) { |
| super((baseResourceType != null |
| ? baseResourceType |
| : ServletResolverConstants.DEFAULT_RESOURCE_TYPE), |
| resource.getResourceType(), resource.getResourceSuperType(), |
| extension, executionPaths); |
| this.methodName = methodName; |
| this.requestSelectors = new String[0]; |
| this.numRequestSelectors = 0; |
| this.isGet = false; |
| this.isDefaultExtension = false; |
| |
| this.suffExt = "." + extension; |
| this.suffMethod = "." + methodName; |
| this.suffExtMethod = suffExt + suffMethod; |
| |
| // create the hash code once |
| final String key = methodName + ':' + baseResourceType + ':' |
| + extension + "::" |
| + (this.resourceType == null ? "" : this.resourceType) + ':' |
| + (this.resourceSuperType == null ? "" : this.resourceSuperType); |
| this.hashCode = key.hashCode(); |
| } |
| |
| /** |
| * Creates a <code>ResourceCollector</code> finding servlets and scripts for |
| * the given <code>resource</code>. |
| * |
| * @param methodName The <code>methodName</code> used to find scripts for. |
| * This must not be <code>null</code>. |
| * @param baseResourceType The basic resource type to use as a final |
| * resource super type. If this is <code>null</code> the default |
| * value |
| * {@link org.apache.sling.servlets.resolver.internal.ServletResolverConstants#DEFAULT_SERVLET_NAME} |
| * is assumed. |
| */ |
| private ResourceCollector(final Resource resource, |
| final String extension, |
| final String[] executionPaths, |
| final boolean isDefaultExtension, |
| final String methodName, |
| final String[] selectors) { |
| super(ServletResolverConstants.DEFAULT_RESOURCE_TYPE, |
| resource.getResourceType(), |
| resource.getResourceSuperType(), |
| extension, executionPaths); |
| this.methodName = methodName; |
| |
| this.suffExt = "." + extension; |
| this.suffMethod = "." + methodName; |
| this.suffExtMethod = suffExt + suffMethod; |
| |
| this.requestSelectors = selectors; |
| this.numRequestSelectors = requestSelectors.length; |
| |
| this.isGet = "GET".equals(methodName) || "HEAD".equals(methodName); |
| this.isDefaultExtension = isDefaultExtension; |
| |
| // create the hash code once |
| final String key = methodName + ':' + baseResourceType + ':' |
| + extension + ':' + StringUtils.join(requestSelectors, '.') + ':' |
| + (this.resourceType == null ? "" : this.resourceType) + ':' |
| + (this.resourceSuperType == null ? "" : this.resourceSuperType); |
| this.hashCode = key.hashCode(); |
| } |
| |
| @Override |
| protected void getWeightedResources(final Set<WeightedResource> resources, |
| final Resource location) { |
| |
| final ResourceResolver resolver = location.getResourceResolver(); |
| Resource current = location; |
| String parentName = current.getName(); |
| |
| int selIdx = 0; |
| String selector; |
| do { |
| selector = (selIdx < numRequestSelectors) |
| ? requestSelectors[selIdx] |
| : null; |
| |
| Iterator<Resource> children = resolver.listChildren(current); |
| while (children.hasNext()) { // NOSONAR |
| Resource child = children.next(); |
| |
| if (!SlingServletResolver.isPathAllowed(child.getPath(), this.executionPaths)) { |
| continue; |
| } |
| String scriptName = child.getName(); |
| int lastDot = scriptName.lastIndexOf('.'); |
| if (lastDot < 0) { |
| // no extension in the name, this is not a script |
| continue; |
| } |
| |
| scriptName = scriptName.substring(0, lastDot); |
| |
| if (isGet |
| && checkScriptName(scriptName, selector, parentName, |
| suffExt, null, resources, child, selIdx)) { |
| continue; |
| } |
| |
| if (checkScriptName(scriptName, selector, parentName, |
| suffExtMethod, suffMethod, resources, child, selIdx)) { |
| continue; |
| } |
| |
| // SLING-754: Not technically really correct because |
| // the request extension is only optional in the script |
| // name for HTML methods, but we keep this for backwards |
| // compatibility. |
| if (selector != null |
| && matches(scriptName, selector, suffMethod)) { |
| addWeightedResource(resources, child, selIdx + 1, |
| WeightedResource.WEIGHT_NONE); |
| continue; |
| } |
| |
| if (scriptName.equals(methodName)) { |
| addWeightedResource(resources, child, selIdx, |
| WeightedResource.WEIGHT_NONE); |
| } |
| } |
| |
| if (selector != null) { |
| current = resolver.getResource(current, selector); |
| parentName = selector; |
| selIdx++; |
| } |
| } while (selector != null && current != null); |
| |
| // special treatment for servlets registered with neither a method |
| // name nor extensions and selectors |
| addLocationServlet(resources, location); |
| } |
| |
| /** |
| * Checks whether the <code>scriptName</code> matches a certain number of |
| * combinations of <code>selector</code>, <code>parentName</code>, |
| * <code>suffix</code> and <code>htmlSuffix</code>. If a match is found the |
| * {@link #addWeightedResource(Set, Resource, int, int)} method is called to |
| * register the found script resource with appropriate selection weight. |
| * |
| * @param scriptName The name of the script (without the script extension) |
| * to check for compliance. |
| * @param selector The current selector to check for in the script name; may |
| * be <code>null</code>. |
| * @param parentName The name of the parent folder; must not be |
| * <code>null</code>. |
| * @param suffix Expected second part of the script name (besides either the |
| * selector or the parent name); must not be <code>null</code>; |
| * applicable for any request method. |
| * @param htmlSuffix Expected second part of the script name (besides either |
| * the selector or the parent name); may be <code>null</code>; |
| * applicable for GET or HEAD methods only. |
| * @param resources The set of weighted resource to which the new weighted |
| * resource is added if a match is found. |
| * @param child The resource representing the script |
| * @param selIdx The selector weight value |
| * @return <code>true</code> if a match has been found and a weighted |
| * resource has been added to the <code>resources</code> set. |
| */ |
| private boolean checkScriptName(final String scriptName, |
| final String selector, final String parentName, |
| final String suffix, final String htmlSuffix, |
| final Set<WeightedResource> resources, final Resource child, |
| final int selIdx) { |
| if (selector != null && matches(scriptName, selector, suffix)) { |
| addWeightedResource(resources, child, selIdx + 1, |
| WeightedResource.WEIGHT_EXTENSION); |
| return true; |
| } |
| |
| if (matches(scriptName, parentName, suffix)) { |
| addWeightedResource(resources, child, selIdx, |
| WeightedResource.WEIGHT_EXTENSION |
| + WeightedResource.WEIGHT_PREFIX + ((htmlSuffix != null) ? WeightedResource.WEIGHT_METHOD : WeightedResource.WEIGHT_NONE)); |
| return true; |
| } |
| |
| if (suffix != null && !suffix.isEmpty() && scriptName.equals(suffix.substring(1))) { |
| addWeightedResource(resources, child, selIdx, |
| WeightedResource.WEIGHT_EXTENSION + ((htmlSuffix != null) ? WeightedResource.WEIGHT_METHOD : WeightedResource.WEIGHT_NONE)); |
| return true; |
| } |
| |
| if (isDefaultExtension) { |
| if (selector != null && matches(scriptName, selector, htmlSuffix)) { |
| addWeightedResource(resources, child, selIdx + 1, |
| WeightedResource.WEIGHT_NONE); |
| return true; |
| } |
| |
| if (matches(scriptName, parentName, htmlSuffix)) { |
| addWeightedResource(resources, child, selIdx, |
| WeightedResource.WEIGHT_PREFIX); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean matches(final String scriptName, final String name, |
| String suffix) { |
| if (suffix == null) { |
| return scriptName.equals(name); |
| } |
| final int lenScriptName = scriptName.length(); |
| final int lenName = name.length(); |
| final int lenSuffix = suffix.length(); |
| return scriptName.regionMatches(0, name, 0, lenName) |
| && scriptName.regionMatches(lenName, suffix, 0, lenSuffix) |
| && lenScriptName == (lenName + lenSuffix); |
| } |
| |
| private void addLocationServlet(final Set<WeightedResource> resources, |
| final Resource location) { |
| final String path = location.getPath() |
| + ServletResourceProviderFactory.SERVLET_PATH_EXTENSION; |
| if (SlingServletResolver.isPathAllowed(path, this.executionPaths)) { |
| final Resource servlet = location.getResourceResolver().getResource( |
| path); |
| if (servlet != null) { |
| addWeightedResource(resources, servlet, 0, |
| WeightedResource.WEIGHT_LAST_RESSORT); |
| } |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = super.hashCode(); |
| result = prime * result + (isDefaultExtension ? 1231 : 1237); |
| result = prime * result + (isGet ? 1231 : 1237); |
| result = prime * result + ((methodName == null) ? 0 : methodName.hashCode()); |
| result = prime * result + numRequestSelectors; |
| result = prime * result + Arrays.hashCode(requestSelectors); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (!super.equals(obj)) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| ResourceCollector other = (ResourceCollector) obj; |
| if (isDefaultExtension != other.isDefaultExtension) |
| return false; |
| if (isGet != other.isGet) |
| return false; |
| if (methodName == null) { |
| if (other.methodName != null) |
| return false; |
| } else if (!methodName.equals(other.methodName)) |
| return false; |
| if (numRequestSelectors != other.numRequestSelectors) |
| return false; |
| return Arrays.equals(requestSelectors, other.requestSelectors); |
| } |
| |
| } |