Package org.apache.sling.api.servlets

Source Code of org.apache.sling.api.servlets.SlingSafeMethodsServlet$NoBodyResponse

/*
* 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 javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
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 <em>HttpServlet</em>
* class which accounts for extensibility. So extensions of this class have
* great control over what methods to overwrite.
* <p>
* 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.
* <p>
* 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 <em>POST</em>,
* <em>PUT</em> and <em>DELETE</em> 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 <em>POST</em>, <em>PUT</em> and
* <em>DELETE</em> methods. This latter class should also be overwritten to
* add support for HTTP methods modifying data.
* <p>
* 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 <em>HEAD</em> method.
     * <p>
     * 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(SlingHttpServletRequest request,
            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 <em>GET</em> request.
     * <p>
     * This default implementation reports back to the client that the method is
     * not supported.
     * <p>
     * Implementations of this class should overwrite this method with their
     * implementation for the HTTP <em>GET</em> 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.
     */
    @SuppressWarnings("unused")
    protected void doGet(SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws ServletException,
            IOException {
        handleMethodNotImplemented(request, response);
    }

    /**
     * Handles the <em>OPTIONS</em> method by setting the HTTP
     * <code>Allow</code> header on the response depending on the methods
     * declared in this class.
     * <p>
     * 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 <code>Allow</code>
     * 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.
     */
    @SuppressWarnings("unused")
    protected void doOptions(SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws ServletException,
            IOException {
        Map<String, Method> methods = getAllDeclaredMethods(getClass());
        StringBuffer allowBuf = getAllowedRequestMethods(methods);
        response.setHeader("Allow", allowBuf.toString());
    }

    /**
     * Handles the <em>TRACE</em> method by just returning the list of all
     * header values in the response body.
     * <p>
     * Extensions of this class do not generally need to overwrite this method
     * as it contains all there is to be done to the <em>TRACE</em> 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.
     */
    @SuppressWarnings("unused")
    protected void doTrace(SlingHttpServletRequest request,
            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.
     * <p>
     * This default implementation reports back to the client that the method is
     * not supported.
     * <p>
     * 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.
     */
    @SuppressWarnings("unused")
    protected void doGeneric(SlingHttpServletRequest request,
            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.
     * <p>
     * This base class implentation dispatches the <em>HEAD</em>,
     * <em>GET</em>, <em>OPTIONS</em> and <em>TRACE</em> to the
     * respective <em>doXXX</em> methods and returns <code>true</code> if
     * any of these methods is requested. Otherwise <code>false</code> is just
     * returned.
     * <p>
     * Implementations of this class may overwrite this method but should first
     * call this base implementation and in case <code>false</code> 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 <code>true</code> if the requested method (<code>request.getMethod()</code>)
     *         is known. Otherwise <code>false</code> is returned.
     * @throws ServletException Forwarded from any of the dispatched methods
     * @throws IOException Forwarded from any of the dispatched methods
     */
    protected boolean mayService(SlingHttpServletRequest request,
            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(SlingHttpServletRequest request,
            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.
     * <p>
     * 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(SlingHttpServletRequest request,
            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.
     * <p>
     * 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.
     */
    public void service(ServletRequest req, 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.
     */
    public String getServletInfo() {
        return getClass().getSimpleName();
    }

    /**
     * Helper method called by
     * {@link #doOptions(SlingHttpServletRequest, SlingHttpServletResponse)} to calculate
     * the value of the <em>Allow</em> header sent as the response to the HTTP
     * <em>OPTIONS</em> request.
     * <p>
     * This base class implementation checks whether any doXXX methods exist for
     * <em>GET</em> and <em>HEAD</em> and returns the list of methods
     * supported found. The list returned always includes the HTTP
     * <em>OPTIONS</em> and <em>TRACE</em> methods.
     * <p>
     * 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 <code>StringBuffer</code> containing the list of HTTP methods
     *         supported.
     */
    protected StringBuffer getAllowedRequestMethods(
            Map<String, Method> 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 <code>Class</code> to get the declared methods from
     * @return The Map of methods considered for support checking.
     */
    private Map<String, Method> getAllDeclaredMethods(Class<?> c) {
        // stop (and do not include) the AbstractSlingServletClass
        if (c == null
            || c.getName().equals(SlingSafeMethodsServlet.class.getName())) {
            return new HashMap<String, Method>();
        }

        // get the declared methods from the base class
        Map<String, Method> 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()}
         */
        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.
         */
        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.
         */
        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;
        }

        public void write(int b) {
            contentLength++;
        }

        public void write(byte buf[], int offset, int len) {
            if (len >= 0) {
                contentLength += len;
            } else {
                throw new IndexOutOfBoundsException();
            }
        }
    }

}
TOP

Related Classes of org.apache.sling.api.servlets.SlingSafeMethodsServlet$NoBodyResponse

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.