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

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

/*
*  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 static ch.entwine.weblounge.common.impl.content.image.ImageStyleUtils.DEFAULT_PREVIEW_FORMAT;

import ch.entwine.weblounge.common.content.PreviewGenerator;
import ch.entwine.weblounge.common.content.Resource;
import ch.entwine.weblounge.common.content.ResourceContent;
import ch.entwine.weblounge.common.content.ResourceURI;
import ch.entwine.weblounge.common.content.ResourceUtils;
import ch.entwine.weblounge.common.content.image.ImageStyle;
import ch.entwine.weblounge.common.impl.content.image.ImageStyleUtils;
import ch.entwine.weblounge.common.impl.language.LanguageUtils;
import ch.entwine.weblounge.common.impl.request.RequestUtils;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.language.UnknownLanguageException;
import ch.entwine.weblounge.common.repository.ContentRepository;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.repository.ResourceSerializer;
import ch.entwine.weblounge.common.repository.ResourceSerializerService;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.ImageScalingMode;
import ch.entwine.weblounge.common.site.Module;
import ch.entwine.weblounge.common.site.Site;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.osgi.service.component.ComponentContext;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
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.ws.rs.core.StreamingOutput;

/**
* This class implements the <code>REST</code> endpoint for resource previews.
*/
@Path("/")
public class PreviewsEndpoint extends ContentRepositoryEndpoint {

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

  /** The list of image styles */
  private final List<ImageStyle> styles = new ArrayList<ImageStyle>();

  /** The request environment */
  protected Environment environment = Environment.Production;

  /** The resource serializer service */
  private ResourceSerializerService serializerService = null;

  /**
   * OSGi callback on component inactivation.
   *
   * @param ctx
   *          the component context
   */
  void deactivate(ComponentContext ctx) {
    styles.clear();
  }

