| /* |
| * 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 static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_EXTENSIONS; |
| import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_METHODS; |
| import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_NAME; |
| import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_PATHS; |
| import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_PREFIX; |
| import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_RESOURCE_SUPER_TYPE; |
| import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES; |
| import static org.apache.sling.api.servlets.ServletResolverConstants.SLING_SERVLET_SELECTORS; |
| import static org.osgi.service.component.ComponentConstants.COMPONENT_NAME; |
| |
| import java.util.HashSet; |
| import java.util.List; |
| 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; |
| import org.apache.sling.commons.osgi.PropertiesUtil; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class ServletResourceProviderFactory { |
| |
| /** |
| * The extension appended to servlets to register into the resource tree to |
| * simplify handling in the resolution process (value is ".servlet"). |
| */ |
| public static final String SERVLET_PATH_EXTENSION = ".servlet"; |
| |
| private static final String[] DEFAULT_SERVLET_METHODS = { |
| HttpConstants.METHOD_GET, HttpConstants.METHOD_HEAD }; |
| |
| private static final String ALL_METHODS = "*"; |
| |
| /** default log */ |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| /** |
| * The root path to use for servlets registered with relative paths. |
| */ |
| private final String servletRoot; |
| |
| /** |
| * The index of the search path to be used as servlet root path |
| */ |
| private final int servletRootIndex; |
| |
| /** |
| * The search paths |
| */ |
| private final List<String> searchPath; |
| |
| static String ensureServletNameExtension(final String servletPath) { |
| if (servletPath.endsWith(SERVLET_PATH_EXTENSION)) { |
| return servletPath; |
| } |
| |
| return servletPath.concat(SERVLET_PATH_EXTENSION); |
| } |
| |
| /** |
| * Constructor |
| * @param servletRoot The default value for the servlet root |
| */ |
| public ServletResourceProviderFactory(String servletRoot, final List<String> searchPath) { |
| this.searchPath = searchPath; |
| // check if servletRoot specifies a number |
| boolean isNumber = false; |
| int index = -1; |
| if (!servletRoot.startsWith("/") ) { |
| try { |
| index = Integer.valueOf(servletRoot); |
| isNumber = true; |
| } catch (NumberFormatException nfe) { |
| // ignore |
| } |
| } |
| if ( !isNumber ) { |
| // ensure the root starts and ends with a slash |
| if (!servletRoot.startsWith("/")) { |
| servletRoot = "/".concat(servletRoot); |
| } |
| if (!servletRoot.endsWith("/")) { |
| servletRoot = servletRoot.concat("/"); |
| } |
| |
| this.servletRoot = servletRoot; |
| this.servletRootIndex = -1; |
| } else { |
| this.servletRoot = null; |
| this.servletRootIndex = index; |
| } |
| } |
| |
| /** |
| * Create a servlet resource provider for the servlet |
| * @param ref The service reference for the servlet |
| * @param servlet The servlet object itself |
| * @return A servlet resource provider |
| */ |
| public ServletResourceProvider create(final ServiceReference<Servlet> ref, final Servlet servlet) { |
| |
| final Set<String> pathSet = new HashSet<>(); |
| |
| // check whether explicit paths are set |
| addByPath(pathSet, ref); |
| |
| // now, we handle resource types, extensions and methods |
| addByType(pathSet, ref); |
| |
| if (pathSet.isEmpty()) { |
| if (log.isDebugEnabled()) { |
| log.debug( |
| "create({}): ServiceReference has no registration settings, ignoring", |
| getServiceReferenceInfo(ref)); |
| } |
| return null; |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("create({}): Registering servlet for paths {}", |
| getServiceReferenceInfo(ref), pathSet); |
| } |
| String resourceSuperType = PropertiesUtil.toString(ref.getProperty(SLING_SERVLET_RESOURCE_SUPER_TYPE), null); |
| Set<String> resourceSuperTypeMarkers = new HashSet<>(); |
| if (StringUtils.isNotEmpty(resourceSuperType)) { |
| for (String rt : PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_RESOURCE_TYPES))) { |
| if (!rt.startsWith("/")) { |
| rt = getPrefix(ref).concat(ResourceUtil.resourceTypeToPath(rt)); |
| } |
| resourceSuperTypeMarkers.add(rt); |
| pathSet.add(rt); |
| } |
| } |
| return new ServletResourceProvider(servlet, pathSet, resourceSuperTypeMarkers, resourceSuperType); |
| } |
| |
| /** |
| * Get the mount prefix. |
| */ |
| private String getPrefix(final ServiceReference<Servlet> ref) { |
| Object value = ref.getProperty(SLING_SERVLET_PREFIX); |
| if ( value == null ) { |
| if ( this.servletRoot != null ) { |
| return this.servletRoot; |
| } |
| value = this.servletRootIndex; |
| } |
| int index = -1; |
| if ( value instanceof Number ) { |
| index = ((Number)value).intValue(); |
| } else { |
| String s = value.toString(); |
| if ( !s.startsWith("/") ) { |
| boolean isNumber = false; |
| try { |
| index = Integer.valueOf(s); |
| isNumber = true; |
| } catch (NumberFormatException nfe) { |
| // ignore |
| } |
| if ( !isNumber ) { |
| if (log.isDebugEnabled()) { |
| log.debug("getPrefix({}): Configuration property is ignored {}", |
| getServiceReferenceInfo(ref), value); |
| } |
| if ( this.servletRoot != null ) { |
| return this.servletRoot; |
| } |
| index = this.servletRootIndex; |
| } |
| } else { |
| return s; |
| } |
| } |
| if ( index == -1 || index >= this.searchPath.size() ) { |
| index = this.searchPath.size() - 1; |
| } |
| return this.searchPath.get(index); |
| } |
| |
| /** |
| * Add a servlet by path. |
| * @param pathSet |
| * @param ref |
| */ |
| private void addByPath(Set<String> pathSet, ServiceReference<Servlet> ref) { |
| String[] paths = PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_PATHS)); |
| if (paths != null && paths.length > 0) { |
| for (String path : paths) { |
| if (!path.startsWith("/")) { |
| path = getPrefix(ref).concat(path); |
| } |
| |
| // add the unmodified path |
| pathSet.add(path); |
| |
| String[] types = PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_RESOURCE_TYPES)); |
| |
| if ((types == null || types.length == 0) || 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)); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Add a servlet by type |
| * @param pathSet |
| * @param ref |
| */ |
| private void addByType(Set<String> pathSet, ServiceReference<Servlet> ref) { |
| String[] types = PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_RESOURCE_TYPES)); |
| String[] paths = PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_PATHS)); |
| boolean hasPathRegistration = true; |
| if (paths == null || paths.length == 0) { |
| hasPathRegistration = false; |
| } |
| if (types == null || types.length == 0) { |
| if (log.isDebugEnabled()) { |
| log.debug("addByType({}): no resource types declared", |
| getServiceReferenceInfo(ref)); |
| } |
| return; |
| } |
| |
| // check for selectors |
| String[] selectors = PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_SELECTORS)); |
| if (selectors == null) { |
| selectors = new String[] { null }; |
| } |
| |
| // we have types and expect extensions and/or methods |
| String[] extensions = PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_EXTENSIONS)); |
| |
| // handle the methods property specially (SLING-430) |
| String[] methods = PropertiesUtil.toStringArray(ref.getProperty(SLING_SERVLET_METHODS)); |
| if (methods == null || methods.length == 0) { |
| |
| // SLING-512 only, set default methods if no extensions are declared |
| if ((extensions == null || extensions.length == 0) && !hasPathRegistration) { |
| if (log.isDebugEnabled()) { |
| log.debug( |
| "addByType({}): No methods declared, assuming GET/HEAD", |
| getServiceReferenceInfo(ref)); |
| } |
| methods = DEFAULT_SERVLET_METHODS; |
| } |
| |
| } else if (methods.length == 1 && ALL_METHODS.equals(methods[0])) { |
| if (log.isDebugEnabled()) { |
| log.debug("addByType({}): Assuming all methods for '*'", |
| getServiceReferenceInfo(ref)); |
| } |
| methods = null; |
| } |
| |
| for (String type : types) { |
| |
| // ensure namespace prefixes are converted to slashes |
| type = ResourceUtil.resourceTypeToPath(type); |
| |
| // make absolute if relative |
| if (!type.startsWith("/")) { |
| type = this.getPrefix(ref) + type; |
| } |
| |
| // ensure trailing slash for full path building |
| if (!type.endsWith("/")) { |
| type += "/"; |
| } |
| |
| // add entries for each selector combined with each ext and method |
| for (String selector : selectors) { |
| |
| String selPath = type; |
| if (selector != null && selector.length() > 0) { |
| selPath += selector.replace('.', '/') + "."; |
| } |
| |
| boolean pathAdded = false; |
| if (extensions != null) { |
| if (methods != null) { |
| // both methods and extensions declared |
| for (String ext : extensions) { |
| for (String method : methods) { |
| pathSet.add(selPath + ext + "." + method |
| + SERVLET_PATH_EXTENSION); |
| pathAdded = true; |
| } |
| } |
| } else { |
| // only extensions declared |
| for (String ext : extensions) { |
| pathSet.add(selPath + ext + SERVLET_PATH_EXTENSION); |
| pathAdded = true; |
| } |
| } |
| } else if (methods != null) { |
| // only methods declared |
| for (String method : methods) { |
| pathSet.add(selPath + method + SERVLET_PATH_EXTENSION); |
| pathAdded = true; |
| } |
| } |
| |
| // if neither methods nor extensions were added |
| if (!pathAdded && !hasPathRegistration) { |
| pathSet.add(selPath.substring(0, selPath.length() - 1) |
| + SERVLET_PATH_EXTENSION); |
| } |
| } |
| } |
| } |
| |
| public static String getServiceReferenceInfo(final ServiceReference<Servlet> reference) { |
| final StringBuilder sb = new StringBuilder("service "); |
| sb.append(String.valueOf(reference.getProperty(Constants.SERVICE_ID))); |
| final Object servletName = reference.getProperty(SLING_SERVLET_NAME); |
| final Object pid = reference.getProperty(Constants.SERVICE_PID); |
| Object componentName = reference.getProperty(COMPONENT_NAME); |
| if ( pid != null && pid.equals(componentName) ) { |
| componentName = null; |
| } |
| if ( servletName != null || pid != null || componentName != null ) { |
| sb.append(" ("); |
| boolean needsComma = false; |
| if ( servletName != null ) { |
| sb.append("name="); |
| sb.append(servletName); |
| needsComma = true; |
| } |
| if ( pid != null ) { |
| if ( needsComma ) { |
| sb.append(", "); |
| } |
| sb.append("pid="); |
| sb.append(pid); |
| needsComma = true; |
| } |
| if ( componentName != null ) { |
| if ( needsComma ) { |
| sb.append(", "); |
| } |
| sb.append("component="); |
| sb.append(componentName); |
| needsComma = true; |
| } |
| sb.append(")"); |
| } |
| sb.append(" from "); |
| final Bundle bundle = reference.getBundle(); |
| if ( bundle == null ) { |
| sb.append("uninstalled bundle"); |
| } else { |
| sb.append("bundle "); |
| if ( bundle.getSymbolicName() == null ) { |
| sb.append(String.valueOf(bundle.getBundleId())); |
| } else { |
| sb.append(bundle.getSymbolicName()); |
| sb.append(":"); |
| sb.append(bundle.getVersion()); |
| sb.append(" ("); |
| sb.append(String.valueOf(bundle.getBundleId())); |
| sb.append(") "); |
| } |
| } |
| final String[] ocs = (String[]) reference.getProperty("objectClass"); |
| if ( ocs != null ) { |
| sb.append("["); |
| for(int i = 0; i < ocs.length; i++) { |
| sb.append(ocs[i]); |
| if (i < ocs.length - 1) { |
| sb.append(", "); |
| } |
| } |
| sb.append("]"); |
| } |
| return sb.toString(); |
| } |
| } |