Package ch.entwine.weblounge.contentrepository.impl.endpoint

Source Code of ch.entwine.weblounge.contentrepository.impl.endpoint.PagesEndpoint

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

import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceURI;
import ch.entwine.weblounge.common.content.ResourceUtils;
import ch.entwine.weblounge.common.content.SearchQuery;
import ch.entwine.weblounge.common.content.SearchQuery.Order;
import ch.entwine.weblounge.common.content.SearchResult;
import ch.entwine.weblounge.common.content.SearchResultItem;
import ch.entwine.weblounge.common.content.page.Composer;
import ch.entwine.weblounge.common.content.page.Page;
import ch.entwine.weblounge.common.content.page.Pagelet;
import ch.entwine.weblounge.common.impl.content.GeneralResourceURIImpl;
import ch.entwine.weblounge.common.impl.content.ResourceURIImpl;
import ch.entwine.weblounge.common.impl.content.SearchQueryImpl;
import ch.entwine.weblounge.common.impl.content.SearchResultImpl;
import ch.entwine.weblounge.common.impl.content.page.PageImpl;
import ch.entwine.weblounge.common.impl.content.page.PageReader;
import ch.entwine.weblounge.common.impl.content.page.PageSearchResultItemImpl;
import ch.entwine.weblounge.common.impl.content.page.PageURIImpl;
import ch.entwine.weblounge.common.impl.security.SecurityUtils;
import ch.entwine.weblounge.common.impl.security.SystemRole;
import ch.entwine.weblounge.common.impl.security.UserImpl;
import ch.entwine.weblounge.common.impl.url.WebUrlImpl;
import ch.entwine.weblounge.common.impl.util.WebloungeDateFormat;
import ch.entwine.weblounge.common.repository.ContentRepository;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.repository.ReferentialIntegrityException;
import ch.entwine.weblounge.common.repository.WritableContentRepository;
import ch.entwine.weblounge.common.security.SecurityService;
import ch.entwine.weblounge.common.security.User;
import ch.entwine.weblounge.common.site.Site;
import ch.entwine.weblounge.common.url.UrlUtils;
import ch.entwine.weblounge.common.url.WebUrl;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.StringTokenizer;
import java.util.UUID;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.Response.Status;
import javax.xml.parsers.ParserConfigurationException;

/**
* This class implements the <code>REST</code> endpoint for page data.
*/
@Path("/")
@Produces(MediaType.APPLICATION_XML)
public class PagesEndpoint extends ContentRepositoryEndpoint {

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

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

  /** The endpoint documentation */
  private String docs = null;