  /**
   * Returns the resource with the given identifier and styled using the
   * requested image style or a <code>404</code> if the resource or the resource
   * content could not be found.
   * <p>
   * If the content is not available in the requested language, the original
   * language version is used.
   *
   * @param request
   *          the request
   * @param resourceId
   *          the resource identifier
   * @param languageId
   *          the language identifier
   * @param styleId
   *          the image style identifier
   * @return the image
   */
  @GET
  @Path("/{resource}/locales/{language}/styles/{style}")
  public Response getPreview(@Context HttpServletRequest request,
      @PathParam("resource") String resourceId,
      @PathParam("language") String languageId,
      @PathParam("style") String styleId,
      @QueryParam("version") @DefaultValue("0") long version,
      @QueryParam("force") @DefaultValue("false") boolean force) {

    // Check the parameters
    if (resourceId == null)
      throw new WebApplicationException(Status.BAD_REQUEST);

    // Get the resource
    final Site site = getSite(request);
    final Resource<?> resource = loadResource(request, resourceId, null, version);
    if (resource == null)
      throw new WebApplicationException(Status.NOT_FOUND);

    // Extract the language
    Language language = null;
    try {
      language = LanguageUtils.getLanguage(languageId);
      if (!resource.supportsLanguage(language)) {
        if (!resource.contents().isEmpty())
          language = resource.getOriginalContent().getLanguage();
        else if (resource.supportsLanguage(site.getDefaultLanguage()))
          language = site.getDefaultLanguage();
        else if (resource.languages().size() == 1)
          language = resource.languages().iterator().next();
        else
          throw new WebApplicationException(Status.NOT_FOUND);
      }
    } catch (UnknownLanguageException e) {
      throw new WebApplicationException(Status.BAD_REQUEST);
    }

    // Search the site for the image style
    ImageStyle style = null;
    for (Module m : site.getModules()) {
      style = m.getImageStyle(styleId);
      if (style != null) {
        break;
      }
    }

    // Search the global styles
    if (style == null) {
      for (ImageStyle s : styles) {
        if (s.getIdentifier().equals(styleId)) {
          style = s;
          break;
        }
      }
    }

    // The image style was not found
    if (style == null)
      throw new WebApplicationException(Status.BAD_REQUEST);

    // Load the input stream from the scaled image
    File scaledResourceFile = ImageStyleUtils.getScaledFile(resource, language, style);

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

    ResourceURI resourceURI = resource.getURI();
    final ContentRepository contentRepository = getContentRepository(site, false);

    // When there is no scaling required, just return the original
    if (ImageScalingMode.None.equals(style.getScalingMode())) {
      return getResourceContent(request, resource, language);
    }

    // Find a serializer
    ResourceSerializer<?, ?> serializer = serializerService.getSerializerByType(resourceURI.getType());
    if (serializer == null)
      throw new WebApplicationException(Status.PRECONDITION_FAILED);

    // Does the serializer come with a preview generator?
    PreviewGenerator previewGenerator = serializer.getPreviewGenerator(resource);
    if (previewGenerator == null)
      throw new WebApplicationException(Status.NOT_FOUND);

    // Load the resource contents from the repository
    InputStream resourceInputStream = null;
    long contentLength = -1;

    // Load the input stream from the scaled image
    InputStream contentRepositoryIs = null;
    FileOutputStream fos = null;
    try {
      long resourceLastModified = ResourceUtils.getModificationDate(resource, language).getTime();
      if (!scaledResourceFile.isFile() || scaledResourceFile.lastModified() < resourceLastModified) {
        if (!force)
          throw new WebApplicationException(Response.Status.NOT_FOUND);

        contentRepositoryIs = contentRepository.getContent(resourceURI, language);
        scaledResourceFile = ImageStyleUtils.createScaledFile(resource, language, style);
        scaledResourceFile.setLastModified(Math.max(new Date().getTime(), resourceLastModified));
        fos = new FileOutputStream(scaledResourceFile);
        logger.debug("Creating scaled image '{}' at {}", resource, scaledResourceFile);

        previewGenerator.createPreview(resource, environment, language, style, DEFAULT_PREVIEW_FORMAT, contentRepositoryIs, fos);
        if (scaledResourceFile.length() == 0) {
          logger.debug("Error scaling '{}': file size is 0", resourceURI);
          IOUtils.closeQuietly(resourceInputStream);
          FileUtils.deleteQuietly(scaledResourceFile);
        }
      }

      // Did scaling work? If not, cleanup and tell the user
      if (scaledResourceFile.length() == 0)
        throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);

      // The scaled resource should now exist
      resourceInputStream = new FileInputStream(scaledResourceFile);
      contentLength = scaledResourceFile.length();

    } catch (WebApplicationException e) {
      IOUtils.closeQuietly(resourceInputStream);
      FileUtils.deleteQuietly(scaledResourceFile);
      if (scaledResourceFile != null)
        deleteIfEmpty(scaledResourceFile.getParentFile());
      throw e;
    } catch (ContentRepositoryException e) {
      logger.error("Error loading {} image '{}' from {}: {}", new Object[] {
          language,
          resource,
          contentRepository,
          e.getMessage() });
      logger.error(e.getMessage(), e);
      IOUtils.closeQuietly(resourceInputStream);
      FileUtils.deleteQuietly(scaledResourceFile);
      if (scaledResourceFile != null)
        deleteIfEmpty(scaledResourceFile.getParentFile());
      throw new WebApplicationException();
    } catch (IOException e) {
      logger.error("Error scaling image '{}': {}", resourceURI, e.getMessage());
      IOUtils.closeQuietly(resourceInputStream);
      FileUtils.deleteQuietly(scaledResourceFile);
      if (scaledResourceFile != null)
        deleteIfEmpty(scaledResourceFile.getParentFile());
      throw new WebApplicationException();
    } catch (IllegalArgumentException e) {
      logger.error("Image '{}' is of unsupported format: {}", resourceURI, e.getMessage());
      IOUtils.closeQuietly(resourceInputStream);
      FileUtils.deleteQuietly(scaledResourceFile);
      if (scaledResourceFile != null)
        deleteIfEmpty(scaledResourceFile.getParentFile());
      throw new WebApplicationException();
    } catch (Throwable t) {
      logger.error("Error scaling image '{}': {}", resourceURI, t.getMessage());
      IOUtils.closeQuietly(resourceInputStream);
      FileUtils.deleteQuietly(scaledResourceFile);
      if (scaledResourceFile != null)
        deleteIfEmpty(scaledResourceFile.getParentFile());
      throw new WebApplicationException();
    } finally {
      IOUtils.closeQuietly(contentRepositoryIs);
      IOUtils.closeQuietly(fos);
    }

    // Create the response
    final InputStream is = resourceInputStream;
    ResponseBuilder response = Response.ok(new StreamingOutput() {
      public void write(OutputStream os) throws IOException,
          WebApplicationException {
        try {
          IOUtils.copy(is, os);
          os.flush();
        } catch (IOException e) {
          if (!RequestUtils.isCausedByClient(e))
            logger.error("Error writing preview to client", e);
        } finally {
          IOUtils.closeQuietly(is);
        }
      }
    });

    // Add mime type header
    String mimetype = previewGenerator.getContentType(resource, language, style);
    if (mimetype == null)
      mimetype = MediaType.APPLICATION_OCTET_STREAM;
    response.type(mimetype);

    // Add last modified header
    response.lastModified(new Date(scaledResourceFile.lastModified()));

    // Add ETag header
    String eTag = ResourceUtils.getETagValue(scaledResourceFile);
    response.tag(eTag);

    // Add filename header
    String filename = null;
    ResourceContent resourceContent = resource.getContent(language);
    if (resourceContent != null)
      filename = resourceContent.getFilename();
    if (StringUtils.isBlank(filename))
      filename = scaledResourceFile.getName();
    response.header("Content-Disposition", "inline; filename=" + filename);

    // Content length
    response.header("Content-Length", Long.toString(contentLength));

