Package org.apache.sling.engine.impl

Source Code of org.apache.sling.engine.impl.SlingMainServlet

/*
* 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.engine.impl;

import static org.apache.sling.api.SlingConstants.ERROR_REQUEST_URI;
import static org.apache.sling.api.SlingConstants.ERROR_SERVLET_NAME;
import static org.apache.sling.engine.EngineConstants.SESSION;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.SocketException;
import java.net.URL;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import javax.jcr.Session;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.GenericServlet;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.sling.api.SlingException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceNotFoundException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.ServletResolver;
import org.apache.sling.commons.mime.MimeTypeService;
import org.apache.sling.engine.impl.auth.MissingRepositoryException;
import org.apache.sling.engine.impl.auth.SlingAuthenticator;
import org.apache.sling.engine.impl.filter.RequestSlingFilterChain;
import org.apache.sling.engine.impl.filter.SlingComponentFilterChain;
import org.apache.sling.engine.impl.filter.SlingFilterChainHelper;
import org.apache.sling.engine.impl.helper.SlingFilterConfig;
import org.apache.sling.engine.impl.helper.SlingServletContext;
import org.apache.sling.engine.impl.log.RequestLogger;
import org.apache.sling.engine.impl.request.ContentData;
import org.apache.sling.engine.impl.request.RequestData;
import org.apache.sling.engine.servlets.AbstractServiceReferenceConfig;
import org.apache.sling.engine.servlets.ErrorHandler;
import org.apache.sling.jcr.resource.JcrResourceResolverFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.http.HttpContext;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The <code>SlingMainServlet</code> TODO
*
* @scr.component immediate="true" metatype="no"
* @scr.property name="sling.root" value="/" private="true"
* @scr.property name="service.vendor" value="The Apache Software Foundation"
* @scr.property name="service.description" value="Sling Servlet"
* @scr.reference name="Filter" interface="javax.servlet.Filter"
*                cardinality="0..n" policy="dynamic"
*/
public class SlingMainServlet extends GenericServlet implements ErrorHandler, HttpContext {

    /** default log */
    private static final Logger log = LoggerFactory.getLogger(SlingMainServlet.class);

    /**
     * The name of the product to report in the {@link #getServerInfo()} method
     * (value is "ApacheSling").
     */
    private static String PRODUCT_NAME = "ApacheSling";

    /**
     * The name of the Declarative Services reference to the Servlet API Filter
     * services (value is "Filter").
     */
    private static String FILTER_NAME = "Filter";

    private SlingServletContext slingServletContext;

    private ComponentContext osgiComponentContext;

    private List<ServiceReference> delayedComponentFilters;

    /**
     * The server information to report in the {@link #getServerInfo()} method.
     * By default this is just the {@link #PRODUCT_NAME}. The
     * {@link #activate(org.osgi.service.component.ComponentContext)} method
     * appends this value with the version (major and minor part) of this bundle
     * as well as the operating system and java version it is running on.
     */
    private String serverInfo = PRODUCT_NAME;

    /**
     * @scr.reference
     */
    private HttpService httpService;

    /** @scr.reference cardinality="0..1" policy="dynamic" */
    private JcrResourceResolverFactory resourceResolverFactory;

    /** @scr.reference cardinality="0..1" policy="dynamic" */
    private MimeTypeService mimeTypeService;

    /** @scr.reference cardinality="0..1" policy="dynamic" */
    private ServletResolver servletResolver;

    /** @scr.reference cardinality="0..1" policy="dynamic" */
    private ErrorHandler errorHandler;

    /** @scr.reference cardinality="0..1" policy="dynamic" */
    private RequestLogger requestLogger;

    private SlingFilterChainHelper requestFilterChain = new SlingFilterChainHelper();

    private SlingFilterChainHelper innerFilterChain = new SlingFilterChainHelper();

    private String slingRoot;

    private SlingAuthenticator slingAuthenticator;

    public static final String INCLUDE_COUNTER = "Sling.ScriptHelper.include.counter";
    public static final int MAX_INCLUDE_RECURSION_LEVEL = 50;

    public static class InfiniteIncludeLoopException extends SlingException {
        InfiniteIncludeLoopException(String path) {
            super("Infinite include loop (> " + MAX_INCLUDE_RECURSION_LEVEL + " levels) for path '" + path + "'");
        }
    }

    // ---------- Servlet API -------------------------------------------------

