ch.entwine.weblounge.dispatcher.impl.handler.PageRequestHandlerImpl.java Source code

Java tutorial

Introduction

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

Source

/*
 *  Weblounge: Web Content Management System
 *  Copyright (c) 2003 - 2011 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.handler;

import static ch.entwine.weblounge.common.request.RequestFlavor.ANY;
import static ch.entwine.weblounge.common.request.RequestFlavor.HTML;

import ch.entwine.weblounge.common.content.Renderer;
import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceURI;
import ch.entwine.weblounge.common.content.page.HTMLHeadElement;
import ch.entwine.weblounge.common.content.page.HTMLInclude;
import ch.entwine.weblounge.common.content.page.Page;
import ch.entwine.weblounge.common.content.page.PageTemplate;
import ch.entwine.weblounge.common.impl.content.page.PageURIImpl;
import ch.entwine.weblounge.common.impl.request.CacheTagSet;
import ch.entwine.weblounge.common.impl.request.Http11Constants;
import ch.entwine.weblounge.common.impl.request.Http11Utils;
import ch.entwine.weblounge.common.impl.request.RequestUtils;
import ch.entwine.weblounge.common.impl.request.WebloungeRequestImpl;
import ch.entwine.weblounge.common.repository.ContentRepository;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.request.CacheTag;
import ch.entwine.weblounge.common.request.RequestFlavor;
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.User;
import ch.entwine.weblounge.common.site.Action;
import ch.entwine.weblounge.common.site.HTMLAction;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.url.UrlUtils;
import ch.entwine.weblounge.common.url.WebUrl;
import ch.entwine.weblounge.dispatcher.PageRequestHandler;
import ch.entwine.weblounge.dispatcher.RequestHandler;
import ch.entwine.weblounge.dispatcher.impl.DispatchUtils;

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

import java.io.EOFException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Enumeration;

import javax.servlet.http.HttpServletResponse;

/**
 * The <code>PageRequestHandler</code> is used to handle requests to urls simply
 * mapped to a certain template. The request handler will verify access rights
 * and then simply forward the request to the template handler.
 */
public final class PageRequestHandlerImpl implements PageRequestHandler {

    /** Logging facility */
    protected static final Logger logger = LoggerFactory.getLogger(PageRequestHandlerImpl.class);

    /** Alternate uri prefix */
    protected static final String URI_PREFIX = "/weblounge-pages/";

    /** The singleton handler instance */
    private static final PageRequestHandlerImpl handler = new PageRequestHandlerImpl();

