com.liferay.portlet.documentlibrary.util.PDFProcessorImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.liferay.portlet.documentlibrary.util.PDFProcessorImpl.java

Source

/**
 * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
 *
 * 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 2.1 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; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 */

package com.liferay.portlet.documentlibrary.util;

import com.liferay.portal.kernel.configuration.Filter;
import com.liferay.portal.kernel.image.ImageToolUtil;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.messaging.DestinationNames;
import com.liferay.portal.kernel.messaging.MessageBusException;
import com.liferay.portal.kernel.messaging.MessageBusUtil;
import com.liferay.portal.kernel.process.ClassPathUtil;
import com.liferay.portal.kernel.process.ProcessCallable;
import com.liferay.portal.kernel.process.ProcessException;
import com.liferay.portal.kernel.process.ProcessExecutor;
import com.liferay.portal.kernel.repository.model.FileVersion;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.kernel.util.FileUtil;
import com.liferay.portal.kernel.util.InstancePool;
import com.liferay.portal.kernel.util.MimeTypesUtil;
import com.liferay.portal.kernel.util.OSDetector;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
import com.liferay.portal.util.PrefsPropsUtil;
import com.liferay.portal.util.PropsUtil;
import com.liferay.portal.util.PropsValues;
import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
import com.liferay.portlet.documentlibrary.store.DLStoreUtil;

import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.imageio.ImageIO;

import javax.portlet.PortletPreferences;

import org.apache.commons.lang.time.StopWatch;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDPage;

import org.im4java.core.ConvertCmd;
import org.im4java.core.IMOperation;
import org.im4java.process.ProcessStarter;

/**
 * @author Alexander Chow
 * @author Mika Koivisto
 * @author Juan Gonzlez
 * @author Sergio Gonzlez
 */
public class PDFProcessorImpl extends DefaultPreviewableProcessor implements PDFProcessor {

    public static PDFProcessorImpl getInstance() {
        return _instance;
    }

    public void generateImages(FileVersion fileVersion) throws Exception {
        Initializer._initializedInstance._generateImages(fileVersion);
    }

    public String getGlobalSearchPath() throws Exception {
        PortletPreferences preferences = PrefsPropsUtil.getPreferences();

        String globalSearchPath = preferences.getValue(PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, null);

        if (Validator.isNotNull(globalSearchPath)) {
            return globalSearchPath;
        }

        String filterName = null;

        if (OSDetector.isApple()) {
            filterName = "apple";
        } else if (OSDetector.isWindows()) {
            filterName = "windows";
        } else {
            filterName = "unix";
        }

        return PropsUtil.get(PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, new Filter(filterName));
    }

    public InputStream getPreviewAsStream(FileVersion fileVersion, int index) throws Exception {

        return Initializer._initializedInstance.doGetPreviewAsStream(fileVersion, index);
    }

    public int getPreviewFileCount(FileVersion fileVersion) {
        try {
            return Initializer._initializedInstance.doGetPreviewFileCount(fileVersion);
        } catch (Exception e) {
            _log.error(e, e);
        }

        return 0;
    }

    public long getPreviewFileSize(FileVersion fileVersion, int index) throws Exception {

        return Initializer._initializedInstance.doGetPreviewFileSize(fileVersion, index);
    }

    public InputStream getThumbnailAsStream(FileVersion fileVersion, int thumbnailIndex) throws Exception {

        return doGetThumbnailAsStream(fileVersion, thumbnailIndex);
    }

    public long getThumbnailFileSize(FileVersion fileVersion, int thumbnailIndex) throws Exception {

        return doGetThumbnailFileSize(fileVersion, thumbnailIndex);
    }

    public boolean hasImages(FileVersion fileVersion) {
        boolean hasImages = false;

        try {
            hasImages = _hasImages(fileVersion);

            if (!hasImages && isSupported(fileVersion)) {
                Initializer._initializedInstance._queueGeneration(fileVersion);
            }
        } catch (Exception e) {
            _log.error(e, e);
        }

        return hasImages;
    }

    public boolean isDocumentSupported(FileVersion fileVersion) {
        return Initializer._initializedInstance.isSupported(fileVersion);
    }

    public boolean isDocumentSupported(String mimeType) {
        return Initializer._initializedInstance.isSupported(mimeType);
    }

    public boolean isImageMagickEnabled() throws Exception {
        if (PrefsPropsUtil.getBoolean(PropsKeys.IMAGEMAGICK_ENABLED)) {
            return true;
        }

        if (!_warned) {
            StringBundler sb = new StringBundler(5);

            sb.append("Liferay is not configured to use ImageMagick for ");
            sb.append("generating Document Library previews and will default ");
            sb.append("to PDFBox. For better quality previews, install ");
            sb.append("ImageMagick and enable it in portal-ext.properties.");

            _log.warn(sb.toString());

            _warned = true;
        }

        return false;
    }