  /**
   * Returns a collection of pages which match the given criteria.
   *
   * @param request
   *          the request
   * @param path
   *          the page path (e.g. <code>/my/simple/path</code>)
   * @param subjectstring
   *          one ore more subjects, divided by a comma
   * @param searchterms
   *          fulltext search terms
   * @param sort
   *          sort order, possible values are
   *          <code>created-asc, created-desc, published-asc, published-desc, modified-asc & modified-desc</code>
   * @param limit
   *          search result limit
   * @param offset
   *          search result offset (for paging in combination with limit)
   * @param details
   *          switch for providing pages including their bodies
   * @return a collection of matching pages
   */
  @GET
  @Path("/")
  public Response getAllPages(
      @Context HttpServletRequest request,
      @QueryParam("path") String path,
      @QueryParam("subjects") String subjectstring,
      @QueryParam("searchterms") String searchterms,
      @QueryParam("filter") String filter,
      @QueryParam("sort") @DefaultValue("modified-desc") String sort,
      @QueryParam("version") @DefaultValue("-1") long version,
      @QueryParam("preferredversion") @DefaultValue("-1") long preferredVersion,
      @QueryParam("limit") @DefaultValue("10") int limit,
      @QueryParam("offset") @DefaultValue("0") int offset,
      @QueryParam("details") @DefaultValue("false") boolean details) {

    // Create search query
    Site site = getSite(request);
    SearchQuery q = new SearchQueryImpl(site);

    q.withTypes(Page.TYPE);
    if (version != -1)
      q.withVersion(version);

    if (preferredVersion != -1)
      q.withPreferredVersion(preferredVersion);

    // Path
    if (StringUtils.isNotBlank(path))
      q.withPath(path);

    // Subjects
    if (StringUtils.isNotBlank(subjectstring)) {
      StringTokenizer subjects = new StringTokenizer(subjectstring, ",");
      while (subjects.hasMoreTokens())
        q.withSubject(subjects.nextToken());
    }

    // Search terms
    if (StringUtils.isNotBlank(searchterms))
      q.withText(true, searchterms);

    Calendar today = Calendar.getInstance();
    today.set(Calendar.HOUR_OF_DAY, 0);
    today.set(Calendar.MINUTE, 0);
    today.set(Calendar.SECOND, 0);
    today.set(Calendar.MILLISECOND, 0);
    Calendar yesterday = Calendar.getInstance();
    yesterday.add(Calendar.DATE, -1);
    yesterday.set(Calendar.HOUR_OF_DAY, 0);
    yesterday.set(Calendar.MINUTE, 0);
    yesterday.set(Calendar.SECOND, 0);
    yesterday.set(Calendar.MILLISECOND, 0);
    Calendar tomorrow = Calendar.getInstance();
    tomorrow.add(Calendar.DATE, 1);
    tomorrow.set(Calendar.HOUR_OF_DAY, 0);
    tomorrow.set(Calendar.MINUTE, 0);
    tomorrow.set(Calendar.SECOND, 0);
    tomorrow.set(Calendar.MILLISECOND, 0);

    // Filter query
    if (StringUtils.isNotBlank(filter)) {
      if ("/".equals(filter)) {
        q.withPath("/");
      } else if (filter.contains("state:work")) {
        q.withVersion(Resource.WORK);
        q.withPreferredVersion(-1);
      } else if (filter.contains("state:live")) {
        q.withVersion(Resource.LIVE);
        q.withPreferredVersion(-1);
      } else if (filter.contains("state:locked")) {
        q.withLockOwner();
      }

      // by user
      else if (filter.startsWith("locked:") && filter.length() > "locked:".length()) {
        String lockOwner = StringUtils.trim(filter.substring("locked:".length()));
        if ("me".equals(lockOwner))
          q.withLockOwner(securityService.getUser());
        else
          q.withLockOwner(new UserImpl(lockOwner));
      } else if (filter.startsWith("creator:") && filter.length() > "creator:".length()) {
        String creator = StringUtils.trim(filter.substring("creator:".length()));
        if ("me".equals(creator))
          q.withCreator(securityService.getUser());
        else
          q.withCreator(new UserImpl(creator));
      } else if (filter.startsWith("modifier:") && filter.length() > "modifier:".length()) {
        String modifier = StringUtils.trim(filter.substring("modifier:".length()));
        if ("me".equals(modifier))
          q.withModifier(securityService.getUser());
        else
          q.withModifier(new UserImpl(modifier));
      } else if (filter.startsWith("publisher:") && filter.length() > "publisher:".length()) {
        String publisher = StringUtils.trim(filter.substring("publisher:".length()));
        if ("me".equals(publisher))
          q.withPublisher(securityService.getUser());
        else
          q.withPublisher(new UserImpl(publisher));
      }

      // by date

      else if (filter.startsWith("created:") && filter.length() > "created:".length()) {
        String created = StringUtils.trim(filter.substring("created:".length()));
        if ("today".equals(created))
          q.withCreationDateBetween(today.getTime()).and(tomorrow.getTime());
        else if ("yesterday".equals(created))
          q.withCreationDateBetween(yesterday.getTime()).and(today.getTime());
        else
          q.withCreationDate(tomorrow.getTime());
      } else if (filter.startsWith("modified:") && filter.length() > "modified:".length()) {
        String modified = StringUtils.trim(filter.substring("modified:".length()));
        if ("today".equals(modified))
          q.withModificationDateBetween(today.getTime()).and(tomorrow.getTime());
        else if ("yesterday".equals(modified))
          q.withModificationDateBetween(yesterday.getTime()).and(today.getTime());
        else
          q.withCreationDate(tomorrow.getTime());
      } else if (filter.startsWith("publisher:") && filter.length() > "publisher:".length()) {
        String published = StringUtils.trim(filter.substring("published:".length()));
        if ("today".equals(published))
          q.withPublishingDateBetween(today.getTime()).and(tomorrow.getTime());
        else if ("yesterday".equals(published))
          q.withPublishingDateBetween(yesterday.getTime()).and(today.getTime());
        else
          q.withCreationDate(tomorrow.getTime());
      }

      // by id
      else if (filter.contains("id:")) {
        String[] searchTerms = StringUtils.split(filter);
        for (String searchTerm : searchTerms) {
          if (searchTerm.startsWith("id:") && filter.length() > "id:".length()) {
            q.withIdentifier(StringUtils.trim(searchTerm.substring("id:".length())));
          }
        }
      }

      // simple filter
      else if (filter.contains("/")) {
        q.withPathPrefix(filter);
      } else {
        q.withFulltext(true, filter);
      }

    }

    // Limit and Offset
    q.withLimit(limit);
    q.withOffset(offset);

    // Sort order
    if (StringUtils.equalsIgnoreCase("modified-asc", sort)) {
      q.sortByModificationDate(Order.Ascending);
    } else if (StringUtils.equalsIgnoreCase("modified-desc", sort)) {
      q.sortByModificationDate(Order.Descending);
    } else if (StringUtils.equalsIgnoreCase("created-asc", sort)) {
      q.sortByCreationDate(Order.Ascending);
    } else if (StringUtils.equalsIgnoreCase("created-desc", sort)) {
      q.sortByCreationDate(Order.Descending);
    } else if (StringUtils.equalsIgnoreCase("published-asc", sort)) {
      q.sortByPublishingDate(Order.Ascending);
    } else if (StringUtils.equalsIgnoreCase("published-desc", sort)) {
      q.sortByPublishingDate(Order.Descending);
    }

    // Load the result
    String result = loadResultSet(q, details);

    return Response.ok(result).build();
  }

  /**
   * Returns a collection of pages that are defined as pending.
   *
   * @param request
   *          the request
   * @param filter
   *          further search result filtering
   * @param sort
   *          sort order, possible values are
   *          <code>created-asc, created-desc, published-asc, published-desc, modified-asc & modified-desc</code>
   * @param limit
   *          search result limit
   * @param offset
   *          search result offset (for paging in combination with limit)
   * @param details
   *          switch for providing pages including their bodies
   * @return a collection of matching pages
   */
  @GET
  @Path("/pending")
  public Response getPending(@Context HttpServletRequest request,
      @QueryParam("filter") String filter,
      @QueryParam("sort") @DefaultValue("modified-desc") String sort,
      @QueryParam("limit") @DefaultValue("10") int limit,
      @QueryParam("offset") @DefaultValue("0") int offset,
      @QueryParam("details") @DefaultValue("false") boolean details) {

    // Create search query
    Site site = getSite(request);
    SearchQuery q = new SearchQueryImpl(site);
    q.withVersion(Resource.WORK);

    // Only take resources that have not been modified
    q.withoutPublication();

    // Type
    q.withTypes(Page.TYPE);

    // Filter query
    if (StringUtils.isNotBlank(filter))
      q.withFilter(filter);

    // Limit and Offset
    q.withLimit(limit);
    q.withOffset(offset);

    // Sort order
    if (StringUtils.equalsIgnoreCase("modified-asc", sort)) {
      q.sortByModificationDate(Order.Ascending);
    } else if (StringUtils.equalsIgnoreCase("modified-desc", sort)) {
      q.sortByModificationDate(Order.Descending);
    } else if (StringUtils.equalsIgnoreCase("created-asc", sort)) {
      q.sortByCreationDate(Order.Ascending);
    } else if (StringUtils.equalsIgnoreCase("created-desc", sort)) {
      q.sortByCreationDate(Order.Descending);
    } else if (StringUtils.equalsIgnoreCase("published-asc", sort)) {
      q.sortByPublishingDate(Order.Ascending);
    } else if (StringUtils.equalsIgnoreCase("published-desc", sort)) {
      q.sortByPublishingDate(Order.Descending);
    }

    // Load the result
    String result = loadResultSet(q, details);

    // Return the response
    return Response.ok(result).build();

  }

