blob: 50df1119188c673c8abce541f4cde6ac1e2a1224 [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.get.impl;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import org.apache.sling.api.SlingConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceNotFoundException;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.servlets.get.impl.helpers.HeadServletResponse;
import org.apache.sling.servlets.get.impl.helpers.HtmlRenderer;
import org.apache.sling.servlets.get.impl.helpers.JsonRenderer;
import org.apache.sling.servlets.get.impl.helpers.PlainTextRenderer;
import org.apache.sling.servlets.get.impl.helpers.Renderer;
import org.apache.sling.servlets.get.impl.helpers.StreamRenderer;
import org.apache.sling.servlets.get.impl.helpers.XMLRenderer;
import org.apache.sling.xss.XSSAPI;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A SlingSafeMethodsServlet that renders the current Resource as simple HTML
*/
@Component(service = Servlet.class,
name="org.apache.sling.servlets.get.DefaultGetServlet",
property = {
"service.description=Default GET Servlet",
"service.vendor=The Apache Software Foundation",
// Use this as a default servlet for Sling
"sling.servlet.resourceTypes=sling/servlet/default",
"sling.servlet.prefix:Integer=-1",
// Generic handler for all get requests
"sling.servlet.methods=GET",
"sling.servlet.methods=HEAD"
})
@Designate(ocd=DefaultGetServlet.Config.class)
public class DefaultGetServlet extends SlingSafeMethodsServlet {
private static final long serialVersionUID = -2714152339750885354L;
@ObjectClassDefinition(name="Apache Sling GET Servlet",
description="The Sling GET servlet is registered as the default servlet to handle GET requests.")
public @interface Config {
@AttributeDefinition(name = "Extension Aliases",
description="The aliases can be used to map several extensions to a " +
"single servlet. For instance \"xml:pdf,rtf\" maps the extensions \".pdf\" and " +
"\".rtf\" to the servlet helper handling the \".xml\" extension.")
String[] aliases();
@AttributeDefinition(name = "Auto Index",
description="Controls whether a simple directory index is rendered for " +
"a directory request. A directory request is a request to a resource with a " +
"trailing slash (/) character, for example http://host/apps/. If none of the " +
"index resources exists, the default GET servlet may automatically render an " +
"index listing of the child resources if this option is checked, which is the " +
"default. If this option is not checked, the request to the resource is " +
"forbidden and results in a status 403/FORBIDDEN. This configuration " +
"corresponds to the \"Index\" option of the Options directive of Apache HTTP " +
"Server (httpd).")
boolean index() default false;
@AttributeDefinition(name = "Index Resources",
description = "List of child resources to be considered for rendering " +
"the index of a \"directory\". The default value is [ \"index\", \"index.html\" ]. " +
"Each entry in the list is checked and the first entry found is included to " +
"render the index. If an entry is selected, which has not extension (for " +
"example the \"index\" resource), the extension \".html\" is appended for the " +
"inclusion to indicate the desired text/html rendering. If the resource name " +
"has an extension (as in \"index.html\"), no additional extension is appended " +
"for the inclusion. This configuration corresponds to the <DirectoryIndex> " +
"directive of Apache HTTP Server (httpd).")
String[] index_files() default { "index","index.html" };
@AttributeDefinition(name = "Enable HTML",
description = "Whether the renderer for HTML of the default GET servlet is enabled or not. By default the HTML renderer is enabled.")
boolean enable_html() default true;
@AttributeDefinition(name = "Enable JSON",
description = "Whether the renderer for JSON of the default GET servlet is enabled or not. By default the JSON renderer is enabled.")
boolean enable_json() default true;
@AttributeDefinition(name = "Enable Plain Text",
description = "Whether the renderer for plain text of the default GET servlet is enabled or not. By default the plain text renderer is enabled.")
boolean enable_txt() default true;
@AttributeDefinition(name = "Enable XML",
description = "Whether the renderer for XML of the default GET servlet is enabled or not. By default the XML renderer is enabled.")
boolean enable_xml() default true;
@AttributeDefinition(name = "JSON Max results",
description = "The maximum number of resources that should " +
"be returned when doing a node.5.json or node.infinity.json. In JSON terms " +
"this basically means the number of Objects to return. Default value is " +
"200.")
int json_maximumresults() default 200;
@AttributeDefinition(name = "ECMA date Support", description="Enable deprecated ECMA formatting for JSON response")
boolean ecmaSuport() default false;
}
private final Logger logger = LoggerFactory.getLogger(getClass());
private Map<String, Renderer> rendererMap = new HashMap<>();
private int jsonMaximumResults;
/** Additional aliases. */
private String[] aliases;
/** Whether to support automatic index rendering */
private boolean index;
/** The names of index rendering children */
private String[] indexFiles;
private boolean enableHtml;
private boolean enableTxt;
private boolean enableJson;
private boolean enableXml;
@Reference(policyOption = ReferencePolicyOption.GREEDY)
private XSSAPI xssApi;
private boolean enableEcmaSupport;
public static final String EXT_HTML = "html";
public static final String EXT_TXT = "txt";
public static final String EXT_JSON = "json";
public static final String EXT_XML = "xml";
public static final String EXT_RES = "res";
@Activate
protected void activate(Config cfg) {
this.aliases = cfg.aliases();
this.index = cfg.index();
this.indexFiles = cfg.index_files();
if ( this.indexFiles == null ) {
this.indexFiles = new String[0];
}
this.enableHtml = cfg.enable_html();
this.enableTxt = cfg.enable_txt();
this.enableJson = cfg.enable_json();
this.enableXml = cfg.enable_xml();
this.jsonMaximumResults = cfg.json_maximumresults();
this.enableEcmaSupport = cfg.ecmaSuport();
}
@Deactivate
protected void deactivate() {
this.aliases = null;
this.index = false;
this.indexFiles = null;
}
private Renderer getDefaultRenderer(final String type) {
Renderer renderer = null;
if ( EXT_RES.equals(type) ) {
renderer = new StreamRenderer(index, indexFiles, getServletContext());
} else if ( EXT_HTML.equals(type) ) {
renderer = new HtmlRenderer(xssApi);
} else if ( EXT_TXT.equals(type) ) {
renderer = new PlainTextRenderer();
} else if (EXT_JSON.equals(type) ) {
renderer = new JsonRenderer(jsonMaximumResults, enableEcmaSupport);
} else if ( EXT_XML.equals(type) ) {
renderer = new XMLRenderer();
}
return renderer;
}
@Override
public void init() throws ServletException {
super.init();
// use the Renderer for rendering EXT_RES as the
// streamer renderer
Renderer streamRenderer = getDefaultRenderer(EXT_RES);
rendererMap.put(null, streamRenderer);
rendererMap.put(EXT_RES, streamRenderer);
if (enableHtml) {
rendererMap.put(EXT_HTML,
getDefaultRenderer(EXT_HTML));
}
if (enableTxt) {
rendererMap.put(EXT_TXT,
getDefaultRenderer(EXT_TXT));
}
if (enableJson) {
rendererMap.put(EXT_JSON,
getDefaultRenderer(EXT_JSON));
}
if (enableXml) {
rendererMap.put(EXT_XML,
getDefaultRenderer(EXT_XML));
}
// check additional aliases
if (this.aliases != null) {
for (final String m : aliases) {
final int pos = m.indexOf(':');
if (pos != -1) {
final String type = m.substring(0, pos);
Renderer renderer = rendererMap.get(type);
if ( renderer == null ) {
renderer = getDefaultRenderer(type);
}
if (renderer != null) {
final String extensions = m.substring(pos + 1);
final StringTokenizer st = new StringTokenizer(
extensions, ",");
while (st.hasMoreTokens()) {
final String ext = st.nextToken();
rendererMap.put(ext, renderer);
}
} else {
logger.warn("Unable to enable renderer alias(es) for {} - type not supported", m);
}
}
}
}
}
/**
* @throws ResourceNotFoundException if the resource of the request is a non
* existing resource.
*/
@Override
protected void doGet(SlingHttpServletRequest request,
SlingHttpServletResponse response) throws ServletException,
IOException {
// cannot handle the request for missing resources
if (ResourceUtil.isNonExistingResource(request.getResource())) {
throw new ResourceNotFoundException(
request.getResource().getPath(), "No resource found");
}
Renderer renderer;
String ext = request.getRequestPathInfo().getExtension();
renderer = rendererMap.get(ext);
// fail if we should not just stream or we cannot support the ext.
if (renderer == null) {
request.getRequestProgressTracker().log(
"No renderer for extension " + ext);
// if this is an included request, sendError() would fail
// as the response is already committed, in this case we just
// do nothing (but log an error message)
if (response.isCommitted()
|| request.getAttribute(SlingConstants.ATTR_REQUEST_SERVLET) != null) {
logger.error(
"No renderer for extension {}, cannot render resource {}",
ext, request.getResource());
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
return;
}
request.getRequestProgressTracker().log(
"Using " + renderer.getClass().getName()
+ " to render for extension=" + ext);
renderer.render(request, response);
}
@Override
protected void doHead(SlingHttpServletRequest request,
SlingHttpServletResponse response) throws ServletException,
IOException {
response = new HeadServletResponse(response);
doGet(request, response);
}
@Override
public void destroy() {
rendererMap.clear();
super.destroy();
}
}