All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.sling.api.servlets.SlingSafeMethodsServlet Maven / Gradle / Ivy

There is a newer version: 6.5.21
Show newest version
/*
 * 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.api.servlets;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import org.jetbrains.annotations.NotNull;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper;

/**
 * Helper base class for read-only Servlets used in Sling. This base class is
 * actually just a better implementation of the Servlet API HttpServlet
 * class which accounts for extensibility. So extensions of this class have
 * great control over what methods to overwrite.
 * 

* If any of the default HTTP methods is to be implemented just overwrite the * respective doXXX method. If additional methods should be supported implement * appropriate doXXX methods and overwrite the * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method * to dispatch to the doXXX methods as appropriate and overwrite the * {@link #getAllowedRequestMethods(Map)} to add the new method names. *

* Please note, that this base class is intended for applications where data is * only read. As such, this servlet by itself does not support the POST, * PUT and DELETE methods. Extensions of this class should * either overwrite any of the doXXX methods of this class or add support for * other read-only methods only. Applications wishing to support data * modification should rather use or extend the {@link SlingAllMethodsServlet} * which also contains support for the POST, PUT and * DELETE methods. This latter class should also be overwritten to * add support for HTTP methods modifying data. *

* Implementors note: The methods in this class are all declared to throw the * exceptions according to the intentions of the Servlet API rather than * throwing their Sling RuntimeException counter parts. This is done to ease the * integration with traditional servlets. * * @see SlingAllMethodsServlet */ public class SlingSafeMethodsServlet extends GenericServlet { private static final long serialVersionUID = 3620512288346703072L; /** * Handles the HEAD method. *

* This base implementation just calls the * {@link #doGet(SlingHttpServletRequest, SlingHttpServletResponse)} method dropping * the output. Implementations of this class may overwrite this method if * they have a more performing implementation. Otherwise, they may just keep * this base implementation. * * @param request The HTTP request * @param response The HTTP response which only gets the headers set * @throws ServletException Forwarded from the * {@link #doGet(SlingHttpServletRequest, SlingHttpServletResponse)} * method called by this implementation. * @throws IOException Forwarded from the * {@link #doGet(SlingHttpServletRequest, SlingHttpServletResponse)} * method called by this implementation. */ protected void doHead(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { // the null-output wrapper NoBodyResponse wrappedResponse = new NoBodyResponse(response); // do a normal get request, dropping the output doGet(request, wrappedResponse); // ensure the content length is set as gathered by the null-output wrappedResponse.setContentLength(); } /** * Called by the * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method to * handle an HTTP GET request. *

* This default implementation reports back to the client that the method is * not supported. *

* Implementations of this class should overwrite this method with their * implementation for the HTTP GET method support. * * @param request The HTTP request * @param response The HTTP response * @throws ServletException Not thrown by this implementation. * @throws IOException If the error status cannot be reported back to the * client. */ protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { handleMethodNotImplemented(request, response); } /** * Handles the OPTIONS method by setting the HTTP * Allow header on the response depending on the methods * declared in this class. *

* Extensions of this class should generally not overwrite this method but * rather the {@link #getAllowedRequestMethods(Map)} method. This method * gathers all declared public and protected methods for the concrete class * (upto but not including this class) and calls the * {@link #getAllowedRequestMethods(Map)} method with the methods gathered. * The returned value is then used as the value of the Allow * header set. * * @param request The HTTP request object. Not used. * @param response The HTTP response object on which the header is set. * @throws ServletException Not thrown by this implementation. * @throws IOException Not thrown by this implementation. */ protected void doOptions(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { Map methods = getAllDeclaredMethods(getClass()); StringBuffer allowBuf = getAllowedRequestMethods(methods); response.setHeader("Allow", allowBuf.toString()); } /** * Handles the TRACE method by just returning the list of all * header values in the response body. *

* Extensions of this class do not generally need to overwrite this method * as it contains all there is to be done to the TRACE method. * * @param request The HTTP request whose headers are returned. * @param response The HTTP response into which the request headers are * written. * @throws ServletException Not thrown by this implementation. * @throws IOException May be thrown if there is an problem sending back the * request headers in the response stream. */ protected void doTrace(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { String CRLF = "\r\n"; StringBuffer responseString = new StringBuffer(); responseString.append("TRACE ").append(request.getRequestURI()); responseString.append(' ').append(request.getProtocol()); Enumeration reqHeaderEnum = request.getHeaderNames(); while (reqHeaderEnum.hasMoreElements()) { String headerName = (String) reqHeaderEnum.nextElement(); Enumeration reqHeaderValEnum = request.getHeaders(headerName); while (reqHeaderValEnum.hasMoreElements()) { responseString.append(CRLF); responseString.append(headerName).append(": "); responseString.append(reqHeaderValEnum.nextElement()); } } responseString.append(CRLF); String charset = "UTF-8"; byte[] rawResponse = responseString.toString().getBytes(charset); int responseLength = rawResponse.length; response.setContentType("message/http"); response.setCharacterEncoding(charset); response.setContentLength(responseLength); ServletOutputStream out = response.getOutputStream(); out.write(rawResponse); } /** * Called by the {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)} * method to handle a request for an HTTP method, which is not known and * handled by this class or its extension. *

* This default implementation reports back to the client that the method is * not supported. *

* This method should be overwritten with great care. It is better to * overwrite the * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method and * add support for any extension HTTP methods through an additional doXXX * method. * * @param request The HTTP request * @param response The HTTP response * @throws ServletException Not thrown by this implementation. * @throws IOException If the error status cannot be reported back to the * client. */ protected void doGeneric(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { handleMethodNotImplemented(request, response); } /** * Tries to handle the request by calling a Java method implemented for the * respective HTTP request method. *

* This base class implentation dispatches the HEAD, * GET, OPTIONS and TRACE to the * respective doXXX methods and returns true if * any of these methods is requested. Otherwise false is just * returned. *

* Implementations of this class may overwrite this method but should first * call this base implementation and in case false is * returned add handling for any other method and of course return whether * the requested method was known or not. * * @param request The HTTP request * @param response The HTTP response * @return true if the requested method (request.getMethod()) * is known. Otherwise false is returned. * @throws ServletException Forwarded from any of the dispatched methods * @throws IOException Forwarded from any of the dispatched methods */ protected boolean mayService(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { // assume the method is known for now boolean methodKnown = true; String method = request.getMethod(); if (HttpConstants.METHOD_HEAD.equals(method)) { doHead(request, response); } else if (HttpConstants.METHOD_GET.equals(method)) { doGet(request, response); } else if (HttpConstants.METHOD_OPTIONS.equals(method)) { doOptions(request, response); } else if (HttpConstants.METHOD_TRACE.equals(method)) { doTrace(request, response); } else { // actually we do not know the method methodKnown = false; } // return whether we actually knew the request method or not return methodKnown; } /** * Helper method which causes an appropriate HTTP response to be sent for an * unhandled HTTP request method. In case of HTTP/1.1 a 405 status code * (Method Not Allowed) is returned, otherwise a 400 status (Bad Request) is * returned. * * @param request The HTTP request from which the method and protocol values * are extracted to build the appropriate message. * @param response The HTTP response to which the error status is sent. * @throws IOException Thrown if the status cannot be sent to the client. */ protected void handleMethodNotImplemented(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws IOException { String protocol = request.getProtocol(); String msg = "Method " + request.getMethod() + " not supported"; if (protocol.endsWith("1.1")) { // for HTTP/1.1 use 405 Method Not Allowed response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg); } else { // otherwise use 400 Bad Request response.sendError(HttpServletResponse.SC_BAD_REQUEST, msg); } } /** * Called by the {@link #service(ServletRequest, ServletResponse)} method to * handle the HTTP request. This implementation calls the * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method and * depedending on its return value call the * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)} method. If * the {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method * can handle the request, the * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)} method is not * called otherwise it is called. *

* Implementations of this class should not generally overwrite this method. * Rather the {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} * method should be overwritten to add support for more HTTP methods. * * @param request The HTTP request * @param response The HTTP response * @throws ServletException Forwarded from the * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} * or * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)} * methods. * @throws IOException Forwarded from the * {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} * or * {@link #doGeneric(SlingHttpServletRequest, SlingHttpServletResponse)} * methods. */ protected void service(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { // first try to handle the request by the known methods boolean methodKnown = mayService(request, response); // otherwise try to handle it through generic means if (!methodKnown) { doGeneric(request, response); } } /** * Forwards the request to the * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)} * method if the request is a HTTP request. *

* Implementations of this class will not generally overwrite this method. * * @param req The Servlet request * @param res The Servlet response * @throws ServletException If the request is not a HTTP request or * forwarded from the * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)} * called. * @throws IOException Forwarded from the * {@link #service(SlingHttpServletRequest, SlingHttpServletResponse)} * called. */ @Override public void service(@NotNull ServletRequest req, @NotNull ServletResponse res) throws ServletException, IOException { if ((req instanceof SlingHttpServletRequest) && (res instanceof SlingHttpServletResponse)) { service((SlingHttpServletRequest) req, (SlingHttpServletResponse) res); } else { throw new ServletException("Not a Sling HTTP request/response"); } } /** * Returns the simple class name of this servlet class. Extensions of this * class may overwrite to return more specific information. */ @Override public @NotNull String getServletInfo() { return getClass().getSimpleName(); } /** * Helper method called by * {@link #doOptions(SlingHttpServletRequest, SlingHttpServletResponse)} to calculate * the value of the Allow header sent as the response to the HTTP * OPTIONS request. *

* This base class implementation checks whether any doXXX methods exist for * GET and HEAD and returns the list of methods * supported found. The list returned always includes the HTTP * OPTIONS and TRACE methods. *

* Implementations of this class may overwrite this method check for more * methods supported by the extension (generally the same list as used in * the {@link #mayService(SlingHttpServletRequest, SlingHttpServletResponse)} method). * This base class implementation should always be called to make sure the * default HTTP methods are included in the list. * * @param declaredMethods The public and protected methods declared in the * extension of this class. * @return A StringBuffer containing the list of HTTP methods * supported. */ protected StringBuffer getAllowedRequestMethods( Map declaredMethods) { StringBuffer allowBuf = new StringBuffer(); // OPTIONS and TRACE are always supported by this servlet allowBuf.append(HttpConstants.METHOD_OPTIONS); allowBuf.append(", ").append(HttpConstants.METHOD_TRACE); // add more method names depending on the methods found if (declaredMethods.containsKey("doHead") && !declaredMethods.containsKey("doGet")) { allowBuf.append(", ").append(HttpConstants.METHOD_HEAD); } else if (declaredMethods.containsKey("doGet")) { allowBuf.append(", ").append(HttpConstants.METHOD_GET); allowBuf.append(", ").append(HttpConstants.METHOD_HEAD); } return allowBuf; } /** * Returns a map of methods declared by the class indexed by method name. * This method is called by the * {@link #doOptions(SlingHttpServletRequest, SlingHttpServletResponse)} method to * find the methods to be checked by the * {@link #getAllowedRequestMethods(Map)} method. Note, that only extension * classes of this class are considered to be sure to not account for the * default implementations of the doXXX methods in this class. * * @param c The Class to get the declared methods from * @return The Map of methods considered for support checking. */ private Map getAllDeclaredMethods(Class c) { // stop (and do not include) the AbstractSlingServletClass if (c == null || c.getName().equals(SlingSafeMethodsServlet.class.getName())) { return new HashMap(); } // get the declared methods from the base class Map methodSet = getAllDeclaredMethods(c.getSuperclass()); // add declared methods of c (maybe overwrite base class methods) Method[] declaredMethods = c.getDeclaredMethods(); for (Method method : declaredMethods) { // only consider public and protected methods if (Modifier.isProtected(method.getModifiers()) || Modifier.isPublic(method.getModifiers())) { methodSet.put(method.getName(), method); } } return methodSet; } /** * A response that includes no body, for use in (dumb) "HEAD" support. This * just swallows that body, counting the bytes in order to set the content * length appropriately. */ private class NoBodyResponse extends SlingHttpServletResponseWrapper { /** The byte sink and counter */ private NoBodyOutputStream noBody; /** Optional writer around the byte sink */ private PrintWriter writer; /** Whether the request processor set the content length itself or not. */ private boolean didSetContentLength; NoBodyResponse(SlingHttpServletResponse wrappedResponse) { super(wrappedResponse); noBody = new NoBodyOutputStream(); } /** * Called at the end of request processing to ensure the content length * is set. If the processor already set the length, this method does not * do anything. Otherwise the number of bytes written through the * null-output is set on the response. */ void setContentLength() { if (!didSetContentLength) { setContentLength(noBody.getContentLength()); } } /** * Overwrite this to prevent setting the content length at the end of * the request through {@link #setContentLength()} */ @Override public void setContentLength(int len) { super.setContentLength(len); didSetContentLength = true; } /** * Just return the null output stream and don't check whether a writer * has already been acquired. */ @Override public ServletOutputStream getOutputStream() { return noBody; } /** * Just return the writer to the null output stream and don't check * whether an output stram has already been acquired. */ @Override public PrintWriter getWriter() throws UnsupportedEncodingException { if (writer == null) { OutputStreamWriter w; w = new OutputStreamWriter(noBody, getCharacterEncoding()); writer = new PrintWriter(w); } return writer; } } /** * Simple ServletOutputStream which just does not write but counts the bytes * written through it. This class is used by the NoBodyResponse. */ private class NoBodyOutputStream extends ServletOutputStream { private int contentLength = 0; /** * @return the number of bytes "written" through this stream */ int getContentLength() { return contentLength; } @Override public void write(int b) { contentLength++; } @Override public void write(byte buf[], int offset, int len) { if (len >= 0) { contentLength += len; } else { throw new IndexOutOfBoundsException(); } } @Override public boolean isReady() { return true; } @Override public void setWriteListener(WriteListener writeListener) { // nothing to do } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy