ch.entwine.weblounge.contentrepository.impl.PreviewGeneratorWorker.java Source code

Java tutorial

Introduction

Here is the source code for ch.entwine.weblounge.contentrepository.impl.PreviewGeneratorWorker.java

Source

/*
 *  Weblounge: Web Content Management System
 *  Copyright (c) 2012 The Weblounge Team
 *  http://weblounge.o2it.ch
 *
 *  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;

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.ImagePreviewGenerator;
import ch.entwine.weblounge.common.content.image.ImageStyle;
import ch.entwine.weblounge.common.impl.content.image.ImageStyleUtils;
import ch.entwine.weblounge.common.language.Language;
import ch.entwine.weblounge.common.repository.ContentRepositoryException;
import ch.entwine.weblounge.common.repository.ResourceSerializer;
import ch.entwine.weblounge.common.site.Environment;
import ch.entwine.weblounge.common.site.ImageScalingMode;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * Worker implementation that creates a preview in a separate thread.
 */
class PreviewGeneratorWorker implements Runnable {

    /** The logging facility */
    private final Logger logger = LoggerFactory.getLogger(PreviewGeneratorWorker.class);

    /** The content repository */
    private AbstractContentRepository contentRepository = null;

    /** The resource to render */
    private Resource<?> resource = null;

    /** The list of image styles to produce */
    private List<ImageStyle> styles = null;

    /** The environment */
    private Environment environment = null;

    /** The languages to render */
    private List<Language> languages = null;

    /** The format */
    private String format = null;

    /** Flag to indicate a canceled preview generation */
    private boolean canceled = false;

    /**
     * Creates a new preview worker who will create the corresponding previews for
     * the given resource and style.
     * 
     * @param resource
     *          the resource
     * @param environment
     *          the current environment
     * @param languages
     *          the languages
     * @param styles
     *          the image styles
     */
    public PreviewGeneratorWorker(AbstractContentRepository repository, Resource<?> resource,
            Environment environment, List<Language> languages, List<ImageStyle> styles, String format) {
        if (languages == null || languages.size() == 0)
            throw new IllegalArgumentException("At least one language must be provided");
        if (styles == null || styles.size() == 0)
            throw new IllegalArgumentException("At least one preview style must be provided");
        this.contentRepository = repository;
        this.resource = resource;
        this.environment = environment;
        this.languages = languages;
        this.styles = styles;
        this.format = format;
    }

    /**
     * Indicates to this preview generation worker to cancel the current
     * operation.
     */
    public void cancel() {
        this.canceled = true;
    }

    /**
     * {@inheritDoc}
     * 
     * @see java.lang.Runnable#run()
     */
    public void run() {
        ResourceURI resourceURI = resource.getURI();
        String resourceType = resourceURI.getType();

        try {

            // Find the resource serializer
            ResourceSerializer<?, ?> serializer = contentRepository.getSerializerByType(resourceType);
            if (serializer == null) {
                logger.warn("Unable to index resources of type '{}': no resource serializer found", resourceType);
                return;
            }

            // Does the serializer come with a preview generator?
            PreviewGenerator previewGenerator = serializer.getPreviewGenerator(resource);
            if (previewGenerator == null) {
                logger.debug("Resource type '{}' does not support previews", resourceType);
                return;
            }

            // Create the scaled images
            String mimeType = "image/" + format;
            ResourceSerializer<?, ?> s = contentRepository.getSerializerByMimeType(mimeType);
            if (s == null) {
                logger.warn("No resource serializer is capable of dealing with resources of format '{}'", mimeType);
                return;
            } else if (!(s instanceof ImageResourceSerializer)) {
                logger.warn("Resource serializer lookup for format '{}' returned {}", format, s.getClass());
                return;
            }

            // Find us an image serializer
            ImageResourceSerializer irs = (ImageResourceSerializer) s;
            ImagePreviewGenerator imagePreviewGenerator = (ImagePreviewGenerator) irs.getPreviewGenerator(format);
            if (imagePreviewGenerator == null) {
                logger.warn("Image resource serializer {} does not provide support for '{}'", irs, format);
                return;
            }

            // Now scale the original preview according to the existing styles
            for (Language l : languages) {
                if (!resource.supportsContentLanguage(l))
                    continue;

                // Have we been told to stop doing work in the meantime?
                if (canceled)
                    return;

                // Create the original preview image for every language
                File originalPreview = null;
                if (!resource.supportsContentLanguage(l))
                    continue;
                originalPreview = createPreview(resource, null, l, previewGenerator, format);
                if (originalPreview == null || !originalPreview.exists() || originalPreview.length() == 0) {
                    logger.warn("Preview generation for {} failed", resource);
                    return;
                }

                long resourceLastModified = ResourceUtils.getModificationDate(resource, l).getTime();

                // Create the remaining styles
                for (ImageStyle style : styles) {

                    // Have we been told to stop doing work in the meantime?
                    if (canceled)
                        return;

                    // The original has been produced already
                    if (ImageScalingMode.None.equals(style.getScalingMode()))
                        continue;

                    FileInputStream fis = null;
                    FileOutputStream fos = null;
                    try {
                        File scaledFile = ImageStyleUtils.createScaledFile(resource, l, style);

                        // Create the file if it doesn't exist or if it is out dated. Note
                        // that the last modified date of a file has a precision of seconds
                        if (!scaledFile.isFile()
                                || FileUtils.isFileOlder(scaledFile, new Date(resourceLastModified))) {

                            logger.info("Creating preview at {}", scaledFile.getAbsolutePath());

                            fis = new FileInputStream(originalPreview);
                            fos = new FileOutputStream(scaledFile);
                            imagePreviewGenerator.createPreview(originalPreview, environment, l, style, format, fis,
                                    fos);
                            scaledFile.setLastModified(Math.max(new Date().getTime(), resourceLastModified));

                            // Store the style definition used while creating the preview
                            File baseDir = ImageStyleUtils.getDirectory(resource.getURI().getSite(), style);
                            File definitionFile = new File(baseDir, "style.xml");
                            if (!definitionFile.isFile()) {
                                logger.debug("Storing style definition at {}", definitionFile);
                                definitionFile.createNewFile();
                                FileUtils.copyInputStreamToFile(IOUtils.toInputStream(style.toXml(), "UTF-8"),
                                        definitionFile);
                            }
                        } else {
                            logger.debug("Skipping creation of existing '{}' preview of {}", style, resource);
                        }

                    } catch (Throwable t) {
                        logger.error("Error scaling {}: {}", originalPreview, t.getMessage());
                        continue;
                    } finally {
                        IOUtils.closeQuietly(fis);
                        IOUtils.closeQuietly(fos);
                    }
                }
            }

        } finally {
            if (canceled)
                logger.debug("Preview operation for {} has been canceled", resource.getIdentifier());
            contentRepository.previewCreated(resource);
        }
    }

    /**
     * Creates the actual preview.
     * 
     * @param resource
     *          the resource
     * @param style
     *          the image style
     * @param language
     *          the language
     * @param previewGenerator
     *          the preview generator
     * @param the
     *          preview format
     * @return returns the preview file
     */
    private File createPreview(Resource<?> resource, ImageStyle style, Language language,
            PreviewGenerator previewGenerator, String format) {

        ResourceURI resourceURI = resource.getURI();
        String resourceType = resourceURI.getType();

        // Create the filename
        ResourceContent content = resource.getContent(language);

        // Initiate creation of previews
        InputStream resourceInputStream = null;
        InputStream contentRepositoryIs = null;
        FileOutputStream fos = null;
        File scaledResourceFile = null;

        try {
            scaledResourceFile = ImageStyleUtils.getScaledFile(resource, language, style);

            // Find the modification date
            long lastModified = ResourceUtils.getModificationDate(resource, language).getTime();

            // Create the file if it doesn't exist or if it is out dated. Note that
            // the last modified date of a file has a precision of seconds
            if (!scaledResourceFile.isFile() || FileUtils.isFileOlder(scaledResourceFile, new Date(lastModified))) {
                contentRepositoryIs = contentRepository.getContent(resourceURI, language);

                // Is this local content?
                if (contentRepositoryIs == null && content != null && content.getExternalLocation() != null) {
                    contentRepositoryIs = content.getExternalLocation().openStream();
                }

                // Create the parent directory
                File scaledResourceDir = scaledResourceFile.getParentFile();
                if (!scaledResourceDir.isDirectory() && !scaledResourceDir.mkdirs()) {
                    AbstractContentRepository.logger.warn("Error creating parent directory of preview file {}",
                            scaledResourceFile.getAbsolutePath());
                    return null;
                }

                // Create the file if it doesn't exist
                if (!scaledResourceFile.isFile() && !scaledResourceFile.createNewFile()) {
                    AbstractContentRepository.logger.warn("Error creating preview file {}",
                            scaledResourceFile.getAbsolutePath());
                    return null;
                }

                // Create the preview
                fos = new FileOutputStream(scaledResourceFile);
                AbstractContentRepository.logger.debug("Creating preview of '{}' at {}", resource,
                        scaledResourceFile);
                previewGenerator.createPreview(resource, environment, language, style, format, contentRepositoryIs,
                        fos);
            }

        } catch (ContentRepositoryException e) {
            AbstractContentRepository.logger.error("Error loading {} {} '{}' from {}: {}",
                    new Object[] { language, resourceType, resource, this, e.getMessage() });
            AbstractContentRepository.logger.error(e.getMessage(), e);
            IOUtils.closeQuietly(resourceInputStream);
        } catch (IOException e) {
            AbstractContentRepository.logger.warn("Error creating preview for {} '{}': {}",
                    new Object[] { resourceType, resourceURI, e.getMessage() });
            IOUtils.closeQuietly(resourceInputStream);
        } catch (Throwable t) {
            AbstractContentRepository.logger.warn("Error creating preview for {} '{}': {}",
                    new Object[] { resourceType, resourceURI, t.getMessage() });
            IOUtils.closeQuietly(resourceInputStream);

        } finally {
            IOUtils.closeQuietly(contentRepositoryIs);
            IOUtils.closeQuietly(fos);

            // Make sure corrupted preview images are being deleted
            File f = scaledResourceFile;
            if (f != null && f.length() == 0) {
                FileUtils.deleteQuietly(f);
                f = f.getParentFile();
                while (f != null && f.isDirectory() && (f.listFiles() == null || f.listFiles().length == 0)) {
                    FileUtils.deleteQuietly(f);
                    f = f.getParentFile();
                }
            }
        }

        return scaledResourceFile;
    }

}