    // Send the response
    return response.build();
  }

  /**
   * Deletes the preview images for the given resource and language.
   *
   * @param request
   *          the request
   * @param resourceId
   *          the resource identifier
   * @param languageId
   *          the language identifier
   */
  @POST
  @Path("/")
  public Response createPreviews(@Context HttpServletRequest request) {
    Site site = super.getSite(request);
    final ContentRepository contentRepository = getContentRepository(site, false);
    new Thread(new Runnable() {
      public void run() {
        try {
          contentRepository.createPreviews();
        } catch (ContentRepositoryException e) {
          logger.warn("Preview generation returned with an error: {}", e.getMessage());
        }
      }
    }).start();
    return Response.ok().build();
  }

  /**
   * Deletes all preview images.
   *
   * @param request
   *          the request
   */
  @DELETE
  @Path("/")
  public Response removePreviews(@Context HttpServletRequest request) {
    Site site = super.getSite(request);
    File previewsDir = ImageStyleUtils.getDirectory(site);
    if (FileUtils.deleteQuietly(previewsDir))
      return Response.ok().build();
    else
      return Response.serverError().build();
  }

  /**
   * Deletes all preview images for the given resource.
   *
   * @param request
   *          the request
   * @param resourceId
   *          the resource identifier
   */
  @DELETE
  @Path("/{resource}")
  public Response removePreviewsByStyle(@Context HttpServletRequest request,
      @PathParam("resource") String styleId) {
    return removePreview(request, null, null, null);
  }

  /**
   * Deletes the preview images for the given style and language.
   *
   * @param request
   *          the request
   * @param resourceId
   *          the resource identifier
   * @param languageId
   *          the language identifier
   */
  @DELETE
  @Path("/styles/{style}")
  public Response removePreview(@Context HttpServletRequest request,
      @PathParam("style") String styleId) {
    Site site = super.getSite(request);

    // Check the parameters
    if (styleId == null)
      throw new WebApplicationException(Status.BAD_REQUEST);

    // Search the site for the image style
    ImageStyle style = null;
    for (Module m : site.getModules()) {
      style = m.getImageStyle(styleId);
      if (style != null) {
        break;
      }
    }

    // Search the global styles
    if (style == null) {
      for (ImageStyle s : styles) {
        if (s.getIdentifier().equals(styleId)) {
          style = s;
          break;
        }
      }
    }

    // The image style was not found
    if (style == null)
      throw new WebApplicationException(Status.BAD_REQUEST);

    File previewsDir = ImageStyleUtils.getDirectory(site, style);
    if (FileUtils.deleteQuietly(previewsDir))
      return Response.ok().build();
    else
      return Response.serverError().build();
  }

  /**
   * Deletes the preview images for the given resource, the language and image
   * style.
   *
   * @param request
   *          the request
   * @param resourceId
   *          the resource identifier
   * @param languageId
   *          the language identifier
   * @param styleId
   *          the image style identifier
   */
  @DELETE
  @Path("/{resource}/locales/{language}/styles/{style}")
  public Response removePreview(@Context HttpServletRequest request,
      @PathParam("resource") String resourceId,
      @PathParam("language") String languageId,
      @PathParam("style") String styleId) {

    // Check the parameters
    if (resourceId == null)
      throw new WebApplicationException(Status.BAD_REQUEST);

    // Get the resource
    final Site site = getSite(request);
    final Resource<?> resource = loadResource(request, resourceId, null);
    if (resource == null)
      throw new WebApplicationException(Status.NOT_FOUND);

    // Extract the language
    List<Language> languages = new ArrayList<Language>();
    if (languageId != null) {
      try {
        languages.add(LanguageUtils.getLanguage(languageId));
      } catch (UnknownLanguageException e) {
        throw new WebApplicationException(Status.BAD_REQUEST);
      }
    } else {
      languages.addAll(resource.languages());
    }

    // Search the site for the image style (if applicable)
    List<ImageStyle> removeStyles = new ArrayList<ImageStyle>();
    if (styleId != null) {
      for (Module m : site.getModules()) {
        ImageStyle s = m.getImageStyle(styleId);
        if (s != null) {
          removeStyles.add(s);
          break;
        }
      }

      // Search the global styles
      if (removeStyles.size() > 0) {
        for (ImageStyle s : this.styles) {
          if (s.getIdentifier().equals(styleId)) {
            removeStyles.add(s);
            break;
          }
        }
      }
    } else {
      removeStyles.addAll(this.styles);
      for (Module m : site.getModules()) {
        removeStyles.addAll(Arrays.asList(m.getImageStyles()));
      }
    }

    ResourceURI resourceURI = resource.getURI();
    final ContentRepository contentRepository = getContentRepository(site, false);

    // Load the resource versions from the repository
    ResourceURI[] versions = null;
    try {
      versions = contentRepository.getVersions(resourceURI);
    } catch (ContentRepositoryException e1) {
      throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
    }

    // Remove the preview for all versions in the specified styles and languages
    for (ResourceURI u : versions) {
      for (ImageStyle style : removeStyles) {
        for (Language language : languages) {
          deletePreview(resource, u.getVersion(), style, language);
        }
      }
    }

    // Send the response
    return Response.status(Status.OK).build();
  }

  /**
   * Deletes a single preview image.
   *
   * @param resource
   *          the resource
   * @param version
   *          the resource version
   * @param style
   *          the image style
   * @param language
   *          the language
   */
  private void deletePreview(Resource<?> resource, long version,
      ImageStyle style, Language language) {

    // Find a serializer
    ResourceSerializer<?, ?> serializer = serializerService.getSerializerByType(resource.getURI().getType());
    if (serializer == null)
      throw new WebApplicationException(Status.PRECONDITION_FAILED);

    // Does the serializer come with a preview generator?
    PreviewGenerator previewGenerator = serializer.getPreviewGenerator(resource);
    if (previewGenerator == null)
      throw new WebApplicationException(Status.NOT_FOUND);

    // Load the input stream from the scaled image
    File scaledResourceFile = null;
    try {
      scaledResourceFile = ImageStyleUtils.getScaledFile(resource, language, style);
      if (scaledResourceFile.exists()) {
        logger.debug("Deleting preview at {}", scaledResourceFile);
        FileUtils.deleteQuietly(scaledResourceFile);
        File parentDir = scaledResourceFile.getParentFile();
        while (parentDir.isDirectory() && parentDir.list().length == 0) {
          logger.debug("Deleting empty preview directory {}", parentDir);
          FileUtils.deleteQuietly(parentDir);
          parentDir = parentDir.getParentFile();
        }
      }
    } catch (Throwable t) {
      logger.error("Error removing preview image '{}': {}", resource.getURI(), t.getMessage());
      throw new WebApplicationException();
    }
  }

  /**
   * Deletes the directory if it is empty and tries the same for the parent
   * directory.
   *
   * @param dir
   *          the directory
   */
  private void deleteIfEmpty(File dir) {
    while (dir != null && dir.isDirectory() && (dir.listFiles() == null || dir.listFiles().length == 0)) {
      FileUtils.deleteQuietly(dir);
      dir = dir.getParentFile();
    }
  }

  /**
   * Returns the list of image styles that are registered for a site.
   *
   * @param request
   *          the request
   * @return the list of image styles
   */
  @GET
  @Produces(MediaType.TEXT_XML)
  @Path("/styles")
  public Response getImagestyles(@Context HttpServletRequest request) {
    Site site = getSite(request);
    if (site == null)
      throw new WebApplicationException(Status.NOT_FOUND);

    StringBuffer buf = new StringBuffer("<styles>");

    // Add styles of current site
    for (Module m : site.getModules()) {
      ImageStyle[] styles = m.getImageStyles();
      for (ImageStyle style : styles) {
        buf.append(style.toXml());
      }
    }

    // Add global styles
    for (ImageStyle style : styles) {
      buf.append(style.toXml());
    }

    buf.append("</styles>");

    ResponseBuilder response = Response.ok(buf.toString());
    return response.build();
  }

  /**
   * Returns the image styles or a <code>404</code>.
   *
   * @param request
   *          the request
   * @param styleId
   *          the image style identifier
   * @return the image
   */
  @GET
  @Produces(MediaType.TEXT_XML)
  @Path("/styles/{style}")
  public Response getImagestyle(@Context HttpServletRequest request,
      @PathParam("style") String styleId) {
    Site site = getSite(request);

    // Search styles of current site
    for (Module m : site.getModules()) {
      ImageStyle style = m.getImageStyle(styleId);
      if (style != null) {
        ResponseBuilder response = Response.ok(style.toXml());
        return response.build();
      }
    }

    // Search global styles
    for (ImageStyle style : styles) {
      if (style.getIdentifier().equals(styleId)) {
        ResponseBuilder response = Response.ok(style.toXml());
        return response.build();
      }
    }

    // The image style was not found
    throw new WebApplicationException(Status.NOT_FOUND);
  }

  /**
   * 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 = PreviewsEndpointDocs.createDocumentation(servicePath);
    }
    return docs;
  }

  /**
   * Callback from OSGi declarative services on registration of a new image
   * style in the service registry.
   *
   * @param style
   *          the image style
   */
  void addImageStyle(ImageStyle style) {
    styles.add(style);
  }

  /**
   * Callback from OSGi declarative services on removal of an image style from
   * the service registry.
   *
   * @param style
   *          the image style
   */
  void removeImageStyle(ImageStyle style) {
    styles.remove(style);
  }

  /**
   * Callback from the OSGi environment when the environment becomes published.
   *
   * @param environment
   *          the environment
   */
  void setEnvironment(Environment environment) {
    this.environment = environment;
  }

  /**
   * OSGi callback that is setting the resource serializer.
   *
   * @param serializer
   *          the resource serializer service
   */
  void setResourceSerializer(ResourceSerializerService serializer) {
    this.serializerService = serializer;
  }

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

}
TOP

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

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.