    /**
     * Handles the request for a simple url available somewhere in the system. The
     * handler sets the response type, does the url history and then forwards
     * request to the corresponding JSP page or XSLT stylesheet.
     * <p>
     * This method returns <code>true</code> if the handler is decided to handle
     * the request, <code>false</code> otherwise.
     * 
     * @param request
     *          the weblounge request
     * @param response
     *          the weblounge response
     */
    public boolean service(WebloungeRequest request, WebloungeResponse response) {

        logger.debug("Page handler agrees to handle {}", request.getUrl());

        Mode processingMode = Mode.Default;
        WebUrl url = request.getUrl();
        String path = url.getPath();
        RequestFlavor contentFlavor = request.getFlavor();

        if (contentFlavor == null || contentFlavor.equals(ANY))
            contentFlavor = RequestFlavor.HTML;

        // Check the request flavor
        // TODO: Criteria would be loading the page from the repository
        // TODO: Think about performance, page lookup is expensive
        if (!HTML.equals(contentFlavor)) {
            logger.debug("Skipping request for {}, flavor {} is not supported", path, request.getFlavor());
            return false;
        }

        // Determine the editing state
        boolean isEditing = RequestUtils.isEditingState(request);

        // Check if the request is controlled by an action.
        Action action = (Action) request.getAttribute(WebloungeRequest.ACTION);

        // Get the renderer id that has been registered with the url. For this,
        // we first have to load the page data, then get the associated renderer
        // bundle.
        try {
            Page page = null;
            ResourceURI pageURI = null;
            Site site = request.getSite();

            // Check if a page was passed as an attribute
            if (request.getAttribute(WebloungeRequest.PAGE) != null) {
                page = (Page) request.getAttribute(WebloungeRequest.PAGE);
                pageURI = page.getURI();
            }

            // Load the page from the content repository
            else {
                ContentRepository contentRepository = site.getContentRepository();
                if (contentRepository == null) {
                    logger.debug("No content repository found for site '{}'", site);
                    return false;
                } else if (contentRepository.isIndexing()) {
                    logger.debug("Content repository of site '{}' is currently being indexed", site);
                    DispatchUtils.sendServiceUnavailable(request, response);
                    return true;
                }

                ResourceURI requestURI = null;
                ResourceURI requestedURI = null;

                // Load the page. Note that we are taking care of the special case where
                // a user may have created a page with a url that matches a valid
                // language identifier, in which case it would have been stripped from
                // request.getUrl().
                try {
                    if (action != null) {
                        pageURI = getPageURIForAction(action, request);
                        requestURI = pageURI;
                    } else if (path.startsWith(URI_PREFIX)) {
                        String uriSuffix = StringUtils.substringBefore(path.substring(URI_PREFIX.length()), "/");
                        uriSuffix = URLDecoder.decode(uriSuffix, "utf-8");
                        ResourceURI uri = new PageURIImpl(site, null, uriSuffix, request.getVersion());
                        requestURI = uri;
                        WebUrl requestedUrl = request.getRequestedUrl();
                        if (requestedUrl.hasLanguagePathSegment()) {
                            String requestedPath = UrlUtils.concat(path, request.getLanguage().getIdentifier());
                            String requestedUriSuffix = StringUtils
                                    .substringBefore(requestedPath.substring(URI_PREFIX.length()), "/");
                            requestedUriSuffix = URLDecoder.decode(requestedUriSuffix, "utf-8");
                            requestedURI = new PageURIImpl(site, requestedUriSuffix, null, request.getVersion());
                        }
                    } else {
                        long version = isEditing ? Resource.WORK : Resource.LIVE;
                        ResourceURI uri = new PageURIImpl(request);
                        uri.setVersion(version);
                        requestURI = uri;
                        WebUrl requestedUrl = request.getRequestedUrl();
                        if (requestedUrl.hasLanguagePathSegment()) {
                            String requestedPath = UrlUtils.concat(path, request.getLanguage().getIdentifier());
                            requestedPath = URLDecoder.decode(requestedPath, "utf-8");
                            requestedURI = new PageURIImpl(site, requestedPath, null, version);
                        }
                    }

                    // Is this a request with potential path clashes?
                    if (requestedURI != null) {
                        long version = requestedURI.getVersion();
                        if (contentRepository.existsInAnyVersion(requestedURI)) {
                            if (!isEditing && version == Resource.LIVE && contentRepository.exists(requestedURI)) {
                                pageURI = requestedURI;
                                ((WebloungeRequestImpl) request).setLanguage(request.getSessionLanguage());
                            } else if (isEditing && version == Resource.WORK
                                    && !contentRepository.exists(requestedURI)) {
                                requestedURI.setVersion(Resource.LIVE);
                                pageURI = requestedURI;
                                ((WebloungeRequestImpl) request).setLanguage(request.getSessionLanguage());
                            } else if (isEditing && version == Resource.WORK
                                    && !contentRepository.exists(requestedURI)) {
                                pageURI = requestedURI;
                                ((WebloungeRequestImpl) request).setLanguage(request.getSessionLanguage());
                            }
                        }
                    }

                    // Does the page exist?
                    if (pageURI == null && contentRepository.existsInAnyVersion(requestURI)) {
                        long version = requestURI.getVersion();

                        // If the work version is requested, we need to make sure
                        // a) it exists and b) the user is in editing mode
                        if (version == Resource.WORK && isEditing) {
                            if (contentRepository.exists(requestURI)) {
                                pageURI = requestURI;
                            } else {
                                requestURI.setVersion(Resource.LIVE);
                                if (contentRepository.exists(requestURI))
                                    pageURI = requestURI;
                            }
                        } else if (contentRepository.exists(requestURI)) {
                            pageURI = requestURI;
                        }
                    }

                    // Did we find a matching uri?
                    if (pageURI == null) {
                        DispatchUtils.sendNotFound(request, response);
                        return true;
                    }

                    page = (Page) contentRepository.get(pageURI);
                    if (page == null) {
                        DispatchUtils.sendNotFound(request, response);
                        return true;
                    }
                } catch (ContentRepositoryException e) {
                    logger.error("Unable to load page {} from {}: {}",
                            new Object[] { pageURI, contentRepository, e.getMessage(), e });
                    DispatchUtils.sendInternalError(request, response);
                    return true;
                }
            }

            // Check the request method. This handler only supports GET, POST and
            // OPTIONS
            String requestMethod = request.getMethod().toUpperCase();
            if ("OPTIONS".equals(requestMethod)) {
                String verbs = "OPTIONS, GET, POST";
                logger.trace("Answering options request to {} with {}", url, verbs);
                response.setHeader("Allow", verbs);
                response.setContentLength(0);
                return true;
            } else if (!"GET".equals(requestMethod) && !"POST".equals(requestMethod)
                    && !RequestUtils.containsAction(request)) {
                logger.debug("Url {} does not handle {} requests", url, requestMethod);
                DispatchUtils.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, request, response);
                return true;
            }

            // Is it published?
            if (!page.isPublished() && !(page.getVersion() == Resource.WORK)) {
                logger.debug("Access to unpublished page {}", pageURI);
                response.sendError(HttpServletResponse.SC_NOT_FOUND);
                return true;
            }

            // Can the page be accessed by the current user?
            User user = request.getUser();
            try {
                // TODO: Check permission
                // PagePermission p = new PagePermission(page, user);
                // AccessController.checkPermission(p);
            } catch (SecurityException e) {
                logger.warn("Accessed to page {} denied for user {}", pageURI, user);
                DispatchUtils.sendAccessDenied(request, response);
                return true;
            }

            // Check for explicit no cache instructions
            boolean ignoreCache = request.getParameter(ResponseCache.NOCACHE_PARAM) != null;

            // Check if the page is already part of the cache. If so, our task is
            // already done!
            if (!ignoreCache && request.getVersion() == Resource.LIVE && !isEditing) {

                // Create the set of tags that identify the page
                CacheTagSet cacheTags = createPrimaryCacheTags(request);

                if (action == null) {
                    long expirationTime = Renderer.DEFAULT_VALID_TIME;
                    long revalidationTime = Renderer.DEFAULT_RECHECK_TIME;

                    // Check if the page is already part of the cache
                    if (response.startResponse(cacheTags.getTags(), expirationTime, revalidationTime)) {
                        logger.debug("Page handler answered request for {} from cache", request.getUrl());
                        return true;
                    }
                }

                processingMode = Mode.Cached;
                cacheTags.add(CacheTag.Resource, page.getURI().getIdentifier());
                response.addTags(cacheTags);

            } else if (Http11Constants.METHOD_HEAD.equals(requestMethod)) {
                // handle HEAD requests
                Http11Utils.startHeadResponse(response);
                processingMode = Mode.Head;
            } else if (request.getVersion() == Resource.WORK) {
                response.setCacheExpirationTime(0);
            }

            // Set the default maximum render and valid times for pages
            response.setClientRevalidationTime(Renderer.DEFAULT_RECHECK_TIME);
            response.setCacheExpirationTime(Renderer.DEFAULT_VALID_TIME);

            // Store the page in the request
            request.setAttribute(WebloungeRequest.PAGE, page);

            // Get hold of the page template
            PageTemplate template = null;
            try {
                template = getPageTemplate(page, request);
                template.setEnvironment(request.getEnvironment());
            } catch (IllegalStateException e) {
                logger.debug(e.getMessage());
                DispatchUtils.sendInternalError(request, response);
                return true;
            }

            // Does the template support the requested flavor?
            if (!template.supportsFlavor(contentFlavor)) {
                logger.warn("Template '{}' does not support requested flavor {}", template, contentFlavor);
                DispatchUtils.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, request, response);
                return true;
            }