    public void service(ServletRequest req, ServletResponse res)
            throws ServletException {

        if (req instanceof HttpServletRequest
            && res instanceof HttpServletResponse) {

            try {

                // real request handling for HTTP requests
                service((HttpServletRequest) req, (HttpServletResponse) res);

            } catch (IOException ioe) {

                // unwrap any causes (Jetty wraps SocketException in
                // EofException)
                Throwable cause = ioe;
                while (cause.getCause() != null) {
                    cause = cause.getCause();
                }

                if (cause instanceof SocketException) {

                    // if the cause is a SocketException, the client most
                    // probably
                    // aborted the request, we do not fill the log with errors
                    // in this case
                    log.debug(
                        "service: Socketexception (Client abort or network problem",
                        ioe);

                } else {

                    // otherwise we want to know why the servlet failed
                    log.error(
                        "service: Uncaught IO Problem while handling the request",
                        ioe);
                }

            } catch (Throwable t) {

                // some failure while handling the request, log the issue
                // and terminate. We do not call error handling here, because
                // we assume the real request handling would have done this.
                // So here we just log

                log.error("service: Uncaught Problem handling the request", t);
            }

        } else {
            throw new ServletException(
                "Apache Sling must be run in an HTTP servlet environment.");
        }
    }

    // ---------- Request Handling on behalf of the servlet -------------------

