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 java.io.IOException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.servlet.GenericServlet;
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.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyUnbounded;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.References;
import org.apache.sling.api.adapter.AdapterManager;
import org.apache.sling.api.request.SlingRequestEvent;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.ServletResolver;
import org.apache.sling.auth.core.AuthenticationSupport;
import org.apache.sling.commons.mime.MimeTypeService;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.engine.SlingRequestProcessor;
import org.apache.sling.engine.impl.filter.ServletFilterManager;
import org.apache.sling.engine.impl.helper.RequestListenerManager;
import org.apache.sling.engine.impl.helper.SlingServletContext;
import org.apache.sling.engine.impl.helper.SlingServletContext3;
import org.apache.sling.engine.impl.request.RequestData;
import org.apache.sling.engine.impl.request.RequestHistoryConsolePlugin;
import org.apache.sling.engine.jmx.RequestProcessorMBean;
import org.apache.sling.engine.servlets.ErrorHandler;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.Version;
import org.osgi.service.http.HttpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* The <code>SlingMainServlet</code> TODO
*/
@SuppressWarnings("serial")
@Component(immediate = true, metatype = true, label = "%sling.name", description = "%sling.description")
@Properties( {
    @Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation"),
    @Property(name = Constants.SERVICE_DESCRIPTION, value = "Sling Servlet")
})
@References( {
    @Reference(name = "ErrorHandler", referenceInterface = ErrorHandler.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC, bind = "setErrorHandler", unbind = "unsetErrorHandler"),
    @Reference(name = "ServletResolver", referenceInterface = ServletResolver.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC, bind = "setServletResolver", unbind = "unsetServletResolver"),
    @Reference(name = "MimeTypeService", referenceInterface = MimeTypeService.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC, bind = "setMimeTypeService", unbind = "unsetMimeTypeService"),
    @Reference(name = "AuthenticationSupport", referenceInterface = AuthenticationSupport.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC, bind = "setAuthenticationSupport", unbind = "unsetAuthenticationSupport") })
public class SlingMainServlet extends GenericServlet {

    @Property(intValue=RequestData.DEFAULT_MAX_CALL_COUNTER)
    public static final String PROP_MAX_CALL_COUNTER = "sling.max.calls";

    @Property(intValue=RequestData.DEFAULT_MAX_INCLUSION_COUNTER)
    public static final String PROP_MAX_INCLUSION_COUNTER = "sling.max.inclusions";

    public static final boolean DEFAULT_ALLOW_TRACE = false;

    @Property(boolValue=DEFAULT_ALLOW_TRACE)
    public static final String PROP_ALLOW_TRACE = "sling.trace.allow";

    public static final boolean DEFAULT_FILTER_COMPAT_MODE = false;

    @Property(boolValue=DEFAULT_FILTER_COMPAT_MODE)
    public static final String PROP_FILTER_COMPAT_MODE = "sling.filter.compat.mode";

    @Property(intValue = RequestHistoryConsolePlugin.STORED_REQUESTS_COUNT)
    private static final String PROP_MAX_RECORD_REQUESTS = "sling.max.record.requests";

    @Property(unbounded=PropertyUnbounded.ARRAY)
    private static final String PROP_TRACK_PATTERNS_REQUESTS = "sling.store.pattern.requests";

    private static final String PROP_DEFAULT_PARAMETER_ENCODING = "sling.default.parameter.encoding";

    @Property
    private static final String PROP_SERVER_INFO = "sling.serverinfo";
   

    @Property(value = {"X-Content-Type-Options=nosniff"},
            label = "Additional response headers",
            description = "Provides mappings for additional response headers "
                + "Each entry is of the form 'bundleId [ \":\" responseHeaderName ] \"=\" responseHeaderValue' ",
            unbounded = PropertyUnbounded.ARRAY)
    private static final String PROP_ADDITIONAL_RESPONSE_HEADERS = "sling.additional.response.headers";

    @Reference
    private HttpService httpService;

