ch.entwine.weblounge.dispatcher.impl.WebloungeDispatcherServlet.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.dispatcher.impl.WebloungeDispatcherServlet.java

Source

/*
 * Weblounge: Web Content Management System Copyright (c) 2009 The Weblounge
 * Team http://entwinemedia.com/weblounge
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any
 * later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package ch.entwine.weblounge.dispatcher.impl;

import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;

import ch.entwine.weblounge.cache.CacheService;
import ch.entwine.weblounge.common.impl.request.RequestUtils;
import ch.entwine.weblounge.common.impl.request.WebloungeRequestImpl;
import ch.entwine.weblounge.common.impl.request.WebloungeResponseImpl;
import ch.entwine.weblounge.common.request.RequestListener;
import ch.entwine.weblounge.common.request.ResponseCache;
import ch.entwine.weblounge.common.request.WebloungeRequest;
import ch.entwine.weblounge.common.request.WebloungeResponse;
import ch.entwine.weblounge.common.security.SecurityService;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.dispatcher.DispatchListener;
import ch.entwine.weblounge.dispatcher.RequestHandler;
import ch.entwine.weblounge.dispatcher.SiteDispatcherService;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This is the main dispatcher for weblounge, every request starts and ends
 * here. Using the <code>HttpServiceTracker</code>, the servlet is registered
 * with an instance of the OSGi web service.
 * <p>
 * The servlet is also where you enable and disable response caching by calling
 * <code>setResponseCache()</code> with the appropriate implementation
 * reference.
 */
public final class WebloungeDispatcherServlet extends HttpServlet {

    /** Serial version uid */
    private static final long serialVersionUID = 8939686825567275614L;

    /** Logger */
    private static final Logger logger = LoggerFactory.getLogger(WebloungeDispatcherServlet.class);

    /** Value of the X-Powered-By header */
    private static final String POWERED_BY = "Weblounge Content Management System";

    /** Response default encoding */
    private static final String DEFAULT_RESPONSE_ENCODING = "UTF-8";

    /** The environment */
    private Environment environment = Environment.Production;

    /** The sites that are online */
    private transient SiteDispatcherService sites = null;

    /** The security service */
    private SecurityService securityService = null;

    /** List of request listeners */
    private List<RequestListener> requestListeners = null;

    /** List of dispatcher listeners */
    private List<DispatchListener> dispatcher = null;

    /** List of dispatcher listeners */
    private Set<RequestHandler> requestHandler = null;

    /** List with well known urls and files */
    private static List<String> wellknownFiles = new ArrayList<String>();

    /** List of offline sites that have already issued a warning once */
    private final Set<String> missingRepositoryWarnings = new TreeSet<String>();

    /** List of missing sites that have already issued a warning once */
    private final Set<String> missingSiteWarnings = new TreeSet<String>();

    /** The response caches */
    private Map<String, ResponseCache> caches = null;

    /** Name of this Weblounge instance */
    private String instanceName = null;

    static {
        wellknownFiles.add("/favicon.ico");
        wellknownFiles.add("/robots.txt");
    }

    /**
     * Creates a new instance of the weblounge dispatcher servlet.
     * 
     * @param environment
     *          the environment
     */
    WebloungeDispatcherServlet(Environment environment) {
        this.environment = environment;
        requestListeners = new CopyOnWriteArrayList<RequestListener>();
        dispatcher = new CopyOnWriteArrayList<DispatchListener>();
        requestHandler = new TreeSet<RequestHandler>(new Comparator<RequestHandler>() {
            public int compare(RequestHandler handler1, RequestHandler handler2) {
                int compare = Integer.valueOf(handler2.getPriority())
                        .compareTo(Integer.valueOf(handler1.getPriority()));
                // FIXME if 0 is returned the request handler will not be added?!
                if (compare == 0)
                    return 1;
                return compare;
            }
        });
        caches = new HashMap<String, ResponseCache>();
    }