    public void service(HttpServletRequest servletRequest,
            HttpServletResponse servletResponse) throws IOException {

        // setting the Sling request and response
        final RequestData requestData = new RequestData(this, servletRequest,
            servletResponse);
        SlingHttpServletRequest request = requestData.getSlingRequest();
        SlingHttpServletResponse response = requestData.getSlingResponse();

        // request entry log
        if (requestLogger != null) {
            requestLogger.logRequestEntry(request, response);
        }

        Session session = null;
        try {
            // check that we have all required services
            String missing = null;
            if (getResourceResolverFactory() == null) {
                missing = "ResourceResolverFactory";
            } else if (getServletResolver() == null) {
                missing = "ServletResolver";
            } else if (mimeTypeService == null) {
                missing = "MimeTypeService";
            }

            if (missing != null) {
                final String err = missing
                    + " service missing, cannot service requests";
                final int status = HttpServletResponse.SC_SERVICE_UNAVAILABLE;
                log.error("{} , sending status {}", err, status);
                sendError(status, err, null, servletRequest, servletResponse);
                return;
            }

            // get JCR Session
            session = (Session) servletRequest.getAttribute(SESSION);
            if (session == null) {
                log.error("service: Cannot handle request: Missing JCR Session");
                sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
                    "Missing JCR Session", null, servletRequest, servletResponse);
                return;
            }

            // initialize the request data - resolve resource and servlet
            ResourceResolver resolver = getResourceResolverFactory().getResourceResolver(
                session);
            Resource resource = requestData.initResource(resolver);
            requestData.initServlet(resource);

            Filter[] filters = requestFilterChain.getFilters();
            if (filters != null) {
                FilterChain processor = new RequestSlingFilterChain(this,
                    filters);

                processor.doFilter(request, response);

            } else {

                // no filters, directly call resource level filters and servlet
                processRequest(request, response);

            }

        } catch (ResourceNotFoundException rnfe) {

            // send this exception as a 404 status
            log.info("service: Resource {} not found", rnfe.getResource());

            getErrorHandler().handleError(HttpServletResponse.SC_NOT_FOUND,
                rnfe.getMessage(), request, response);

        } catch (SlingException se) {

            // if we have request data and a non-null active servlet name
            // we assume, that this is the name of the causing servlet
            if (requestData.getActiveServletName() != null) {
                request.setAttribute(ERROR_SERVLET_NAME,
                    requestData.getActiveServletName());
            }

            // send this exception as is (albeit unwrapping and wrapped
            // exception.
            Throwable t = (se.getCause() != null) ? se.getCause() : se;
            log.error("service: Uncaught SlingException", t);
            getErrorHandler().handleError(t, request, response);

        } catch (AccessControlException ace) {

            // SLING-319 if anything goes wrong, send 403/FORBIDDEN
            log.info(
                "service: Authenticated user {} does not have enough rights to executed requested action",
                request.getRemoteUser());
            getErrorHandler().handleError(HttpServletResponse.SC_FORBIDDEN,
                null, request, response);

        } catch (Throwable t) {

            // if we have request data and a non-null active servlet name
            // we assume, that this is the name of the causing servlet
            if (requestData.getActiveServletName() != null) {
                request.setAttribute(ERROR_SERVLET_NAME,
                    requestData.getActiveServletName());
            }

            log.error("service: Uncaught Throwable", t);
            getErrorHandler().handleError(t, request, response);

        } finally {

            // request exit log
            if (requestLogger != null) {
                requestLogger.logRequestExit(request, response);
            }

            // dispose any request data
            requestData.dispose();

            // logout the session we have got for this request
            if (session != null) {
                session.logout();
            }
        }
    }

    // ---------- Generic Content Request processor ----------------------------

    public void includeServlet(ServletRequest request,
            ServletResponse response, String path) throws IOException,
            ServletException {

        checkRecursionLevel(request, path);

        // check type of response, don't care actually for the response itself
        RequestData.unwrap(response);

        // get the request data (and btw check the correct type
        RequestData requestData = RequestData.getRequestData(request);

        RequestDispatcher rd = requestData.getServletRequest().getRequestDispatcher(
            path);
        if (rd != null) {
            rd.include(request, response);
        } else {
            log.error("includeServlet: Got no request dispatcher for {}", path);
        }
    }

    public void includeContent(ServletRequest request,
            ServletResponse response, Resource resource,
            RequestPathInfo resolvedURL) throws IOException, ServletException {

        checkRecursionLevel(request, resolvedURL.getResourcePath());

        // we need a SlingHttpServletRequest/SlingHttpServletResponse tupel to continue
        SlingHttpServletRequest cRequest = RequestData.toSlingHttpServletRequest(request);
        SlingHttpServletResponse cResponse = RequestData.toSlingHttpServletResponse(response);

        // get the request data (and btw check the correct type)
        RequestData requestData = RequestData.getRequestData(cRequest);
        ContentData contentData = requestData.pushContent(resource, resolvedURL);

        try {
            // resolve the servlet
            Servlet servlet = getServletResolver().resolveServlet(cRequest);
            contentData.setServlet(servlet);

            processRequest(cRequest, cResponse);
        } finally {
            requestData.popContent();
        }
    }

    /** Add a recursion counter to req and fail if it's too high */
    protected void checkRecursionLevel(ServletRequest request, String infothrows InfiniteIncludeLoopException {
        // Detect infinite loops
        Integer recursionLevel = (Integer)request.getAttribute(INCLUDE_COUNTER);
        if(recursionLevel == null) {
            recursionLevel = new Integer(1);
        } else if(recursionLevel.intValue() > MAX_INCLUDE_RECURSION_LEVEL) {
            throw new InfiniteIncludeLoopException(info);
        } else {
            recursionLevel = new Integer(recursionLevel.intValue() + 1);
        }
        request.setAttribute(INCLUDE_COUNTER, recursionLevel);
    }

    public void processRequest(SlingHttpServletRequest request,
            SlingHttpServletResponse response) throws IOException,
            ServletException {

        // 2.0 Set Content from mappedURL
        // ContentData contentData = null;

        Filter filters[] = innerFilterChain.getFilters();
        if (filters != null) {
            FilterChain processor = new SlingComponentFilterChain(filters);

            processor.doFilter(request, response);
        } else {
            log.debug("service: No Resource level filters, calling servlet");
            RequestData.service(request, response);
        }
    }

    // ---------- ErrorHandler interface (default implementation) --------------

    // reset the response, set the status and write a simple message
    public void handleError(int status, String message,
            SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws IOException {

        if (message == null) {
            message = "HTTP ERROR:" + String.valueOf(status);
        } else {
            message = "HTTP ERROR:" + status + " - " + message;
        }

        sendError(status, message, null, request, response);
    }

    // just rethrow the exception as explained in the class comment
    public void handleError(Throwable throwable,
            SlingHttpServletRequest request, SlingHttpServletResponse response)
            throws IOException {
        sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
            throwable.getMessage(), throwable, request, response);
    }

    private void sendError(int status, String message, Throwable throwable,
            HttpServletRequest request, HttpServletResponse response)
            throws IOException {

        if (response.isCommitted()) {
            log.error(
                "handleError: Response already committed; cannot send error "
                    + status + message, throwable);
        } else {

            // error situation
            String servletName = (String) request.getAttribute(ERROR_SERVLET_NAME);
            String requestURI = (String) request.getAttribute(ERROR_REQUEST_URI);
            if (requestURI == null) {
                requestURI = request.getRequestURI();
            }

            // reset anything in the response first
            response.reset();

            // set the status, content type and encoding
            response.setStatus(status);
            response.setContentType("text/html; charset=UTF-8");

            PrintWriter pw = response.getWriter();
            pw.println("<html><head><title>");
            pw.println(message);
            pw.println("</title></head><body><h1>");
            if(throwable != null) {
                pw.println(throwable.toString());
            } else if(message != null) {
                pw.println(message);
            } else {
                pw.println("Internal error (no Exception to report)");
            }
            pw.println("</h1><p>");
            pw.println("RequestURI=" + request.getRequestURI());
            if (servletName != null) {
                pw.println("</p>Servlet=" + servletName + "<p>");
            }
            pw.println("</p>");

            if (throwable != null) {
                pw.println("<pre>");
                throwable.printStackTrace(pw);
                pw.println("</pre>");
            }

            pw.println("<hr /><address>");
            pw.println(getServerInfo());
            pw.println("</address></body></html>");

            // commit the response
            response.flushBuffer();

        }
    }

    // ---------- Internal helper ----------------------------------------------

    public String getServerInfo() {
        return serverInfo;
    }

    public BundleContext getBundleContext() {
        return osgiComponentContext.getBundleContext();
    }

    public SlingAuthenticator getSlingAuthenticator() {
        return slingAuthenticator;
    }

    public JcrResourceResolverFactory getResourceResolverFactory() {
        return resourceResolverFactory;
    }

    public ServletResolver getServletResolver() {
        return servletResolver;
    }

    public ErrorHandler getErrorHandler() {
        ErrorHandler eh = errorHandler;
        return (eh != null) ? eh : this;
    }

    // ---------- Property Setter for SCR --------------------------------------

    protected void activate(ComponentContext componentContext) {

        osgiComponentContext = componentContext;

        // setup server info
        BundleContext bundleContext = componentContext.getBundleContext();
        Dictionary<?, ?> props = bundleContext.getBundle().getHeaders();
        Version bundleVersion = Version.parseVersion((String) props.get(Constants.BUNDLE_VERSION));
        String productVersion = bundleVersion.getMajor() + "."
            + bundleVersion.getMinor();
        this.serverInfo = PRODUCT_NAME + "/" + productVersion + " ("
            + System.getProperty("java.vm.name") + " "
            + System.getProperty("java.version") + "; "
            + System.getProperty("os.name") + " "
            + System.getProperty("os.version") + " "
            + System.getProperty("os.arch") + ")";

        // prepare the servlet configuration from the component config
        Hashtable<String, Object> configuration = new Hashtable<String, Object>();
        Dictionary<?, ?> componentConfig = componentContext.getProperties();
        for (Enumeration<?> cce = componentConfig.keys(); cce.hasMoreElements();) {
            Object key = cce.nextElement();
            configuration.put(String.valueOf(key), componentConfig.get(key));
        }

        // get the web manager root path
        Object wmr = configuration.get("sling.root");
        this.slingRoot = (wmr instanceof String) ? (String) wmr : null;
        if (this.slingRoot == null) {
            this.slingRoot = "/";
        } else if (!this.slingRoot.startsWith("/")) {
            this.slingRoot = "/" + this.slingRoot;
        }

        // ensure the servlet name
        if (!(configuration.get("servlet-name") instanceof String)) {
            configuration.put("servlet-name", PRODUCT_NAME + " "
                + productVersion);
        }

        // setup servlet request processing helpers
        SlingServletContext tmpServletContext = new SlingServletContext(this);
        slingAuthenticator = new SlingAuthenticator(bundleContext);

        // register the servlet and resources
        try {
            Dictionary<String, String> servletConfig = toStringConfig(configuration);

            this.httpService.registerServlet(this.slingRoot, this,
                servletConfig, this);

            log.info("{} ready to serve requests", this.getServerInfo());

        } catch (Exception e) {
            log.error("Cannot register " + this.getServerInfo(), e);
        }

        // register render filters already registered after registration with
        // the HttpService as filter initialization may cause the servlet
        // context to be required (see SLING-42)

        List<ServiceReference> filterList;
        synchronized (this) {
            filterList = delayedComponentFilters;

            // prepare the Sling Component Context now after having finished the
            // handler setup but before initializing the filters.
            // After leaving this synched block, bindFilter will be "active" and
            // set the delayedComponentFilters field to null for GC
            slingServletContext = tmpServletContext;
            delayedComponentFilters = null;
        }

        // if there are filters at all, initialize them now
        if (filterList != null) {
            for (ServiceReference serviceReference : filterList) {
                initFilter(componentContext, serviceReference);
            }
        }
    }

    protected void deactivate(ComponentContext componentContext) {

        // first of all, we have to unregister
        httpService.unregister(this.slingRoot);

        destroyFilters(innerFilterChain);
        destroyFilters(requestFilterChain);

        if (slingAuthenticator != null) {
            slingAuthenticator.dispose();
            slingAuthenticator = null;
        }

        if (slingServletContext != null) {
            slingServletContext.dispose();
            slingServletContext = null;
        }

        this.osgiComponentContext = null;

        log.info(this.getServerInfo() + " shut down");
    }

    protected void bindFilter(ServiceReference ref) {
        synchronized (this) {
            if (slingServletContext == null) {
                if (delayedComponentFilters == null) {
                    delayedComponentFilters = new ArrayList<ServiceReference>();
                }
                delayedComponentFilters.add(ref);
            } else {
                initFilter(osgiComponentContext, ref);
            }
        }
    }

    protected void unbindFilter(ServiceReference ref) {
        // service id
        Object serviceId = ref.getProperty(Constants.SERVICE_ID);

        // unregister by scope and destroy it
        Filter filter = getChain(ref).removeFilterById(serviceId);
        if (filter != null) {
            try {
                filter.destroy();
            } catch (Throwable t) {
                log.error("Unexpected problem destroying ComponentFilter {}",
                    filter, t);
            }
        }
    }

    private void initFilter(ComponentContext osgiContext, ServiceReference ref) {
        Filter filter = (Filter) osgiContext.locateService(FILTER_NAME, ref);

        // require a name for the filter
        String filterName = AbstractServiceReferenceConfig.getName(ref);
        if (filterName == null) {
            log.error("initFilter: Missing name for filter {}", ref);
            return;
        }

        // initialize the filter first
        try {
            FilterConfig config = new SlingFilterConfig(slingServletContext,
                ref, filterName);
            filter.init(config);

            // service id
            Long serviceId = (Long) ref.getProperty(Constants.SERVICE_ID);

            // get the order, Integer.MAX_VALUE by default
            Object orderObj = ref.getProperty("filter.order");
            int order = (orderObj instanceof Integer)
                    ? ((Integer) orderObj).intValue()
                    : Integer.MAX_VALUE;

            // register by scope
            getChain(ref).addFilter(filter, serviceId, order);

        } catch (ServletException ce) {
            log.error("Filter " + filterName + " failed to initialize", ce);
        } catch (Throwable t) {
            log.error("Unexpected Problem initializing ComponentFilter " + "",
                t);
        }
    }

    private void destroyFilters(SlingFilterChainHelper chain) {
        Filter[] filters = chain.removeAllFilters();
        if (filters != null) {
            for (int i = 0; i < filters.length; i++) {
                try {
                    filters[i].destroy();
                } catch (Throwable t) {
                    log.error(
                        "Unexpected problem destroying ComponentFilter {}",
                        filters[i], t);
                }
            }
        }
    }

    private SlingFilterChainHelper getChain(ServiceReference ref) {

        // component rendering filter
        Object scope = ref.getProperty("filter.scope");
        if ("component".equals(scope)) {
            return innerFilterChain;
        }

        // global filter by default
        return requestFilterChain;
    }

    private Dictionary<String, String> toStringConfig(Dictionary<?, ?> config) {
        Dictionary<String, String> stringConfig = new Hashtable<String, String>();
        for (Enumeration<?> ke = config.keys(); ke.hasMoreElements();) {
            Object key = ke.nextElement();
            stringConfig.put(key.toString(), String.valueOf(config.get(key)));
        }
        return stringConfig;
    }

    //---------- HttpContext interface ----------------------------------------

    public String getMimeType(String name) {
        MimeTypeService mtservice = mimeTypeService;
        if (mtservice != null) {
            return mtservice.getMimeType(name);
        }

        log.debug(
            "getMimeType: MimeTypeService not available, cannot resolve mime type for {}",
            name);
        return null;
    }

    public URL getResource(String name) {
        return null;
    }

    /**
     * Tries to authenticate the request using the
     * <code>SlingAuthenticator</code>. If the authenticator or the
     * Repository is missing this method returns <code>false</code> and sends
     * a 503/SERVICE UNAVAILABLE status back to the client.
     */
    public boolean handleSecurity(HttpServletRequest request,
            HttpServletResponse response) throws IOException {

        SlingAuthenticator authenticator = slingAuthenticator;
        if (authenticator != null) {
            try {

                return authenticator.authenticate(request, response);

            } catch (MissingRepositoryException mre) {

                log.error("handleSecurity: Cannot authenticate request: "
                    + mre.getMessage());
                log.debug("handleSecurity: Reason", mre);

                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                    "Cannot handle requests due to missing Repository");
            }

        } else {

            response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,
                "Sling not ready to serve requests");

        }

        // fall back to security failure and request termination
        return false;
    }

}
TOP

Related Classes of org.apache.sling.engine.impl.SlingMainServlet

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.