org.dishevelled.thumbnail.AbstractThumbnailManager.java Source code

Java tutorial

Introduction

Here is the source code for org.dishevelled.thumbnail.AbstractThumbnailManager.java

Source

/*
    
dsh-thumbnail  Implementation of the freedesktop.org thumbnail specification.
Copyright (c) 2013-2015 held jointly by the individual authors.
    
This library 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 3 of the License, or (at
your option) any later version.
    
This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; with out 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 library;  if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA.
    
> http://www.fsf.org/licensing/licenses/lgpl.html
> http://www.opensource.org/licenses/lgpl-license.php
    
*/
package org.dishevelled.thumbnail;

import java.awt.image.BufferedImage;

import java.io.File;
import java.io.IOException;

import java.net.URI;
import java.net.URL;

import java.nio.file.Files;

import java.nio.file.attribute.PosixFilePermission;

import java.util.EnumSet;
import java.util.Set;

import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

import org.apache.commons.codec.digest.DigestUtils;

import org.imgscalr.Scalr;

/**
 * Abstract thumbnail manager.
 *
 * @author  Michael Heuer
 */
public abstract class AbstractThumbnailManager implements ThumbnailManager {
    /** Root thumbnail directory. */
    private final File directory;

    /** Directory for normal size (128x128 pixel) thumbnails. */
    private final File normalDirectory;

    /** Directory for large size (256x256 pixel) thumbnails. */
    private final File largeDirectory;

    /** Directory for metadata about failed thumbnail images. */
    private final File failDirectory;

    /** Maximum cache size. */
    private static final int MAXIMUM_SIZE = 1000;

    /** Cache expire after write, in seconds. */
    private static final int EXPIRE = 30;

    /** Cache of thumbnails keyed by thumbnail file. */
    private final LoadingCache<File, Thumbnail> cache = CacheBuilder.newBuilder().maximumSize(MAXIMUM_SIZE)
            .expireAfterWrite(EXPIRE, TimeUnit.SECONDS).build(new CacheLoader<File, Thumbnail>() {
                @Override
                public Thumbnail load(final File key) throws IOException {
                    return Thumbnail.read(key);
                }
            });

    /** Normal (128x128 pixel) size. */
    private static final int NORMAL_SIZE = 128;

    /** Large (256x256 pixel) size. */
    private static final int LARGE_SIZE = 256;

    /** Directory permissions. */
    private static final Set<PosixFilePermission> DIRECTORY_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ,
            PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE);
    /** File permissions. */
    private static final Set<PosixFilePermission> FILE_PERMISSIONS = EnumSet.of(PosixFilePermission.OWNER_READ,
            PosixFilePermission.OWNER_WRITE);

    /**
     * Create a new abstract thumbnail manager with the specified root thumbnail directory.
     *
     * @param directory root thumbnail directory, must not be null
     */
    protected AbstractThumbnailManager(final File directory) {
        if (directory == null) {
            throw new IllegalArgumentException("directory must not be null");
        }
        this.directory = directory;
        normalDirectory = new File(this.directory, "normal");
        largeDirectory = new File(this.directory, "large");
        // todo: fail directory should be application specific; e.g. fail/dsh-thumbnail-1.0/
        failDirectory = new File(this.directory, "fail");
        normalDirectory.mkdirs();
        largeDirectory.mkdirs();
        failDirectory.mkdirs();

        fixDirectoryPermissions(this.directory);
        fixDirectoryPermissions(normalDirectory);
        fixDirectoryPermissions(largeDirectory);
        fixDirectoryPermissions(failDirectory);
    }

    /**
     * Fix the permissions of the specified file.
     *
     * @param file file
     */
    private void fixPermissions(final File file) {
        try {
            // set to 600
            Files.setPosixFilePermissions(file.toPath(), FILE_PERMISSIONS);
        } catch (Exception e) {
            // ignore
        }
    }

    /**
     * Fix the permissions of the specified directory.
     *
     * @param directory directory
     */
    private void fixDirectoryPermissions(final File directory) {
        try {
            // set to 700
            Files.setPosixFilePermissions(directory.toPath(), DIRECTORY_PERMISSIONS);
        } catch (Exception e) {
            // ignore
        }
    }

    /**
     * Attempt to create an empty fail file.
     *
     * @param failFile fail file to create
     */
    private void createFailFile(final File failFile) {
        try {
            failFile.createNewFile();
        } catch (IOException e) {
            // ignore
        }
    }

    /**
     * Create and return a thumbnail image for the specified URI.
     *
     * @param uri URI for the original image, must not be null
     * @param modificationTime modification time for the original image
     * @param thumbnailDirectory thumbnail directory
     * @param size size
     * @return a thumbnail image for the specified URI
     * @throws IOException if an I/O error occurs
     */
    private BufferedImage createThumbnail(final URI uri, final long modificationTime, final File thumbnailDirectory,
            final int size) throws IOException {
        if (uri == null) {
            throw new IllegalArgumentException("uri must not be null");
        }
        String fileName = DigestUtils.md5Hex(uri.toString()) + ".png";

        // fail fast if fail file exists
        File failFile = new File(failDirectory, fileName);
        if (failFile.exists()) {
            throw new IOException("cannot create a thumbnail image for " + uri);
        }

        // check if thumbnail exists
        File thumbnailFile = new File(thumbnailDirectory, fileName);
        if (thumbnailFile.exists()) {
            Thumbnail thumbnail = cache.getUnchecked(thumbnailFile);
            if (thumbnail.getModificationTime() == modificationTime) {
                return thumbnail.getImage();
            }
        }

        // load the image and create new thumbnail
        URL url = uri.toURL();
        BufferedImage image = null;
        try {
            image = ImageIO.read(url);
        } catch (IOException e) {
            createFailFile(failFile);
            throw new IOException("cannot create a thumbnail image for " + uri, e);
        }
        if (image == null) {
            createFailFile(failFile);
            throw new IOException("cannot create a thumbnail image for " + uri);
        }
        Thumbnail thumbnail = new Thumbnail(uri, modificationTime, image.getWidth(), image.getHeight(),
                Scalr.resize(image, size));

        File tmp = File.createTempFile("tmp", ".png", thumbnailDirectory);
        fixPermissions(tmp);
        thumbnail.write(tmp);
        com.google.common.io.Files.move(tmp, thumbnailFile);

        return thumbnail.getImage();
    }

    @Override
    public final BufferedImage createThumbnail(final URI uri, final long modificationTime) throws IOException {
        return createThumbnail(uri, modificationTime, normalDirectory, NORMAL_SIZE);
    }

    @Override
    public final BufferedImage createLargeThumbnail(final URI uri, final long modificationTime) throws IOException {
        return createThumbnail(uri, modificationTime, largeDirectory, LARGE_SIZE);
    }
}