Package ch.entwine.weblounge.dispatcher.impl.handler

Source Code of ch.entwine.weblounge.dispatcher.impl.handler.PageRequestHandlerImpl

/*
*  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;
  }

}
TOP

Related Classes of ch.entwine.weblounge.dispatcher.impl.handler.PageRequestHandlerImpl

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.