    @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC)
    private volatile AdapterManager adapterManager;

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

    /**
     * The registration path for the SlingMainServlet is hard wired to always
     * be the root, aka "<code>/</code>" (value is "/").
     */
    private static final String SLING_ROOT = "/";

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

    private SlingServletContext slingServletContext;

    /**
     * The product information part of the {@link #serverInfo} returns from the
     * <code>ServletContext.getServerInfo()</code> method. This field defaults
     * to {@link #PRODUCT_NAME} and is amended with the major and minor version
     * of the Sling Engine bundle while this component is being
     * {@link #activate(BundleContext, Map)} activated}.
     */
    private String productInfo = PRODUCT_NAME;

    /**
     * The server information to report in the {@link #getServerInfo()} method.
     * By default this is just the {@link #PRODUCT_NAME} (same as
     * {@link #productInfo}. During {@link #activate(BundleContext, Map)}
     * activation} the field is updated with the full {@link #productInfo} value
     * as well as the operating system and java version it is running on.
     * Finally during servlet initialization the product information from the
     * servlet container's server info is added to the comment section.
     */
    private String serverInfo = PRODUCT_NAME;

    private RequestListenerManager requestListenerManager;

    private boolean allowTrace = DEFAULT_ALLOW_TRACE;

    private Object printerRegistration;

    // new properties

    private SlingHttpContext slingHttpContext = new SlingHttpContext();

    private ServletFilterManager filterManager;

    private final SlingRequestProcessorImpl requestProcessor = new SlingRequestProcessorImpl();

    private ServiceRegistration requestProcessorRegistration;

    private ServiceRegistration requestProcessorMBeanRegistration;

    private String configuredServerInfo;
   
    // ---------- Servlet API -------------------------------------------------

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

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

            HttpServletRequest request = (HttpServletRequest) req;

            // set the thread name according to the request
            String threadName = setThreadName(request);

            requestListenerManager.sendEvent( request, SlingRequestEvent.EventType.EVENT_INIT );

            ResourceResolver resolver = null;
            try {
                if (!allowTrace && "TRACE".equals(request.getMethod())) {
                    HttpServletResponse response = (HttpServletResponse) res;
                    response.setStatus(405);
                    response.setHeader("Allow", "GET, HEAD, POST, PUT, DELETE, OPTIONS");
                    return;
                }

                // get ResourceResolver (set by AuthenticationSupport)
                Object resolverObject = request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);
                resolver = (resolverObject instanceof ResourceResolver)
                        ? (ResourceResolver) resolverObject
                        : null;

                // real request handling for HTTP requests
                requestProcessor.doProcessRequest(request, (HttpServletResponse) res,
                    resolver);

            } catch (IOException ioe) {

                // SLING-3498: Jetty with NIO does not have a wrapped
                // SocketException any longer but a plain IOException
                // from the NIO Socket channel. Hence we don't care for
                // unwrapping and just log at DEBUG level
                log.debug("service: Probably client aborted request or any other network problem", 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);

            } finally {


                // close the resource resolver (not relying on servlet request
                // listener to do this for now; see SLING-1270)
                if (resolver != null) {
                    resolver.close();
                }

                requestListenerManager.sendEvent( request, SlingRequestEvent.EventType.EVENT_DESTROY );

                // reset the thread name
                if (threadName != null) {
                    Thread.currentThread().setName(threadName);
                }
            }

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

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

    /**
     * Sets the {@link #productInfo} field from the providing bundle's version
     * and the {@link #PRODUCT_NAME}.
     * <p>
     * Also {@link #setServerInfo() updates} the {@link #serverInfo} based
     * on the product info calculated.
     *
     * @param bundleContext Provides access to the "Bundle-Version" manifest
     *            header of the containing bundle.
     */
    private void setProductInfo(final BundleContext bundleContext) {
        final Dictionary<?, ?> props = bundleContext.getBundle().getHeaders();
        final Version bundleVersion = Version.parseVersion((String) props.get(Constants.BUNDLE_VERSION));
        final String productVersion = bundleVersion.getMajor() + "."
            + bundleVersion.getMinor();
        this.productInfo = PRODUCT_NAME + "/" + productVersion;

        // update the server info
        this.setServerInfo();
    }

    public String getServerInfo() {
        return serverInfo;
    }

    /**
     * Sets up the server info to be returned for the
     * <code>ServletContext.getServerInfo()</code> method for servlets and
     * filters deployed inside Sling. The {@link SlingRequestProcessor} instance
     * is also updated with the server information.
     * <p>
     * This server info is either configured through an OSGi configuration or
     * it is made up of the following components:
     * <ol>
     * <li>The {@link #productInfo} field as the primary product information</li>
     * <li>The primary product information of the servlet container into which
     * the Sling Main Servlet is deployed. If the servlet has not yet been
     * deployed this will show as <i>unregistered</i>. If the servlet container
     * does not provide a server info this will show as <i>unknown</i>.</li>
     * <li>The name and version of the Java VM as reported by the
     * <code>java.vm.name</code> and <code>java.vm.version</code> system
     * properties</li>
     * <li>The name, version, and architecture of the OS platform as reported by
     * the <code>os.name</code>, <code>os.version</code>, and
     * <code>os.arch</code> system properties</li>
     * </ol>
     */
    private void setServerInfo() {
        if ( this.configuredServerInfo != null ) {
            this.serverInfo = this.configuredServerInfo;
        } else {
            final String containerProductInfo;
            if (getServletConfig() == null || getServletContext() == null) {
                containerProductInfo = "unregistered";
            } else {
                final String containerInfo = getServletContext().getServerInfo();
                if (containerInfo != null && containerInfo.length() > 0) {
                    int lbrace = containerInfo.indexOf('(');
                    if (lbrace < 0) {
                        lbrace = containerInfo.length();
                    }
                    containerProductInfo = containerInfo.substring(0, lbrace).trim();
                } else {
                    containerProductInfo = "unknown";
                }
            }

            this.serverInfo = String.format("%s (%s, %s %s, %s %s %s)",
                this.productInfo, containerProductInfo,
                System.getProperty("java.vm.name"),
                System.getProperty("java.version"), System.getProperty("os.name"),
                System.getProperty("os.version"), System.getProperty("os.arch"));
        }
        if (this.requestProcessor != null) {
            this.requestProcessor.setServerInfo(serverInfo);
        }
    }

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

    @Activate
    protected void activate(final BundleContext bundleContext,
            final Map<String, Object> componentConfig) {
       
        final String[] props = PropertiesUtil.toStringArray(componentConfig.get(PROP_ADDITIONAL_RESPONSE_HEADERS));
       
        final ArrayList<StaticResponseHeader> mappings = new ArrayList<StaticResponseHeader>(props.length);
        for (final String prop : props) {
            if (prop != null && prop.trim().length() > 0 ) {
                try {
                    final StaticResponseHeader mapping = new StaticResponseHeader(prop.trim());
                    mappings.add(mapping);
                } catch (final IllegalArgumentException iae) {
                    log.info("configure: Ignoring '{}': {}", prop, iae.getMessage());
                }
            }
        }
        RequestData.setAdditionalResponseHeaders(mappings);
       
        configuredServerInfo = PropertiesUtil.toString(componentConfig.get(PROP_SERVER_INFO), null);

        // setup server info
        setProductInfo(bundleContext);

        // prepare the servlet configuration from the component config
        final Hashtable<String, Object> configuration = new Hashtable<String, Object>(
            componentConfig);

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

        // configure method filter
        allowTrace = PropertiesUtil.toBoolean(componentConfig.get(PROP_ALLOW_TRACE),
                DEFAULT_ALLOW_TRACE);

        // configure the request limits
        RequestData.setMaxIncludeCounter(PropertiesUtil.toInteger(
            componentConfig.get(PROP_MAX_INCLUSION_COUNTER),
            RequestData.DEFAULT_MAX_INCLUSION_COUNTER));
        RequestData.setMaxCallCounter(PropertiesUtil.toInteger(
            componentConfig.get(PROP_MAX_CALL_COUNTER),
            RequestData.DEFAULT_MAX_CALL_COUNTER));
        RequestData.setSlingMainServlet(this);

        // configure default request parameter encoding
        // log a message if such configuration exists ....
        if (componentConfig.get(PROP_DEFAULT_PARAMETER_ENCODING) != null) {
            log.warn("Configure default request parameter encoding with 'org.apache.sling.parameters.config' configuration; the property "
                + PROP_DEFAULT_PARAMETER_ENCODING
                + "="
                + componentConfig.get(PROP_DEFAULT_PARAMETER_ENCODING)
                + " is ignored");
        }

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

            this.httpService.registerServlet(SLING_ROOT, this, servletConfig,
                slingHttpContext);

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

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

        // now that the sling main servlet is registered with the HttpService
        // and initialized we can register the servlet context
        if (getServletContext() == null || getServletContext().getMajorVersion() < 3) {
            slingServletContext = new SlingServletContext(bundleContext, this);
        } else {
            slingServletContext = new SlingServletContext3(bundleContext, this);
        }

        // register render filters already registered after registration with
        // the HttpService as filter initialization may cause the servlet
        // context to be required (see SLING-42)
        filterManager = new ServletFilterManager(bundleContext,
            slingServletContext,
            PropertiesUtil.toBoolean(componentConfig.get(PROP_FILTER_COMPAT_MODE), DEFAULT_FILTER_COMPAT_MODE));
        filterManager.open();
        requestProcessor.setFilterManager(filterManager);

        // initialize requestListenerManager
        requestListenerManager = new RequestListenerManager( bundleContext, slingServletContext );

        // Setup configuration printer
        this.printerRegistration = WebConsoleConfigPrinter.register(bundleContext, filterManager);

        // setup the request info recorder
        try {
            int maxRequests = PropertiesUtil.toInteger(
                componentConfig.get(PROP_MAX_RECORD_REQUESTS),
                RequestHistoryConsolePlugin.STORED_REQUESTS_COUNT);
            String[] patterns = PropertiesUtil.toStringArray(componentConfig.get(PROP_TRACK_PATTERNS_REQUESTS), new String[0]);
            List<Pattern> compiledPatterns = new ArrayList<Pattern>(patterns.length);
            for (String pattern : patterns) {
                if(pattern != null && pattern.trim().length() > 0) {
                    compiledPatterns.add(Pattern.compile(pattern));
                }
            }
            RequestHistoryConsolePlugin.initPlugin(bundleContext, maxRequests, compiledPatterns);
        } catch (Throwable t) {
            log.debug(
                "Unable to register web console request recorder plugin.", t);
        }

        try {
            Dictionary<String, String> mbeanProps = new Hashtable<String, String>();
            mbeanProps.put("jmx.objectname", "org.apache.sling:type=engine,service=RequestProcessor");

            RequestProcessorMBeanImpl mbean = new RequestProcessorMBeanImpl();
            requestProcessorMBeanRegistration = bundleContext.registerService(RequestProcessorMBean.class.getName(), mbean, mbeanProps);
            requestProcessor.setMBean(mbean);
        } catch (Throwable t) {
            log.debug("Unable to register mbean");
        }

        // provide the SlingRequestProcessor service
        Hashtable<String, String> srpProps = new Hashtable<String, String>();
        srpProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
        srpProps.put(Constants.SERVICE_DESCRIPTION, "Sling Request Processor");
        requestProcessorRegistration = bundleContext.registerService(
            SlingRequestProcessor.NAME, requestProcessor, srpProps);
    }

    @Override
    public void init() {
        setServerInfo();
    }

    @Deactivate
    protected void deactivate() {
        // unregister the sling request processor
        if (requestProcessorRegistration != null) {
            requestProcessorRegistration.unregister();
            requestProcessorRegistration = null;
        }

        if (requestProcessorMBeanRegistration != null) {
            requestProcessorMBeanRegistration.unregister();
            requestProcessorMBeanRegistration = null;
        }

        // unregister request recorder plugin
        try {
            RequestHistoryConsolePlugin.destroyPlugin();
        } catch (Throwable t) {
            log.debug(
                "Problem unregistering web console request recorder plugin.", t);
        }

        // this reverses the activation setup
        if ( this.printerRegistration != null ) {
            WebConsoleConfigPrinter.unregister(this.printerRegistration);
            this.printerRegistration = null;
        }
        // destroy servlet filters before destroying the sling servlet
        // context because the filters depend on that context
        if (filterManager != null) {
            requestProcessor.setFilterManager(null);
            filterManager.close();
        }

        // second unregister the servlet context *before* unregistering
        // and destroying the the sling main servlet
        if (slingServletContext != null) {
            slingServletContext.dispose();
            slingServletContext = null;
        }

        // third unregister and destroy the sling main servlet
        httpService.unregister(SLING_ROOT);

        // dispose of request listener manager after unregistering the servlet
        // to prevent a potential NPE in the service method
        if ( this.requestListenerManager != null ) {
            this.requestListenerManager.dispose();
            this.requestListenerManager = null;
        }

        // reset the sling main servlet reference (help GC and be nice)
        RequestData.setSlingMainServlet(null);

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

    void setErrorHandler(final ErrorHandler errorHandler) {
        requestProcessor.setErrorHandler(errorHandler);
    }

    void unsetErrorHandler(final ErrorHandler errorHandler) {
        requestProcessor.unsetErrorHandler(errorHandler);
    }

    public void setServletResolver(final ServletResolver servletResolver) {
        requestProcessor.setServletResolver(servletResolver);
    }

    public void unsetServletResolver(final ServletResolver servletResolver) {
        requestProcessor.unsetServletResolver(servletResolver);
    }

    public void setMimeTypeService(final MimeTypeService mimeTypeService) {
        slingHttpContext.setMimeTypeService(mimeTypeService);
    }

    public void unsetMimeTypeService(final MimeTypeService mimeTypeService) {
        slingHttpContext.unsetMimeTypeService(mimeTypeService);
    }

    public void setAuthenticationSupport(
            final AuthenticationSupport authenticationSupport) {
        slingHttpContext.setAuthenticationSupport(authenticationSupport);
    }

    public void unsetAuthenticationSupport(
            final AuthenticationSupport authenticationSupport) {
        slingHttpContext.unsetAuthenticationSupport(authenticationSupport);
    }

    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) {
        return slingHttpContext.getMimeType(name);
    }

    public <Type> Type adaptTo(Object object, Class<Type> type) {
        AdapterManager adapterManager = this.adapterManager;
        if (adapterManager != null) {
            return adapterManager.getAdapter(object, type);
        }

        // no adapter manager, nothing to adapt to
        return null;
    }

    /**
     * Sets the name of the current thread to the IP address of the remote
     * client with the current system time and the first request line consisting
     * of the method, path and protocol.
     *
     * @param request The request to extract the remote IP address, method,
     *            request URL and protocol from.
     * @return The name of the current thread before setting the new name.
     */
    private String setThreadName(HttpServletRequest request) {

        // get the name of the current thread (to be returned)
        Thread thread = Thread.currentThread();
        String oldThreadName = thread.getName();

        // construct and set the new thread name of the form:
        // 127.0.0.1 [1224156108055] GET /system/console/config HTTP/1.1
        final StringBuilder buf = new StringBuilder();
        buf.append(request.getRemoteAddr());
        buf.append(" [").append(System.currentTimeMillis()).append("] ");
        buf.append(request.getMethod()).append(' ');
        buf.append(request.getRequestURI()).append(' ');
        buf.append(request.getProtocol());
        thread.setName(buf.toString());

        // return the previous thread name
        return oldThreadName;
    }
}
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.