  /**
   * Returns the page with the given identifier or a <code>404</code> if the
   * page could not be found.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @return the page
   */
  @GET
  @Path("/{page}")
  public Response getPageById(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @QueryParam("version") @DefaultValue("0") long version) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Resolve name clash
    if ("docs".equals(pageId)) {
      return Response.ok(getDocumentation(request)).type(MediaType.TEXT_HTML).build();
    }

    // Load the page
    Page page = (Page) loadResource(request, pageId, Page.TYPE, version);
    if (page == null) {
      return Response.status(Status.NOT_FOUND).build();
    }

    // Is there an up-to-date, cached version on the client side?
    if (!ResourceUtils.hasChanged(request, page)) {
      return Response.notModified().build();
    }

    // Create the response
    ResponseBuilder response = Response.ok(page.toXml());
    response.tag(ResourceUtils.getETagValue(page));
    response.lastModified(ResourceUtils.getModificationDate(page));
    return response.build();
  }

  /**
   * Returns child pages of the page with the given identifier or a
   * <code>404</code> if the page could not be found.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @return the child pages
   */
  @GET
  @Path("/{page}/children")
  public Response getChildPagesByURI(@Context HttpServletRequest request,
      @PathParam("page") String pageId) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Load the page
    Page page = (Page) loadResource(request, pageId, Page.TYPE);
    if (page == null) {
      return Response.status(Status.NOT_FOUND).build();
    }

    Site site = getSite(request);
    SearchQuery q = new SearchQueryImpl(site);
    q.withVersion(Resource.LIVE);
    q.withTypes(Page.TYPE);
    q.withPathPrefix(page.getURI().getPath());

    ContentRepository repository = getContentRepository(site, false);
    SearchResult result = null;
    try {
      result = repository.find(q);
    } catch (ContentRepositoryException e) {
      return Response.status(Status.INTERNAL_SERVER_ERROR).build();
    }

    StringBuffer buf = new StringBuffer("<pages>");
    for (SearchResultItem item : result.getItems()) {
      if (pageId.equals(item.getId()))
        continue;
      String headerXml = ((PageSearchResultItemImpl) item).getPageHeaderXml();
      buf.append(headerXml);
    }
    buf.append("</pages>");

    // Create the response
    return Response.ok(buf.toString()).build();
  }

  /**
   * Returns pages containing pagelets with properties of name
   * <code>resourceid</code> and a value equal to that of the page identifier.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @return the referring pages
   */
  @GET
  @Path("/{page}/referrer")
  public Response getReferencesByURI(@Context HttpServletRequest request,
      @PathParam("page") String pageId) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    Site site = getSite(request);
    SearchQuery q = new SearchQueryImpl(site);
    q.withTypes(Page.TYPE);
    q.withVersion(Resource.LIVE);
    q.withProperty("resourceid", pageId);

    ContentRepository repository = getContentRepository(site, false);
    SearchResult result = null;
    try {
      result = repository.find(q);
    } catch (ContentRepositoryException e) {
      return Response.status(Status.INTERNAL_SERVER_ERROR).build();
    }

    StringBuffer buf = new StringBuffer("<pages>");
    for (SearchResultItem item : result.getItems()) {
      String headerXml = ((PageSearchResultItemImpl) item).getPageHeaderXml();
      buf.append(headerXml);
    }
    buf.append("</pages>");

    // Create the response
    return Response.ok(buf.toString()).build();
  }

  /**
   * Updates the indicated page.
   *
   * @param request
   *          the http request
   * @param pageId
   *          the page identifier
   * @param pageXml
   *          the updated page
   * @param ifMatchHeader
   *          the page's <code>etag</code> value
   * @param asynchronous
   *          <code>true</code> to prevent blocking while content is being
   *          updated
   * @return response an empty response
   * @throws WebApplicationException
   *           if the update fails
   */
  @PUT
  @Path("/{page}")
  public Response updatePage(@Context HttpServletRequest request,
      @PathParam("page") String pageId, @FormParam("content") String pageXml,
      @HeaderParam("If-Match") String ifMatchHeader,
      @FormParam("asynchronous") boolean asynchronous) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();
    if (pageXml == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Extract the site
    Site site = getSite(request);

    // Make sure the content repository is writable
    if (site.getContentRepository().isReadOnly()) {
      logger.warn("Attempt to write to read-only content repository {}", site);
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    }

    WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true);
    ResourceURI workURI = new PageURIImpl(site, null, pageId, Resource.WORK);

    // Does the page exist?
    Page currentPage = null;
    try {
      currentPage = (Page) contentRepository.get(workURI);
      if (currentPage == null) {
        logger.warn("Attempt to update a page without creating a work version first");
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      }
      workURI.setPath(currentPage.getURI().getPath());
    } catch (ContentRepositoryException e) {
      logger.warn("Error lookup up page {} from repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Check the value of the If-Match header against the etag
    if (ifMatchHeader != null) {
      String etag = ResourceUtils.getETagValue(currentPage);
      if (!etag.equals(ifMatchHeader)) {
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      }
    }

    // Get the user
    User user = securityService.getUser();
    if (user == null)
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Make sure the user has editing rights
    if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    boolean isAdmin = SecurityUtils.userHasRole(user, SystemRole.SITEADMIN);

    // If the page is locked by a different user, refuse
    if (currentPage.isLocked() && (!currentPage.getLockOwner().equals(user) && !isAdmin)) {
      return Response.status(Status.FORBIDDEN).build();
    }

    // Parse the page and update it in the repository
    Page page = null;
    try {
      PageReader pageReader = new PageReader();
      page = pageReader.read(IOUtils.toInputStream(pageXml, "utf-8"), site);
      if (StringUtils.isBlank(page.getURI().getPath()))
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      page.setModified(user, new Date());
      page.setVersion(Resource.WORK);

      // TODO: Preview generation disabled due to performance problems
      contentRepository.putAsynchronously(page, false);

      // Check if the page has been moved
      String currentPath = currentPage.getURI().getPath();
      String newPath = page.getURI().getPath();
      if ((currentPath != null && !currentPath.equals(newPath) || (currentPath == null && newPath != null))) {
        contentRepository.moveAsynchronously(currentPage.getURI(), newPath, true);
      }
    } catch (SecurityException e) {
      logger.warn("Tried to update page {} of site '{}' without permission", workURI, site);
      throw new WebApplicationException(Status.FORBIDDEN);
    } catch (IOException e) {
      logger.warn("Error reading updated page {} from request", workURI);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (ParserConfigurationException e) {
      logger.warn("Error configuring parser to read updated page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (SAXException e) {
      logger.warn("Error parsing updated page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.BAD_REQUEST);
    } catch (IllegalStateException e) {
      logger.warn("Error updating page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (ContentRepositoryException e) {
      logger.warn("Error updating page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Create the response
    ResponseBuilder response = Response.ok();
    response.tag(ResourceUtils.getETagValue(page));
    response.lastModified(ResourceUtils.getModificationDate(page));
    return response.build();
  }

  /**
   * Creates a page at the site's content repository and returns the location to
   * post updates to.
   * <p>
   * Note that any of identifier, path and version that may be contained in an
   * initial <code>pageXml</code> will be overwritten.
   *
   * @param request
   *          the http request
   * @param pageXml
   *          the new page
   * @param path
   *          the path to store the page at
   * @param asynchronous
   *          <code>true</code> to prevent blocking while the page is being
   *          created in the database
   * @return response the page location
   */
  @POST
  @Path("/")
  public Response createPage(@Context HttpServletRequest request,
      @FormParam("content") String pageXml, @FormParam("path") String path,
      @FormParam("asynchronous") boolean asynchronous) {

    Site site = getSite(request);
    WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true);

    // Create the page uri
    ResourceURIImpl pageURI = null;
    String uuid = UUID.randomUUID().toString();
    if (!StringUtils.isBlank(path)) {
      try {
        if (!path.startsWith("/"))
          path = "/" + path;
        WebUrl url = new WebUrlImpl(site, path);
        path = url.getPath();
      } catch (IllegalArgumentException e) {
        logger.warn("Tried to create a page with an invalid path '{}': {}", path, e.getMessage());
        throw new WebApplicationException(Status.BAD_REQUEST);
      }
    } else {
      path = "/" + uuid.replaceAll("-", "");
    }

    pageURI = new PageURIImpl(site, path, uuid, Resource.WORK);

    // Make sure the page doesn't exist
    try {
      if (contentRepository.existsInAnyVersion(new GeneralResourceURIImpl(site, pageURI.getPath()))) {
        logger.warn("Tried to create already existing page {} in site '{}'", pageURI, site);
        throw new WebApplicationException(Status.CONFLICT);
      }
    } catch (IllegalArgumentException e) {
      logger.warn("Tried to create a page with an invalid path '{}': {}", path, e.getMessage());
      throw new WebApplicationException(Status.BAD_REQUEST);
    } catch (ContentRepositoryException e) {
      logger.warn("Page lookup {} failed for site '{}'", pageURI, site);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Get the user
    User user = securityService.getUser();
    if (user == null)
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Make sure the user has editing rights
    if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Parse the page and store it
    PageImpl page = null;
    URI uri = null;
    if (!StringUtils.isBlank(pageXml)) {
      logger.debug("Adding page to {}", pageURI);
      try {
        PageReader pageReader = new PageReader();
        page = pageReader.read(IOUtils.toInputStream(pageXml, "utf-8"), site);
        page.setIdentifier(pageURI.getIdentifier());
        page.setPath(pageURI.getPath());
        page.setVersion(pageURI.getVersion());
      } catch (IOException e) {
        logger.warn("Error reading page {} from request", pageURI);
        throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
      } catch (ParserConfigurationException e) {
        logger.warn("Error configuring parser to read updated page {}: {}", pageURI, e.getMessage());
        throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
      } catch (SAXException e) {
        logger.warn("Error parsing updated page {}: {}", pageURI, e.getMessage());
        throw new WebApplicationException(Status.BAD_REQUEST);
      }
    } else {
      logger.debug("Creating new page at {}", pageURI);
      page = new PageImpl(pageURI);
      page.setTemplate(site.getDefaultTemplate().getIdentifier());
      page.setCreated(user, new Date());
    }

    // Store the new page
    try {
      contentRepository.put(page, true);
      uri = new URI(UrlUtils.concat(request.getRequestURL().toString(), pageURI.getIdentifier()));
    } catch (URISyntaxException e) {
      logger.warn("Error creating a uri for page {}: {}", pageURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (SecurityException e) {
      logger.warn("Tried to update page {} of site '{}' without permission", pageURI, site);
      throw new WebApplicationException(Status.FORBIDDEN);
    } catch (IOException e) {
      logger.warn("Error writing new page {}: {}", pageURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (IllegalStateException e) {
      logger.warn("Error adding new page {}: {}", pageURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (ContentRepositoryException e) {
      logger.warn("Error adding new page {}: {}", pageURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Create the response
    ResponseBuilder response = Response.created(uri);
    response.tag(ResourceUtils.getETagValue(page));
    response.lastModified(ResourceUtils.getModificationDate(page));
    return response.build();
  }

  /**
   * Removes the indicated page from the site.
   *
   * @param request
   *          the http request
   * @param pageId
   *          the page identifier
   * @param asynchronous
   *          <code>true</code> to prevent blocking while the page is being
   *          deleted from the database
   * @return response an empty response
   */
  @DELETE
  @Path("/{page}")
  public Response deletePage(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @QueryParam("asynchronous") boolean asynchronous) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    Site site = getSite(request);
    WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true);

    ResourceURI livePageURI = new PageURIImpl(site, null, pageId, Resource.LIVE);
    ResourceURI workPageURI = new PageURIImpl(site, null, pageId, Resource.WORK);

    try {
      if (!contentRepository.existsInAnyVersion(livePageURI)) {
        logger.warn("Tried to delete non existing page {} in site '{}'", livePageURI, site);
        throw new WebApplicationException(Status.NOT_FOUND);
      }
    } catch (ContentRepositoryException e) {
      logger.warn("Page lookup {} failed for site '{}'", livePageURI, site);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    Page page = null;
    try {
      page = (Page) contentRepository.get(livePageURI);
      if (page != null) {
        livePageURI.setPath(page.getURI().getPath());
      } else {
        page = (Page) contentRepository.get(workPageURI);
        workPageURI.setPath(page.getURI().getPath());
      }
    } catch (ContentRepositoryException e) {
      logger.warn("Error lookup up page {} from repository: {}", livePageURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Get the user
    User user = securityService.getUser();
    if (user == null)
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Make sure the user has editing rights
    if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // If the page is published, the user needs publishing rights
    if (page.isPublished() && !SecurityUtils.userHasRole(user, SystemRole.PUBLISHER))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    boolean isAdmin = SecurityUtils.userHasRole(user, SystemRole.SITEADMIN);

    // If the page is locked by a different user, refuse
    if (page.isLocked() && (!page.getLockOwner().equals(user) && !isAdmin)) {
      return Response.status(Status.FORBIDDEN).build();
    }

    // Delete the page
    try {
      contentRepository.delete(page.getURI(), true);
    } catch (SecurityException e) {
      logger.warn("Tried to delete page {} of site '{}' without permission", livePageURI, site);
      throw new WebApplicationException(Status.FORBIDDEN);
    } catch (ReferentialIntegrityException e) {
      logger.warn("Tried to delete referenced page {} of site '{}'", livePageURI, site);
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (IOException e) {
      logger.warn("Error deleting page {} from site '{}': {}", new Object[] {
          livePageURI,
          site,
          e.getMessage() });
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (ContentRepositoryException e) {
      logger.warn("Error deleting page {} from site '{}': {}", new Object[] {
          livePageURI,
          site,
          e.getMessage() });
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    return Response.ok().build();
  }

  /**
   * Returns the composer specified by <code>composerId</code> and
   * <code>pageletIndex</code> or a <code>404</code> if either the page or the
   * composer does not exist.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @param composerId
   *          the composer identifier
   * @return the composer
   */
  @GET
  @Path("/{page}/composers/{composer}")
  public Response getComposer(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @PathParam("composer") String composerId,
      @QueryParam("version") @DefaultValue("0") long version) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();
    else if (composerId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Load the page
    Page page = (Page) loadResource(request, pageId, Page.TYPE, version);
    if (page == null) {
      return Response.status(Status.NOT_FOUND).build();
    }

    // Is there an up-to-date, cached version on the client side?
    if (!ResourceUtils.hasChanged(request, page)) {
      return Response.notModified().build();
    }

    // Load the composer
    Composer composer = page.getComposer(composerId);
    if (composer == null) {
      return Response.status(Status.NOT_FOUND).build();
    }

    // Return the composer
    return Response.ok(composer.toXml()).lastModified(ResourceUtils.getModificationDate(page)).build();
  }

  /**
   * Returns the pagelet specified by <code>pageId</code>,
   * <code>composerId</code> and <code>pageletIndex</code> or a <code>404</code>
   * if either of the the page, the composer or the pagelet does not exist.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @param composerId
   *          the composer identifier
   * @param pageletIndex
   *          the pagelet index within the composer
   * @return the pagelet
   */
  @GET
  @Path("/{page}/composers/{composer}/pagelets/{pageletindex}")
  public Response getPagelet(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @PathParam("composer") String composerId,
      @PathParam("pageletindex") int pageletIndex,
      @QueryParam("version") @DefaultValue("0") long version) {

    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();
    else if (composerId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Load the page
    Page page = (Page) loadResource(request, pageId, Page.TYPE, version);
    if (page == null) {
      return Response.status(Status.NOT_FOUND).build();
    }

    // Is there an up-to-date, cached version on the client side?
    if (!ResourceUtils.hasChanged(request, page)) {
      return Response.notModified().build();
    }

    // Load the composer
    Composer composer = page.getComposer(composerId);
    if (composer == null || composer.size() < pageletIndex) {
      return Response.status(Status.NOT_FOUND).build();
    }

    // Return the pagelet
    ResponseBuilder response = Response.ok(composer.getPagelet(pageletIndex).toXml());
    response.lastModified(ResourceUtils.getModificationDate(page));
    return response.build();
  }

  /**
   * Locks the page and returns with a <code>200</code> status code if the lock
   * operation succeeds, <code>400</code> if the page is not found or
   * <code>403</code> if another user has already locked the page.
   * <p>
   * If <code>user</code> is not specified, then the current user will be used
   * for lock acquisition.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @param asynchronous
   *          <code>true</code> to prevent blocking while the page is being
   *          locked
   * @return the page
   */
  @PUT
  @Path("/{page}/lock")
  public Response lockPage(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @HeaderParam("If-Match") String ifMatchHeader,
      @FormParam("asynchronous") boolean asynchronous) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Extract the site
    Site site = getSite(request);

    // Make sure the content repository is writable
    if (site.getContentRepository().isReadOnly()) {
      logger.warn("Attempt to lock a page in a read-only content repository {}", site);
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    }

    WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true);
    ResourceURI workURI = new PageURIImpl(site, null, pageId, Resource.WORK);

    // Does the page exist at all?
    Page workPage = null;
    try {
      if (!contentRepository.existsInAnyVersion(workURI))
        throw new WebApplicationException(Status.NOT_FOUND);
    } catch (ContentRepositoryException e) {
      logger.warn("Error looking up page {} from repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Make sure we have a work page. If it doesn't exist yet, it needs
    // to be created as a result of the lock operation
    ResourceURI liveURI = new PageURIImpl(site, null, pageId, Resource.LIVE);
    try {
      workPage = (Page) contentRepository.get(workURI);
      if (workPage == null) {
        logger.debug("Creating work version of {}", liveURI);
        PageReader reader = new PageReader();
        Page livePage = (Page) contentRepository.get(liveURI);
        workPage = reader.read(IOUtils.toInputStream(livePage.toXml(), "utf-8"), site);
        workPage.setVersion(Resource.WORK);
        contentRepository.putAsynchronously(workPage, false);
      } else {
        workURI.setPath(workPage.getURI().getPath());
      }
    } catch (ContentRepositoryException e) {
      logger.warn("Error lookup up page {} from repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (IllegalStateException e) {
      logger.warn("Error putting a page work copy {} to repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (IOException e) {
      logger.warn("Error putting a page work copy {} to repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (ParserConfigurationException e) {
      logger.warn("Error reading live page {} from repository: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (SAXException e) {
      logger.warn("Error reading live page {} from repository: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Check the value of the If-Match header against the etag
    if (ifMatchHeader != null) {
      String etag = ResourceUtils.getETagValue(workPage);
      if (!etag.equals(ifMatchHeader)) {
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      }
    }

    // Get the user
    User user = securityService.getUser();
    if (user == null)
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Make sure the user has editing rights
    if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    boolean isAdmin = SecurityUtils.userHasRole(user, SystemRole.SITEADMIN);

    // If the page is locked by a different user, refuse
    if (workPage.isLocked() && (!workPage.getLockOwner().equals(user) && !isAdmin)) {
      return Response.status(Status.FORBIDDEN).build();
    }

    // Finally, perform the lock operation (on all resource versions)
    try {
      contentRepository.lockAsynchronously(workURI, user);
      logger.info("Page {} has been locked by {}", workURI, user);
    } catch (SecurityException e) {
      logger.warn("Tried to lock page {} of site '{}' without permission", workURI, site);
      throw new WebApplicationException(Status.FORBIDDEN);
    } catch (IOException e) {
      logger.warn("Error writing page lock {} to repository", workURI);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (IllegalStateException e) {
      logger.warn("Error locking page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (ContentRepositoryException e) {
      logger.warn("Error locking page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Create the response
    ResponseBuilder response = Response.ok();
    response.tag(ResourceUtils.getETagValue(workPage));
    response.lastModified(workPage.getLastModified());
    return response.build();
  }

  /**
   * Unlocks the page and returns with a <code>200</code> status code if the
   * unlock operation succeeds, <code>400</code> if the page is not found or
   * <code>403</code> if the page is locked by a different user.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @param asynchronous
   *          <code>true</code> to prevent blocking while the page is being
   *          unlocked
   * @return the page
   */
  @DELETE
  @Path("/{page}/lock")
  public Response unlockPage(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @HeaderParam("If-Match") String ifMatchHeader,
      @QueryParam("asynchronous") boolean asynchronous) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Extract the site
    Site site = getSite(request);

    // Make sure the content repository is writable
    if (site.getContentRepository().isReadOnly()) {
      logger.warn("Attempt to unlock a page in a read-only content repository {}", site);
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    }

    WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true);
    ResourceURI pageURI = new PageURIImpl(site, null, pageId, Resource.WORK);

    // Does the page exist?
    Page page = null;
    try {
      ResourceURI[] versions = contentRepository.getVersions(pageURI);
      if (versions.length == 0)
        throw new WebApplicationException(Status.NOT_FOUND);
      page = (Page) contentRepository.get(versions[0]);
      if (page == null)
        throw new WebApplicationException(Status.NOT_FOUND);
      pageURI.setPath(page.getURI().getPath());
    } catch (ContentRepositoryException e) {
      logger.warn("Error lookup up page {} from repository: {}", pageURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Check the value of the If-Match header against the etag
    if (ifMatchHeader != null) {
      String etag = ResourceUtils.getETagValue(page);
      if (!etag.equals(ifMatchHeader)) {
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      }
    }

    // Get the user
    User user = securityService.getUser();
    if (user == null)
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Make sure the user has editing rights
    if (!SecurityUtils.userHasRole(user, SystemRole.EDITOR))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    boolean isAdmin = SecurityUtils.userHasRole(user, SystemRole.SITEADMIN);

    // If the page is locked by a different user, refuse
    if (page.isLocked() && (!page.getLockOwner().equals(user) && !isAdmin)) {
      return Response.status(Status.FORBIDDEN).build();
    }

    // Finally, perform the lock operation (on all resource versions)
    try {
      contentRepository.unlockAsynchronously(pageURI, user);
      logger.info("Page {} has been unlocked by {}", pageURI, user);
    } catch (SecurityException e) {
      logger.warn("Tried to unlock page {} of site '{}' without permission", pageURI, site);
      throw new WebApplicationException(Status.FORBIDDEN);
    } catch (IOException e) {
      logger.warn("Error removing page lock {} from repository", pageURI);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (IllegalStateException e) {
      logger.warn("Error unlocking page {}: {}", pageURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (ContentRepositoryException e) {
      logger.warn("Error unlocking page {}: {}", pageURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Create the response
    ResponseBuilder response = Response.ok();
    response.tag(ResourceUtils.getETagValue(page));
    response.lastModified(ResourceUtils.getModificationDate(page));
    return response.build();
  }

  /**
   * Publishes the page for the given date range and returns with a
   * <code>200</code> status code if the publish operation succeeds,
   * <code>400</code> if the page is not found or <code>403</code> if the page
   * is currently locked by a different user.
   * <p>
   * If <code>startdate</code> is not specified, then the page will be published
   * immediately. A missing <code>enddate</code> indicates to publish the page
   * forever.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @param startDateText
   *          the optional publishing start date
   * @param endDateText
   *          the optional publishing end date
   * @param modified
   *          <code>true</code> to update the page's modified date
   * @param asynchronous
   *          <code>true</code> to prevent blocking while the page is being
   *          published
   * @return the page
   */
  @PUT
  @Path("/{page}/publish")
  public Response publishPage(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @FormParam("startdate") String startDateText,
      @FormParam("enddate") String endDateText,
      @HeaderParam("If-Match") String ifMatchHeader,
      @FormParam("modified") boolean setModified,
      @FormParam("asynchronous") boolean asynchronous) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Extract the site
    Site site = getSite(request);

    // Make sure the content repository is writable
    if (site.getContentRepository().isReadOnly()) {
      logger.warn("Attempt to publish a page in a read-only content repository {}", site);
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    }

    WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true);
    ResourceURI workURI = new PageURIImpl(site, null, pageId, Resource.WORK);

    // Does the work page exist?
    Page workPage = null;
    try {
      if (!contentRepository.existsInAnyVersion(workURI))
        throw new WebApplicationException(Status.NOT_FOUND);
      workPage = (Page) contentRepository.get(workURI);
      if (workPage == null)
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      workURI.setPath(workPage.getURI().getPath());
    } catch (ContentRepositoryException e) {
      logger.warn("Error looking up page {} from repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Check the value of the If-Match header against the etag
    if (ifMatchHeader != null) {
      String etag = ResourceUtils.getETagValue(workPage);
      if (!etag.equals(ifMatchHeader)) {
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      }
    }

    // Make sure the page does not contain references to resources that don't
    // exist anymore.
    logger.debug("Checking referenced resources on {}", workPage);
    try {
      for (Pagelet pagelet : workPage.getPagelets()) {
        String resourceId = pagelet.getProperty("resourceid");
        if (StringUtils.isEmpty(resourceId))
          continue;
        ResourceURI resourceURI = contentRepository.getResourceURI(resourceId);
        if (resourceURI == null) {
          logger.warn("Page {} references non existing resource '{}'", workPage, resourceId);
          throw new WebApplicationException(Status.PRECONDITION_FAILED);
        }
        resourceURI.setVersion(Resource.LIVE);
        if (!contentRepository.exists(resourceURI)) {
          logger.warn("Page {} references unpublished resource '{}'", workPage, resourceURI);
          throw new WebApplicationException(Status.PRECONDITION_FAILED);
        }
      }
    } catch (ContentRepositoryException e) {
      logger.warn("Error looking up referenced resources", e);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Get the user
    User user = securityService.getUser();
    if (user == null)
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Make sure the user has publishing rights
    if (!SecurityUtils.userHasRole(user, SystemRole.PUBLISHER))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    boolean isAdmin = SecurityUtils.userHasRole(user, SystemRole.SITEADMIN);

    // If the page is locked by a different user, refuse
    if (workPage.isLocked() && (!workPage.getLockOwner().equals(user) && !isAdmin)) {
      return Response.status(Status.FORBIDDEN).build();
    }

    // Fix the dates
    Date startDate = null;
    Date endDate = null;
    DateFormat df = new SimpleDateFormat();

    // Parse the start date
    if (StringUtils.isNotBlank(startDateText)) {
      try {
        startDate = df.parse(startDateText);
      } catch (ParseException e) {
        try {
          startDate = WebloungeDateFormat.parseStatic(startDateText);
        } catch (ParseException e2) {
          throw new WebApplicationException(Status.BAD_REQUEST);
        }
      }
    } else {
      startDate = new Date();
    }

    // Parse the end date
    if (StringUtils.isNotBlank(endDateText)) {
      try {
        endDate = df.parse(endDateText);
      } catch (ParseException e) {
        try {
          endDate = WebloungeDateFormat.parseStatic(endDateText);
        } catch (ParseException e2) {
          throw new WebApplicationException(Status.BAD_REQUEST);
        }
      }
    }

    // Finally, perform the publish operation
    try {
      PageReader reader = new PageReader();
      Page livePage = reader.read(IOUtils.toInputStream(workPage.toXml(), "utf-8"), site);
      livePage.setVersion(Resource.LIVE);
      if (setModified)
        livePage.setModified(user, new Date());
      if (!livePage.isPublished())
        livePage.setPublished(user, startDate, endDate);
      contentRepository.putAsynchronously(livePage);
      contentRepository.deleteAsynchronously(workURI);
      logger.info("Page {} has been published by {}", workURI, user);
    } catch (SecurityException e) {
      logger.warn("Tried to publish page {} of site '{}' without permission", workURI, site);
      throw new WebApplicationException(Status.FORBIDDEN);
    } catch (IOException e) {
      logger.warn("Error writing published page {} to repository", workURI);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (IllegalStateException e) {
      logger.warn("Error publishing page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (ContentRepositoryException e) {
      logger.warn("Error publishing page {}: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (ParserConfigurationException e) {
      logger.warn("Error reading work page {} from repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (SAXException e) {
      logger.warn("Error reading work page {} from repository: {}", workURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Create the response
    ResponseBuilder response = Response.ok();
    response.tag(ResourceUtils.getETagValue(workPage));
    response.lastModified(workPage.getLastModified());
    return response.build();
  }

  /**
   * Unpublishes the page on the given date and returns with a <code>200</code>
   * status code if the unlock operation succeeds, <code>400</code> if the page
   * is not found or <code>403</code> if the page is locked by a different user.
   *
   * @param request
   *          the request
   * @param pageId
   *          the page identifier
   * @param enddate
   *          the optional publishing end date
   * @param asynchronous
   *          <code>true</code> to prevent blocking while the page is being
   *          unpublished
   * @return the page
   */
  @DELETE
  @Path("/{page}/publish")
  public Response unpublishPage(@Context HttpServletRequest request,
      @PathParam("page") String pageId,
      @HeaderParam("If-Match") String ifMatchHeader,
      @QueryParam("asynchronous") boolean asynchronous) {

    // Check the parameters
    if (pageId == null)
      return Response.status(Status.BAD_REQUEST).build();

    // Extract the site
    Site site = getSite(request);

    // Make sure the content repository is writable
    if (site.getContentRepository().isReadOnly()) {
      logger.warn("Attempt to unlock a page in a read-only content repository {}", site);
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    }

    WritableContentRepository contentRepository = (WritableContentRepository) getContentRepository(site, true);
    ResourceURI liveURI = new PageURIImpl(site, null, pageId, Resource.LIVE);

    // Does the page exist?
    Page livePage = null;
    try {
      if (!contentRepository.existsInAnyVersion(liveURI))
        throw new WebApplicationException(Status.NOT_FOUND);
      livePage = (Page) contentRepository.get(liveURI);
      if (livePage == null)
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      liveURI.setPath(livePage.getURI().getPath());
    } catch (ContentRepositoryException e) {
      logger.warn("Error lookup up page {} from repository: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (IllegalStateException e) {
      logger.warn("Error unpublishing page {}: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    }

    // Check the value of the If-Match header against the etag
    if (ifMatchHeader != null) {
      String etag = ResourceUtils.getETagValue(livePage);
      if (!etag.equals(ifMatchHeader)) {
        throw new WebApplicationException(Status.PRECONDITION_FAILED);
      }
    }

    // Get the user
    User user = securityService.getUser();
    if (user == null)
      throw new WebApplicationException(Status.UNAUTHORIZED);

    // Make sure the user has publishing rights
    if (!SecurityUtils.userHasRole(user, SystemRole.PUBLISHER))
      throw new WebApplicationException(Status.UNAUTHORIZED);

    boolean isAdmin = SecurityUtils.userHasRole(user, SystemRole.SITEADMIN);

    // If the page is locked by a different user, refuse
    if (livePage.isLocked() && (!livePage.getLockOwner().equals(user) && !isAdmin)) {
      return Response.status(Status.FORBIDDEN).build();
    }

    // Finally, perform the unpublish operation, including saving the current
    // live version of the page as the new work version.
    try {
      contentRepository.delete(liveURI);
      ResourceURI workURI = new ResourceURIImpl(liveURI, Resource.WORK);
      if (!contentRepository.exists(workURI)) {
        logger.debug("Creating work version of {}", workURI);
        PageReader reader = new PageReader();
        Page workPage = reader.read(IOUtils.toInputStream(livePage.toXml(), "utf-8"), site);
        workPage.setVersion(Resource.WORK);
        workPage.setPublished(null, null, null);
        contentRepository.putAsynchronously(workPage);
      }
      logger.info("Page {} has been unpublished by {}", liveURI, user);
    } catch (SecurityException e) {
      logger.warn("Tried to unpublish page {} of site '{}' without permission", liveURI, site);
      throw new WebApplicationException(Status.FORBIDDEN);
    } catch (IOException e) {
      logger.warn("Error removing writing unpublished page {} to repository", liveURI);
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (IllegalStateException e) {
      logger.warn("Error unpublishing page {}: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.PRECONDITION_FAILED);
    } catch (ContentRepositoryException e) {
      logger.warn("Error unpublishing page {}: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (ParserConfigurationException e) {
      logger.warn("Error reading live page {} from repository: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    } catch (SAXException e) {
      logger.warn("Error reading live page {} from repository: {}", liveURI, e.getMessage());
      throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
    }

    // Create the response
    ResponseBuilder response = Response.ok();
    response.tag(ResourceUtils.getETagValue(livePage));
    response.lastModified(ResourceUtils.getModificationDate(livePage));
    return response.build();
  }

  /**
   * Returns the endpoint documentation.
   *
   * @return the endpoint documentation
   */
  @GET
  @Path("/docs")
  @Produces(MediaType.TEXT_HTML)
  public String getDocumentation(@Context HttpServletRequest request) {
    if (docs == null) {
      String docsPath = request.getRequestURI();
      String docsPathExtension = request.getPathInfo();
      String servicePath = request.getRequestURI().substring(0, docsPath.length() - docsPathExtension.length());
      docs = PagesEndpointDocs.createDocumentation(servicePath);
    }
    return docs;
  }

  /**
   * Callback from OSGi to set the security service.
   *
   * @param securityService
   *          the security service
   */
  void setSecurityService(SecurityService securityService) {
    this.securityService = securityService;
  }

  /**
   * Loads the pages from the site's content repository.
   *
   * @param q
   *          the search query
   * @param details
   *          whether to display detailed information or just the header
   * @return the files
   * @throws WebApplicationException
   *           if the content repository is unavailable or if the content can't
   *           be loaded
   */
  private String loadResultSet(SearchQuery q, boolean details)
      throws WebApplicationException {
    ContentRepository repository = getContentRepository(q.getSite(), false);
    if (repository == null)
      throw new WebApplicationException(Status.SERVICE_UNAVAILABLE);

    SearchResult result = null;
    Page pageByPath = null;
    try {
      if (q.getVersion() == Resource.WORK && q.getPath() != null) {
        ResourceURI uri = new PageURIImpl(q.getSite(), q.getPath(), q.getVersion());
        pageByPath = (Page) repository.get(uri);
        int count = pageByPath != null ? 1 : 0;
        result = new SearchResultImpl(q, count, count);
      } else {
        result = repository.find(q);
      }
    } catch (ContentRepositoryException e) {
      throw new WebApplicationException(e);
    }

    StringBuffer buf = new StringBuffer("<pages ");
    buf.append("hits=\"").append(result.getHitCount()).append("\" ");
    buf.append("offset=\"").append(result.getOffset()).append("\" ");
    if (q.getLimit() > 0)
      buf.append("limit=\"").append(result.getLimit()).append("\" ");
    buf.append("page=\"").append(result.getPage()).append("\" ");
    buf.append("pagesize=\"").append(result.getPageSize()).append("\"");
    buf.append(">");
    if (pageByPath != null) {
      String xml = pageByPath.toXml();
      if (!details) {
        xml = xml.replaceAll("<body>.*</body>", "");
        xml = xml.replaceAll("<body/>", "");
      }
      buf.append(xml);
    } else {
      for (SearchResultItem item : result.getItems()) {
        String xml = null;
        if (details)
          xml = ((PageSearchResultItemImpl) item).getResourceXml();
        else
          xml = ((PageSearchResultItemImpl) item).getPageHeaderXml();
        buf.append(xml);
      }
    }
    buf.append("</pages>");

    return buf.toString();
  }

  /**
   * {@inheritDoc}
   *
   * @see java.lang.Object#toString()
   */
  @Override
  public String toString() {
    return "Pages rest endpoint";
  }

}
TOP

Related Classes of ch.entwine.weblounge.contentrepository.impl.endpoint.PagesEndpoint

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.