            // Suggest a last modified data. Note that this may not be the final date
            // as the page may contain content embedded from other pages that feature
            // more recent modification dates
            response.setModificationDate(page.getLastModified());

            // Set the content type
            String characterEncoding = response.getCharacterEncoding();
            if (StringUtils.isNotBlank(characterEncoding))
                response.setContentType("text/html; charset=" + characterEncoding.toLowerCase());
            else
                response.setContentType("text/html");

            // Add the template's HTML header elements to the response if it's not
            // only used in editing mode
            for (HTMLHeadElement header : template.getHTMLHeaders()) {
                if (!HTMLInclude.Use.Editor.equals(header.getUse()))
                    response.addHTMLHeader(header);
            }

            // Select the actual renderer by method and have it render the
            // request. Since renderers are being pooled by the bundle, we
            // have to return it after the request has finished.
            try {
                logger.debug("Rendering {} using page template '{}'", path, template);
                template.render(request, response);
            } catch (Throwable t) {
                String params = RequestUtils.dumpParameters(request);
                String msg = "Error rendering template '" + template + "' on '" + path + "' " + params;
                Throwable o = t.getCause();
                if (o != null) {
                    msg += ": " + o.getMessage();
                    logger.error(msg, o);
                } else {
                    logger.error(msg, t);
                }
                DispatchUtils.sendInternalError(request, response);
            }

