blob: 629d25b23d366b8f9c5e728d34cec75eadd43190 [file] [log] [blame]
/*
* 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();
}
}