    public boolean isSupported(String mimeType) {
        if (Validator.isNull(mimeType)) {
            return false;
        }

        if (mimeType.equals(ContentTypes.APPLICATION_PDF) || mimeType.equals(ContentTypes.APPLICATION_X_PDF)) {

            return true;
        }

        if (DocumentConversionUtil.isEnabled()) {
            Set<String> extensions = MimeTypesUtil.getExtensions(mimeType);

            for (String extension : extensions) {
                extension = extension.substring(1);

                String[] targetExtensions = DocumentConversionUtil.getConversions(extension);

                if (Arrays.binarySearch(targetExtensions, "pdf") >= 0) {
                    return true;
                }
            }
        }

        return false;
    }

    public void reset() throws Exception {
        if (isImageMagickEnabled()) {
            _globalSearchPath = getGlobalSearchPath();

            ProcessStarter.setGlobalSearchPath(_globalSearchPath);

            _convertCmd = new ConvertCmd();
        } else {
            _convertCmd = null;
        }
    }

    public void trigger(FileVersion fileVersion) {
        Initializer._initializedInstance._queueGeneration(fileVersion);
    }

    @Override
    protected String getPreviewType(FileVersion fileVersion) {
        return PREVIEW_TYPE;
    }

    @Override
    protected String getThumbnailType(FileVersion fileVersion) {
        return THUMBNAIL_TYPE;
    }

    protected void initialize() {
        try {
            FileUtil.mkdirs(PREVIEW_TMP_PATH);
            FileUtil.mkdirs(THUMBNAIL_TMP_PATH);

            reset();
        } catch (Exception e) {
            _log.warn(e, e);
        }
    }

    private PDFProcessorImpl() {
    }

    private void _generateImages(FileVersion fileVersion) throws Exception {

        try {
            if (_hasImages(fileVersion)) {
                return;
            }

            String extension = fileVersion.getExtension();

            if (extension.equals("pdf")) {
                if (fileVersion instanceof LiferayFileVersion) {
                    try {
                        LiferayFileVersion liferayFileVersion = (LiferayFileVersion) fileVersion;

                        File file = liferayFileVersion.getFile(false);

                        _generateImages(fileVersion, file);

                        return;
                    } catch (UnsupportedOperationException uoe) {
                    }
                }

                InputStream inputStream = fileVersion.getContentStream(false);

                _generateImages(fileVersion, inputStream);
            } else if (DocumentConversionUtil.isEnabled()) {
                InputStream inputStream = fileVersion.getContentStream(false);

                String tempFileId = DLUtil.getTempFileId(fileVersion.getFileEntryId(), fileVersion.getVersion());

                File file = DocumentConversionUtil.convert(tempFileId, inputStream, extension, "pdf");

                _generateImages(fileVersion, file);
            }
        } catch (NoSuchFileEntryException nsfee) {
        } finally {
            _fileVersionIds.remove(fileVersion.getFileVersionId());
        }
    }

    private void _generateImages(FileVersion fileVersion, File file) throws Exception {

        if (isImageMagickEnabled()) {
            _generateImagesIM(fileVersion, file);
        } else {
            _generateImagesPB(fileVersion, file);
        }
    }

    private void _generateImages(FileVersion fileVersion, InputStream inputStream) throws Exception {

        if (isImageMagickEnabled()) {
            _generateImagesIM(fileVersion, inputStream);
        } else {
            _generateImagesPB(fileVersion, inputStream);
        }
    }

    private void _generateImagesIM(FileVersion fileVersion, File file) throws Exception {

        if (_isGeneratePreview(fileVersion)) {
            StopWatch stopWatch = null;

            if (_log.isInfoEnabled()) {
                stopWatch = new StopWatch();

                stopWatch.start();
            }

            _generateImagesIM(fileVersion, file, false);

            if (_log.isInfoEnabled()) {
                int previewFileCount = getPreviewFileCount(fileVersion);

                _log.info("ImageMagick generated " + previewFileCount + " preview pages for "
                        + fileVersion.getTitle() + " in " + stopWatch);
            }
        }

        if (_isGenerateThumbnail(fileVersion)) {
            StopWatch stopWatch = null;

            if (_log.isInfoEnabled()) {
                stopWatch = new StopWatch();

                stopWatch.start();
            }

            _generateImagesIM(fileVersion, file, true);

            if (_log.isInfoEnabled()) {
                _log.info("ImageMagick generated a thumbnail for " + fileVersion.getTitle() + " in " + stopWatch);
            }
        }
    }