            return true;
        } catch (EOFException e) {
            logger.debug("Error writing page '{}' back to client: connection closed by client", url);
            return true;
        } catch (IOException e) {
            logger.error("I/O exception while sending error status: {}", e.getMessage(), e);
            return true;
        } finally {
            if (action == null) {
                switch (processingMode) {
                case Cached:
                    response.endResponse();
                    break;
                case Head:
                    Http11Utils.endHeadResponse(response);
                    break;
                default:
                    break;
                }
            }
        }
    }

    /**
     * Tries to determine the target page for the action result. The
     * <code>{@link HTMLAction.TARGET}</code> request attribute and parameter will
     * be considered. In any case, the site's homepage will be the fallback.
     * 
     * @param action
     *          the action handler
     * @param request
     *          the weblounge request
     * @return the target page
     */
    protected ResourceURI getPageURIForAction(Action action, WebloungeRequest request) {
        ResourceURI target = null;
        Site site = request.getSite();

        // Check if a target-page parameter was passed
        if (request.getParameter(HTMLAction.TARGET_PAGE) != null) {
            String targetUrl = request.getParameter(HTMLAction.TARGET_PAGE);
            try {
                String decocedTargetUrl = null;
                String encoding = request.getCharacterEncoding();
                if (encoding == null)
                    encoding = "utf-8";
                decocedTargetUrl = URLDecoder.decode(targetUrl, encoding);
                target = new PageURIImpl(site, decocedTargetUrl);
            } catch (UnsupportedEncodingException e) {
                logger.warn("Error while decoding target url {}: {}", targetUrl, e.getMessage());
                target = new PageURIImpl(site, "/");
            }
        }

        // Nothing found, let's choose the site's homepage
        if (target == null) {
            target = new PageURIImpl(site, "/");
        }

        return target;
    }

    /**
     * Returns the template that will be used to handle this request. If the
     * template cannot be found or used for some reason, an
     * {@link IllegalStateException} is thrown.
     * 
     * @param page
     *          the page
     * @param request
     *          the request
     * @return the template
     * @throws IllegalStateException
     *           if the template cannot be found
     */
    protected PageTemplate getPageTemplate(Page page, WebloungeRequest request) throws IllegalStateException {
        Site site = request.getSite();

        // Has a template been defined already (e. g. by an action handler)?
        PageTemplate template = (PageTemplate) request.getAttribute(WebloungeRequest.TEMPLATE);

        // Apparently not...
        if (template == null) {
            String templateId = page.getTemplate();
            template = site.getTemplate(templateId);
            if (template == null) {
                logger.warn("Page {} specified a non-existing template '{}'", page.getURI(), templateId);
                template = site.getDefaultTemplate();
            }
        }

        template.setEnvironment(request.getEnvironment());
        return template;
    }

    /**
     * Returns the primary set of cache tags for the given request.
     * 
     * @param request
     *          the request
     * @return the cache tags
     */
    protected CacheTagSet createPrimaryCacheTags(WebloungeRequest request) {
        CacheTagSet cacheTags = new CacheTagSet();
        cacheTags.add(CacheTag.Url, request.getUrl().getPath());
        cacheTags.add(CacheTag.Url, request.getRequestedUrl().getPath());
        cacheTags.add(CacheTag.Language, request.getLanguage().getIdentifier());
        cacheTags.add(CacheTag.User, request.getUser().getLogin());
        Enumeration<?> pe = request.getParameterNames();
        int parameterCount = 0;
        while (pe.hasMoreElements()) {
            parameterCount++;
            String key = pe.nextElement().toString();
            String[] values = request.getParameterValues(key);
            for (String value : values) {
                cacheTags.add(key, value);
            }
        }
        cacheTags.add(CacheTag.Parameters, Integer.toString(parameterCount));
        return cacheTags;
    }

    /**
     * Returns the singleton instance of this class.
     * 
     * @return the request handler instance
     */
    public static RequestHandler getInstance() {
        return handler;
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.dispatcher.RequestHandler#getName()
     */
    public String getName() {
        return "page request handler";
    }

    /**
     * Returns a string representation of this request handler.
     * 
     * @return the handler name
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return getName();
    }

    /**
     * {@inheritDoc}
     * 
     * @see ch.entwine.weblounge.dispatcher.RequestHandler#getPriority()
     */
    public int getPriority() {
        return -1;
    }

}