    /**
     * Sets the name of this Weblounge instance or <code>null</code> if no
     * instance name was configured.
     * 
     * @param instanceName
     *          name of the instance
     */
    public void setName(String instanceName) {
        this.instanceName = instanceName;
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        super.doGet(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        super.doDelete(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#doHead(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doHead(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        super.doHead(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#doOptions(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doOptions(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        super.doOptions(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        super.doPost(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        super.doPut(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#doTrace(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void doTrace(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        super.doTrace(request, response);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)
     */
    @Override
    protected long getLastModified(HttpServletRequest req) {
        return super.getLastModified(req);
    }

    /**
     * {@inheritDoc}
     * 
     * @see javax.servlet.http.HttpServlet#service(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse)
     */
    @Override
    protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
            throws ServletException, IOException {

        // Return the instance name if available
        if (instanceName != null) {
            httpResponse.addHeader("X-Weblounge-Instance", instanceName);
        }

        if (sites == null) {
            httpResponse.sendError(SC_SERVICE_UNAVAILABLE);
            return;
        }

        logger.debug("Serving {}", httpRequest.getRequestURI());

        // Get the site dispatcher
        Site site = securityService.getSite();
        if (site == null) {
            site = getSiteByRequest(httpRequest);
            securityService.setSite(site);
        }

        boolean isSpecialRequest = StringUtils.isNotBlank(httpRequest.getHeader("X-Weblounge-Special"));

        // See if a site dispatcher was found, and if so, if it's enabled
        if (site == null) {
            String serverName = httpRequest.getScheme() + "://" + httpRequest.getServerName();
            if (httpRequest.getServerPort() != 80)
                serverName += ":" + httpRequest.getServerPort();
            if (!wellknownFiles.contains(httpRequest.getRequestURI())
                    && !missingSiteWarnings.contains(serverName)) {
                missingSiteWarnings.add(serverName);
                logger.warn("No site found to handle {}", serverName);
            }
            httpResponse.sendError(SC_NOT_FOUND);
            return;
        } else if (!site.isOnline() && !isSpecialRequest) {
            if (site.getContentRepository() == null) {
                if (!missingRepositoryWarnings.contains(site.getIdentifier())) {
                    logger.warn("No content repository connected to site '{}'", site);
                    missingRepositoryWarnings.add(site.getIdentifier());
                } else {
                    logger.debug("No content repository connected to site '{}'", site);
                }
            } else {
                logger.debug("Ignoring request for disabled site '{}'", site);
            }
            httpResponse.sendError(SC_SERVICE_UNAVAILABLE);
            return;
        }

        // Make sure the response is buffered
        httpResponse = new BufferedHttpServletResponse(httpResponse);

        // Get the servlet that is responsible for the site's content
        Servlet siteServlet = sites.getSiteServlet(site);

        // Get the response cache, if available
        ResponseCache cache = caches.get(site.getIdentifier());

        // Wrap for caching
        if (cache != null) {
            httpResponse = cache.createCacheableResponse(httpRequest, httpResponse);
        }

        // Wrap request and response
        WebloungeRequestImpl request = new WebloungeRequestImpl(httpRequest, siteServlet, environment);
        WebloungeResponseImpl response = new WebloungeResponseImpl(httpResponse);

        // Configure request and response objects
        request.init(site);
        response.setRequest(request);
        response.setResponseCache(cache);
        response.setCharacterEncoding(DEFAULT_RESPONSE_ENCODING);
        response.setHeader("X-Powered-By", POWERED_BY);
        response.setDateHeader("Date", Calendar.getInstance().getTimeInMillis());

        // Notify listeners about starting request
        fireRequestStarted(request, response, site);

        boolean requestServed = false;

        // Ask the registered request handler if they are willing to handle
        // the request.
        try {
            securityService.setSite(site);
            request.setUser(securityService.getUser());
            for (RequestHandler handler : requestHandler) {
                try {
                    logger.trace("Asking {} to serve {}", handler, request);
                    if (handler.service(request, response)) {
                        requestServed = true;
                        logger.debug("{} served request {}", handler, request);
                        if (response.hasError()) {
                            logger.debug("Request processing failed on {}", request);
                            fireRequestFailed(request, response, site);
                        } else {
                            fireRequestDelivered(request, response, site);
                        }
                        return;
                    }
                } catch (Throwable t) {
                    response.invalidate();
                    String params = RequestUtils.dumpParameters(request);
                    if (t.getCause() != null) {
                        t = t.getCause();
                    }
                    logger.error("Request handler '{}' failed to handle {} {}",
                            new Object[] { handler, request, params });
                    logger.error(t.getMessage(), t);
                    DispatchUtils.sendInternalError(t.getMessage(), request, response);
                    break;
                }
            }
        } finally {
            securityService.setSite(null);
            if (requestServed) {
                response.endResponse();
                response.flushBuffer();
                logger.debug("Finished processing of {}", httpRequest.getRequestURI());
            } else {
                logger.debug("No handler found for {}", request);
                DispatchUtils.sendNotFound(request, response);
                if (cache != null)
                    cache.invalidate(response);
                fireRequestFailed(request, response, site);
            }
        }
    }

    /**
     * Returns the site that is being targeted by <code>request</code>.
     * 
     * @param request
     *          the http request
     * @return the target site or <code>null</code>
     */
    private Site getSiteByRequest(HttpServletRequest request) {
        if (sites == null)
            return null;
        Site site = sites.findSiteByRequest(request);
        return site;
    }

    /**
     * Adds <code>listener</code> to the list of request listeners if it has not
     * already been registered. The listener is called a few times during the life
     * cycle of a request.
     * 
     * @param listener
     *          the lister
     */
    void addRequestListener(RequestListener listener) {
        if (!requestListeners.contains(listener)) {
            requestListeners.add(listener);
        }
    }

    /**
     * Removes the listener from the list of request listeners.
     * 
     * @param listener
     *          the listener to remove
     */
    void removeRequestListener(RequestListener listener) {
        requestListeners.remove(listener);
    }

    /**
     * Adds <code>listener</code> to the list of dispatch listeners if it has not
     * already been registered. The listener is called every time the current
     * request is internally being included or forwarded using the
     * <code>include</code> or <code>forward</code> method of this class.
     * 
     * @param listener
     *          the lister
     */
    void addDispatchListener(DispatchListener listener) {
        if (!dispatcher.contains(listener)) {
            dispatcher.add(listener);
        }
    }

    /**
     * Removes the listener from the list of request listeners.
     * 
     * @param listener
     *          the listener to remove
     */
    void removeDispatchListener(DispatchListener listener) {
        dispatcher.remove(listener);
    }

    /**
     * Adds <code>handler</code> to the list of request handler if it has not
     * already been registered. The handler is called every time a request enters
     * the system.
     * 
     * @param handler
     *          the request handler
     */
    void addRequestHandler(RequestHandler handler) {
        requestHandler.add(handler);
        requestHandler.size();
    }

    /**
     * Removes the request handler from the list of handlers.
     * 
     * @param handler
     *          the request handler to remove
     */
    void removeRequestHandler(RequestHandler handler) {
        requestHandler.remove(handler);
    }

    /**
     * Registers the response cache with the main dispatcher servlet.
     * 
     * @param cache
     *          the response cache
     */
    void addResponseCache(CacheService cache) {
        caches.put(cache.getIdentifier(), cache);
        logger.info("Response caching activated for site '{}'", cache.getIdentifier());
    }

    /**
     * Removes the response cache from the main dispatcher servlet.
     * 
     * @param cache
     *          the response cache
     */
    void removeResponseCache(CacheService cache) {
        caches.remove(cache.getIdentifier());
        logger.info("Response caching deactivated for site '{}'", cache.getIdentifier());
    }

    /**
     * Method to fire a <code>startRequest()</code> message to all registered
     * <code>RequestListeners</code>.
     * 
     * @param request
     *          the starting request
     * @param response
     *          the servlet response
     * @param site
     *          the target site
     */
    protected void fireRequestStarted(WebloungeRequest request, WebloungeResponse response, Site site) {
        for (int i = 0; i < requestListeners.size(); i++) {
            RequestListener listener = requestListeners.get(i);
            listener.requestStarted(request, response);
        }
    }

    /**
     * Method to fire a <code>requestDelivered()</code> message to all registered
     * <code>RequestListeners</code>.
     * 
     * @param request
     *          the delivered request
     * @param response
     *          the servlet response
     * @param site
     *          the target site
     */
    protected void fireRequestDelivered(WebloungeRequest request, WebloungeResponse response, Site site) {
        for (int i = 0; i < requestListeners.size(); i++) {
            RequestListener listener = requestListeners.get(i);
            listener.requestDelivered(request, response);
        }
    }

    /**
     * Method to fire a <code>requestFailed()</code> message to all registered
     * <code>RequestListeners</code>.
     * 
     * @param request
     *          the failing request
     * @param response
     *          the servlet response
     * @param site
     *          the target site
     */
    protected void fireRequestFailed(WebloungeRequest request, WebloungeResponse response, Site site) {
        WebloungeResponseImpl resp = ((WebloungeResponseImpl) response);
        for (int i = 0; i < requestListeners.size(); i++) {
            RequestListener listener = requestListeners.get(i);
            listener.requestFailed(request, response, resp.getStatus());
        }
        if (site != null)
            site.requestFailed(request, response, resp.getStatus());
    }

    /**
     * Sets the environment that is used to determine the correct context for
     * requests.
     * 
     * @param environment
     *          the environment
     */
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    /**
     * Sets the site locator.
     * 
     * @param siteDispatcher
     *          the site locator
     */
    void setSiteDispatcher(SiteDispatcherService siteDispatcher) {
        this.sites = siteDispatcher;
    }

    /**
     * Removes the site locator.
     * 
     * @param siteDispatcher
     *          the site locator
     */
    void removeSiteDispatcher(SiteDispatcherService siteDispatcher) {
        this.sites = null;
    }

    /**
     * Sets the security service.
     * 
     * @param securityService
     *          the security service
     */
    void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Weblounge dispatcher servlet";
    }

}