    private void _generateImagesIM(FileVersion fileVersion, File file, boolean thumbnail) throws Exception {

        // Generate images

        String tempFileId = DLUtil.getTempFileId(fileVersion.getFileEntryId(), fileVersion.getVersion());

        IMOperation imOperation = new IMOperation();

        imOperation.alpha("off");

        imOperation.density(PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI,
                PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI);

        if (PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT != 0) {
            imOperation.adaptiveResize(PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH,
                    PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT);
        } else {
            imOperation.adaptiveResize(PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH);
        }

        imOperation.depth(PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DEPTH);

        if (thumbnail) {
            imOperation.addImage(file.getPath() + "[0]");
            imOperation.addImage(getThumbnailTempFilePath(tempFileId));
        } else {
            imOperation.addImage(file.getPath());
            imOperation.addImage(getPreviewTempFilePath(tempFileId, -1));
        }

        if (_log.isInfoEnabled()) {
            _log.info("Excecuting command 'convert " + imOperation + "'");
        }

        if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
            ProcessCallable<String> processCallable = new ImageMagickProcessCallable(_globalSearchPath,
                    imOperation.getCmdArgs());

            ProcessExecutor.execute(processCallable, ClassPathUtil.getPortalClassPath());
        } else {
            _convertCmd.run(imOperation);
        }

        // Store images

        if (thumbnail) {
            File thumbnailTempFile = getThumbnailTempFile(tempFileId);

            try {
                storeThumbnailImages(fileVersion, thumbnailTempFile);
            } finally {
                FileUtil.delete(thumbnailTempFile);
            }
        } else {

            // ImageMagick converts single page PDFs without appending an
            // index. Rename file for consistency.

            File singlePagePreviewFile = getPreviewTempFile(tempFileId, -1);

            if (singlePagePreviewFile.exists()) {
                singlePagePreviewFile.renameTo(getPreviewTempFile(tempFileId, 1));
            }

            int total = getPreviewTempFileCount(fileVersion);

            for (int i = 0; i < total; i++) {
                File previewTempFile = getPreviewTempFile(tempFileId, i + 1);

                try {
                    addFileToStore(fileVersion.getCompanyId(), PREVIEW_PATH, getPreviewFilePath(fileVersion, i + 1),
                            previewTempFile);
                } finally {
                    FileUtil.delete(previewTempFile);
                }
            }
        }
    }

    private void _generateImagesIM(FileVersion fileVersion, InputStream inputStream) throws Exception {

        File file = FileUtil.createTempFile(inputStream);

        try {
            _generateImagesIM(fileVersion, file);
        } finally {
            FileUtil.delete(file);
        }
    }

    private void _generateImagesPB(FileVersion fileVersion, File file) throws Exception {

        _generateImagesPB(fileVersion, new FileInputStream(file));
    }

    private void _generateImagesPB(FileVersion fileVersion, InputStream inputStream) throws Exception {

        boolean generatePreview = _isGeneratePreview(fileVersion);
        boolean generateThumbnail = _isGenerateThumbnail(fileVersion);

        PDDocument pdDocument = null;

        try {
            pdDocument = PDDocument.load(inputStream);

            PDDocumentCatalog pdDocumentCatalog = pdDocument.getDocumentCatalog();

            List<PDPage> pdPages = pdDocumentCatalog.getAllPages();

            for (int i = 0; i < pdPages.size(); i++) {
                PDPage pdPage = pdPages.get(i);

                if (generateThumbnail && (i == 0)) {
                    _generateImagesPB(fileVersion, pdPage, i);

                    if (_log.isInfoEnabled()) {
                        _log.info("PDFBox generated a thumbnail for " + fileVersion.getFileVersionId());
                    }
                }

                if (!generatePreview) {
                    break;
                }

                _generateImagesPB(fileVersion, pdPage, i + 1);
            }

            if (_log.isInfoEnabled() && generatePreview) {
                _log.info("PDFBox generated " + getPreviewFileCount(fileVersion) + " preview pages for "
                        + fileVersion.getFileVersionId());
            }
        } finally {
            if (pdDocument != null) {
                pdDocument.close();
            }
        }
    }

    private void _generateImagesPB(FileVersion fileVersion, PDPage pdPage, int index) throws Exception {

        // Generate images

        RenderedImage renderedImage = pdPage.convertToImage(BufferedImage.TYPE_INT_RGB,
                PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI);

        if (PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT != 0) {
            renderedImage = ImageToolUtil.scale(renderedImage, PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH,
                    PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT);
        } else {
            renderedImage = ImageToolUtil.scale(renderedImage,
                    PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH);
        }

        // Store images

        if (index == 0) {
            storeThumbnailImages(fileVersion, renderedImage);
        } else {
            File tempFile = null;

            try {
                String tempFileId = DLUtil.getTempFileId(fileVersion.getFileEntryId(), fileVersion.getVersion());

                tempFile = getPreviewTempFile(tempFileId, index);

                tempFile.createNewFile();

                ImageIO.write(renderedImage, PREVIEW_TYPE, new FileOutputStream(tempFile));

                addFileToStore(fileVersion.getCompanyId(), PREVIEW_PATH, getPreviewFilePath(fileVersion, index),
                        tempFile);
            } finally {
                FileUtil.delete(tempFile);
            }
        }
    }

    private boolean _hasImages(FileVersion fileVersion) throws Exception {
        if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED) {
            if (!DLStoreUtil.hasFile(fileVersion.getCompanyId(), REPOSITORY_ID,
                    getPreviewFilePath(fileVersion, 1))) {

                return false;
            }
        }

        if (PropsValues.DL_FILE_ENTRY_THUMBNAIL_ENABLED) {
            if (!hasThumbnail(fileVersion, THUMBNAIL_INDEX_DEFAULT)) {
                return false;
            }
        }

        try {
            if (isCustomThumbnailsEnabled(1)) {
                if (!hasThumbnail(fileVersion, THUMBNAIL_INDEX_CUSTOM_1)) {
                    return false;
                }
            }

            if (isCustomThumbnailsEnabled(2)) {
                if (!hasThumbnail(fileVersion, THUMBNAIL_INDEX_CUSTOM_2)) {
                    return false;
                }
            }
        } catch (Exception e) {
            _log.error(e, e);
        }

        return true;
    }

    private boolean _isGeneratePreview(FileVersion fileVersion) throws Exception {

        String previewFilePath = getPreviewFilePath(fileVersion, 1);

        if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED
                && !DLStoreUtil.hasFile(fileVersion.getCompanyId(), REPOSITORY_ID, previewFilePath)) {

            return true;
        } else {
            return false;
        }
    }

    private boolean _isGenerateThumbnail(FileVersion fileVersion) throws Exception {

        String thumbnailFilePath = getThumbnailFilePath(fileVersion, THUMBNAIL_INDEX_DEFAULT);

        if (PropsValues.DL_FILE_ENTRY_THUMBNAIL_ENABLED
                && !DLStoreUtil.hasFile(fileVersion.getCompanyId(), REPOSITORY_ID, thumbnailFilePath)) {

            return true;
        } else {
            return false;
        }
    }

    private void _queueGeneration(FileVersion fileVersion) {
        if (_fileVersionIds.contains(fileVersion.getFileVersionId())) {
            return;
        }

        boolean generateImages = false;

        String extension = fileVersion.getExtension();

        if (extension.equals("pdf")) {
            generateImages = true;
        } else if (DocumentConversionUtil.isEnabled()) {
            String[] conversions = DocumentConversionUtil.getConversions(extension);

            for (String conversion : conversions) {
                if (conversion.equals("pdf")) {
                    generateImages = true;

                    break;
                }
            }
        }

        if (generateImages) {
            _fileVersionIds.add(fileVersion.getFileVersionId());

            if (PropsValues.DL_FILE_ENTRY_PROCESSORS_TRIGGER_SYNCHRONOUSLY) {
                try {
                    MessageBusUtil.sendSynchronousMessage(DestinationNames.DOCUMENT_LIBRARY_PDF_PROCESSOR,
                            fileVersion);
                } catch (MessageBusException mbe) {
                    if (_log.isWarnEnabled()) {
                        _log.warn(mbe, mbe);
                    }
                }
            } else {
                MessageBusUtil.sendMessage(DestinationNames.DOCUMENT_LIBRARY_PDF_PROCESSOR, fileVersion);
            }
        }
    }

    private static Log _log = LogFactoryUtil.getLog(PDFProcessorImpl.class);

    private static PDFProcessorImpl _instance = new PDFProcessorImpl();

    static {
        InstancePool.put(PDFProcessorImpl.class.getName(), _instance);
    }

    private ConvertCmd _convertCmd;
    private List<Long> _fileVersionIds = new Vector<Long>();
    private String _globalSearchPath;
    private boolean _warned;

    private static class ImageMagickProcessCallable implements ProcessCallable<String> {

        public ImageMagickProcessCallable(String globalSearchPath, LinkedList<String> commandArguments) {

            _globalSearchPath = globalSearchPath;
            _commandArguments = commandArguments;
        }

        public String call() throws ProcessException {
            try {
                LiferayConvertCmd.run(_globalSearchPath, _commandArguments);
            } catch (Exception e) {
                throw new ProcessException(e);
            }

            return StringPool.BLANK;
        }

        private LinkedList<String> _commandArguments;
        private String _globalSearchPath;

    }

    private static class Initializer {

        private static PDFProcessorImpl _initializedInstance;

        static {
            _instance.initialize();

            _initializedInstance = _instance;
        }

    }

}