001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portlet.documentlibrary.util;
016    
017    import com.liferay.portal.kernel.image.GhostscriptUtil;
018    import com.liferay.portal.kernel.image.ImageToolUtil;
019    import com.liferay.portal.kernel.lar.PortletDataContext;
020    import com.liferay.portal.kernel.log.Log;
021    import com.liferay.portal.kernel.log.LogFactoryUtil;
022    import com.liferay.portal.kernel.messaging.DestinationNames;
023    import com.liferay.portal.kernel.repository.model.FileEntry;
024    import com.liferay.portal.kernel.repository.model.FileVersion;
025    import com.liferay.portal.kernel.util.ContentTypes;
026    import com.liferay.portal.kernel.util.FileUtil;
027    import com.liferay.portal.kernel.util.GetterUtil;
028    import com.liferay.portal.kernel.util.MimeTypesUtil;
029    import com.liferay.portal.kernel.util.StreamUtil;
030    import com.liferay.portal.kernel.util.Validator;
031    import com.liferay.portal.kernel.xml.Element;
032    import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
033    import com.liferay.portal.util.PropsValues;
034    import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
035    import com.liferay.portlet.documentlibrary.store.DLStoreUtil;
036    
037    import java.awt.image.BufferedImage;
038    import java.awt.image.RenderedImage;
039    
040    import java.io.File;
041    import java.io.FileInputStream;
042    import java.io.FileOutputStream;
043    import java.io.InputStream;
044    
045    import java.util.ArrayList;
046    import java.util.Arrays;
047    import java.util.List;
048    import java.util.Set;
049    import java.util.Vector;
050    import java.util.concurrent.Future;
051    
052    import javax.imageio.ImageIO;
053    
054    import org.apache.commons.lang.time.StopWatch;
055    import org.apache.pdfbox.pdmodel.PDDocument;
056    import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
057    import org.apache.pdfbox.pdmodel.PDPage;
058    
059    /**
060     * @author Alexander Chow
061     * @author Mika Koivisto
062     * @author Juan González
063     * @author Sergio González
064     * @author Ivica Cardic
065     */
066    public class PDFProcessorImpl
067            extends DLPreviewableProcessor implements PDFProcessor {
068    
069            public void afterPropertiesSet() throws Exception {
070                    FileUtil.mkdirs(PREVIEW_TMP_PATH);
071                    FileUtil.mkdirs(THUMBNAIL_TMP_PATH);
072            }
073    
074            public void generateImages(
075                            FileVersion sourceFileVersion, FileVersion destinationFileVersion)
076                    throws Exception {
077    
078                    _generateImages(sourceFileVersion, destinationFileVersion);
079            }
080    
081            public InputStream getPreviewAsStream(FileVersion fileVersion, int index)
082                    throws Exception {
083    
084                    return doGetPreviewAsStream(fileVersion, index, PREVIEW_TYPE);
085            }
086    
087            public int getPreviewFileCount(FileVersion fileVersion) {
088                    try {
089                            return doGetPreviewFileCount(fileVersion);
090                    }
091                    catch (Exception e) {
092                            _log.error(e, e);
093                    }
094    
095                    return 0;
096            }
097    
098            public long getPreviewFileSize(FileVersion fileVersion, int index)
099                    throws Exception {
100    
101                    return doGetPreviewFileSize(fileVersion, index);
102            }
103    
104            public InputStream getThumbnailAsStream(FileVersion fileVersion, int index)
105                    throws Exception {
106    
107                    return doGetThumbnailAsStream(fileVersion, index);
108            }
109    
110            public long getThumbnailFileSize(FileVersion fileVersion, int index)
111                    throws Exception {
112    
113                    return doGetThumbnailFileSize(fileVersion, index);
114            }
115    
116            public boolean hasImages(FileVersion fileVersion) {
117                    boolean hasImages = false;
118    
119                    try {
120                            hasImages = _hasImages(fileVersion);
121    
122                            if (!hasImages && isSupported(fileVersion)) {
123                                    _queueGeneration(null, fileVersion);
124                            }
125                    }
126                    catch (Exception e) {
127                            _log.error(e, e);
128                    }
129    
130                    return hasImages;
131            }
132    
133            public boolean isDocumentSupported(FileVersion fileVersion) {
134                    return isSupported(fileVersion);
135            }
136    
137            public boolean isDocumentSupported(String mimeType) {
138                    return isSupported(mimeType);
139            }
140    
141            public boolean isSupported(String mimeType) {
142                    if (Validator.isNull(mimeType)) {
143                            return false;
144                    }
145    
146                    if (mimeType.equals(ContentTypes.APPLICATION_PDF) ||
147                            mimeType.equals(ContentTypes.APPLICATION_X_PDF)) {
148    
149                            return true;
150                    }
151    
152                    if (DocumentConversionUtil.isEnabled()) {
153                            Set<String> extensions = MimeTypesUtil.getExtensions(mimeType);
154    
155                            for (String extension : extensions) {
156                                    extension = extension.substring(1);
157    
158                                    String[] targetExtensions =
159                                            DocumentConversionUtil.getConversions(extension);
160    
161                                    if (Arrays.binarySearch(targetExtensions, "pdf") >= 0) {
162                                            return true;
163                                    }
164                            }
165                    }
166    
167                    return false;
168            }
169    
170            @Override
171            public void trigger(
172                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
173    
174                    super.trigger(sourceFileVersion, destinationFileVersion);
175    
176                    _queueGeneration(sourceFileVersion, destinationFileVersion);
177            }
178    
179            @Override
180            protected void copyPreviews(
181                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
182    
183                    if (!PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED) {
184                            return;
185                    }
186    
187                    try {
188                            if (hasPreview(sourceFileVersion) &&
189                                    !hasPreview(destinationFileVersion)) {
190    
191                                    int count = getPreviewFileCount(sourceFileVersion);
192    
193                                    for (int i = 0; i < count; i++) {
194                                            String previewFilePath = getPreviewFilePath(
195                                                    destinationFileVersion, i + 1);
196    
197                                            InputStream is = doGetPreviewAsStream(
198                                                    sourceFileVersion, i + 1, PREVIEW_TYPE);
199    
200                                            addFileToStore(
201                                                    destinationFileVersion.getCompanyId(), PREVIEW_PATH,
202                                                    previewFilePath, is);
203                                    }
204                            }
205                    }
206                    catch (Exception e) {
207                            _log.error(e, e);
208                    }
209            }
210    
211            @Override
212            protected void doExportGeneratedFiles(
213                            PortletDataContext portletDataContext, FileEntry fileEntry,
214                            Element fileEntryElement)
215                    throws Exception {
216    
217                    exportThumbnails(
218                            portletDataContext, fileEntry, fileEntryElement, "pdf");
219    
220                    exportPreviews(portletDataContext, fileEntry, fileEntryElement);
221            }
222    
223            @Override
224            protected void doImportGeneratedFiles(
225                            PortletDataContext portletDataContext, FileEntry fileEntry,
226                            FileEntry importedFileEntry, Element fileEntryElement)
227                    throws Exception {
228    
229                    importThumbnails(
230                            portletDataContext, fileEntry, importedFileEntry, fileEntryElement,
231                            "pdf");
232    
233                    importPreviews(
234                            portletDataContext, fileEntry, importedFileEntry, fileEntryElement);
235            }
236    
237            protected void exportPreviews(
238                            PortletDataContext portletDataContext, FileEntry fileEntry,
239                            Element fileEntryElement)
240                    throws Exception {
241    
242                    FileVersion fileVersion = fileEntry.getFileVersion();
243    
244                    if (!isSupported(fileVersion) || !_hasImages(fileVersion)) {
245                            return;
246                    }
247    
248                    if (!portletDataContext.isPerformDirectBinaryImport()) {
249                            int previewFileCount = getPreviewFileCount(fileVersion);
250    
251                            fileEntryElement.addAttribute(
252                                    "bin-path-pdf-preview-count", String.valueOf(previewFileCount));
253    
254                            for (int i = 0; i < previewFileCount; i++) {
255                                    exportPreview(
256                                            portletDataContext, fileEntry, fileEntryElement, "pdf",
257                                            PREVIEW_TYPE, i);
258                            }
259                    }
260            }
261    
262            @Override
263            protected List<Long> getFileVersionIds() {
264                    return _fileVersionIds;
265            }
266    
267            @Override
268            protected String getPreviewType(FileVersion fileVersion) {
269                    return PREVIEW_TYPE;
270            }
271    
272            @Override
273            protected String getThumbnailType(FileVersion fileVersion) {
274                    return THUMBNAIL_TYPE;
275            }
276    
277            protected boolean hasPreview(FileVersion fileVersion) throws Exception {
278                    return hasPreview(fileVersion, null);
279            }
280    
281            @Override
282            protected boolean hasPreview(FileVersion fileVersion, String type)
283                    throws Exception {
284    
285                    String previewFilePath = getPreviewFilePath(fileVersion, 1);
286    
287                    return DLStoreUtil.hasFile(
288                            fileVersion.getCompanyId(), REPOSITORY_ID, previewFilePath);
289            }
290    
291            protected void importPreviews(
292                            PortletDataContext portletDataContext, FileEntry fileEntry,
293                            FileEntry importedFileEntry, Element fileEntryElement)
294                    throws Exception {
295    
296                    int previewFileCount = GetterUtil.getInteger(
297                            fileEntryElement.attributeValue("bin-path-pdf-preview-count"));
298    
299                    for (int i = 0; i < previewFileCount; i++) {
300                            importPreview(
301                                    portletDataContext, fileEntry, importedFileEntry,
302                                    fileEntryElement, "pdf", PREVIEW_TYPE, i);
303                    }
304            }
305    
306            private void _generateImages(FileVersion fileVersion, File file)
307                    throws Exception {
308    
309                    if (GhostscriptUtil.isEnabled()) {
310                            if (!_ghostscriptInitialized) {
311                                    GhostscriptUtil.reset();
312    
313                                    _ghostscriptInitialized = true;
314                            }
315    
316                            _generateImagesGS(fileVersion, file);
317                    }
318                    else {
319                            _generateImagesPB(fileVersion, file);
320                    }
321            }
322    
323            private void _generateImages(
324                            FileVersion sourceFileVersion, FileVersion destinationFileVersion)
325                    throws Exception {
326    
327                    InputStream inputStream = null;
328    
329                    try {
330                            if (sourceFileVersion != null) {
331                                    copy(sourceFileVersion, destinationFileVersion);
332    
333                                    return;
334                            }
335    
336                            if (_hasImages(destinationFileVersion)) {
337                                    return;
338                            }
339    
340                            String extension = destinationFileVersion.getExtension();
341    
342                            if (extension.equals("pdf")) {
343                                    if (destinationFileVersion instanceof LiferayFileVersion) {
344                                            try {
345                                                    LiferayFileVersion liferayFileVersion =
346                                                            (LiferayFileVersion)destinationFileVersion;
347    
348                                                    File file = liferayFileVersion.getFile(false);
349    
350                                                    _generateImages(destinationFileVersion, file);
351    
352                                                    return;
353                                            }
354                                            catch (UnsupportedOperationException uoe) {
355                                            }
356                                    }
357    
358                                    inputStream = destinationFileVersion.getContentStream(false);
359    
360                                    _generateImages(destinationFileVersion, inputStream);
361                            }
362                            else if (DocumentConversionUtil.isEnabled()) {
363                                    inputStream = destinationFileVersion.getContentStream(false);
364    
365                                    String tempFileId = DLUtil.getTempFileId(
366                                            destinationFileVersion.getFileEntryId(),
367                                            destinationFileVersion.getVersion());
368    
369                                    File file = DocumentConversionUtil.convert(
370                                            tempFileId, inputStream, extension, "pdf");
371    
372                                    _generateImages(destinationFileVersion, file);
373                            }
374                    }
375                    catch (NoSuchFileEntryException nsfee) {
376                    }
377                    finally {
378                            StreamUtil.cleanUp(inputStream);
379    
380                            _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
381                    }
382            }
383    
384            private void _generateImages(
385                            FileVersion fileVersion, InputStream inputStream)
386                    throws Exception {
387    
388                    if (GhostscriptUtil.isEnabled()) {
389                            _generateImagesGS(fileVersion, inputStream);
390                    }
391                    else {
392                            _generateImagesPB(fileVersion, inputStream);
393                    }
394            }
395    
396            private void _generateImagesGS(FileVersion fileVersion, File file)
397                    throws Exception {
398    
399                    if (_isGeneratePreview(fileVersion)) {
400                            StopWatch stopWatch = null;
401    
402                            if (_log.isInfoEnabled()) {
403                                    stopWatch = new StopWatch();
404    
405                                    stopWatch.start();
406                            }
407    
408                            _generateImagesGS(fileVersion, file, false);
409    
410                            if (_log.isInfoEnabled()) {
411                                    int previewFileCount = getPreviewFileCount(fileVersion);
412    
413                                    _log.info(
414                                            "Ghostscript generated " + previewFileCount +
415                                                    " preview pages for " + fileVersion.getTitle() +
416                                                            " in " + stopWatch);
417                            }
418                    }
419    
420                    if (_isGenerateThumbnail(fileVersion)) {
421                            StopWatch stopWatch = null;
422    
423                            if (_log.isInfoEnabled()) {
424                                    stopWatch = new StopWatch();
425    
426                                    stopWatch.start();
427                            }
428    
429                            _generateImagesGS(fileVersion, file, true);
430    
431                            if (_log.isInfoEnabled()) {
432                                    _log.info(
433                                            "Ghostscript generated a thumbnail for " +
434                                                    fileVersion.getTitle() + " in " + stopWatch);
435                            }
436                    }
437            }
438    
439            private void _generateImagesGS(
440                            FileVersion fileVersion, File file, boolean thumbnail)
441                    throws Exception {
442    
443                    // Generate images
444    
445                    String tempFileId = DLUtil.getTempFileId(
446                            fileVersion.getFileEntryId(), fileVersion.getVersion());
447    
448                    List<String> arguments = new ArrayList<String>();
449    
450                    arguments.add("-sDEVICE=png16m");
451    
452                    if (thumbnail) {
453                            arguments.add(
454                                    "-sOutputFile=" + getThumbnailTempFilePath(tempFileId));
455                            arguments.add("-dFirstPage=1");
456                            arguments.add("-dLastPage=1");
457                    }
458                    else {
459                            arguments.add(
460                                    "-sOutputFile=" + getPreviewTempFilePath(tempFileId, -1));
461                    }
462    
463                    arguments.add("-dPDFFitPage");
464                    arguments.add("-dTextAlphaBits=4");
465                    arguments.add("-dGraphicsAlphaBits=4");
466                    arguments.add("-r" + PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI);
467    
468                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH != 0) {
469                            arguments.add(
470                                    "-dDEVICEWIDTH" +
471                                            PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH);
472                    }
473    
474                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT != 0) {
475                            arguments.add(
476                                    "-dDEVICEHEIGHT" +
477                                            PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT);
478                    }
479    
480                    arguments.add(file.getPath());
481    
482                    Future<?> future = GhostscriptUtil.execute(arguments);
483    
484                    String processIdentity = String.valueOf(fileVersion.getFileVersionId());
485    
486                    futures.put(processIdentity, future);
487    
488                    future.get();
489    
490                    // Store images
491    
492                    if (thumbnail) {
493                            File thumbnailTempFile = getThumbnailTempFile(tempFileId);
494    
495                            try {
496                                    storeThumbnailImages(fileVersion, thumbnailTempFile);
497                            }
498                            finally {
499                                    FileUtil.delete(thumbnailTempFile);
500                            }
501                    }
502                    else {
503                            int total = getPreviewTempFileCount(fileVersion);
504    
505                            for (int i = 0; i < total; i++) {
506                                    File previewTempFile = getPreviewTempFile(tempFileId, i + 2);
507    
508                                    try {
509                                            addFileToStore(
510                                                    fileVersion.getCompanyId(), PREVIEW_PATH,
511                                                    getPreviewFilePath(fileVersion, i + 1),
512                                                    previewTempFile);
513                                    }
514                                    finally {
515                                            FileUtil.delete(previewTempFile);
516                                    }
517                            }
518                    }
519            }
520    
521            private void _generateImagesGS(
522                            FileVersion fileVersion, InputStream inputStream)
523                    throws Exception {
524    
525                    File file = null;
526    
527                    try {
528                            file = FileUtil.createTempFile(inputStream);
529    
530                            _generateImagesGS(fileVersion, file);
531                    }
532                    finally {
533                            FileUtil.delete(file);
534                    }
535            }
536    
537            private void _generateImagesPB(FileVersion fileVersion, File file)
538                    throws Exception {
539    
540                    _generateImagesPB(fileVersion, new FileInputStream(file));
541            }
542    
543            private void _generateImagesPB(
544                            FileVersion fileVersion, InputStream inputStream)
545                    throws Exception {
546    
547                    boolean generatePreview = _isGeneratePreview(fileVersion);
548                    boolean generateThumbnail = _isGenerateThumbnail(fileVersion);
549    
550                    PDDocument pdDocument = null;
551    
552                    try {
553                            pdDocument = PDDocument.load(inputStream);
554    
555                            PDDocumentCatalog pdDocumentCatalog =
556                                    pdDocument.getDocumentCatalog();
557    
558                            List<PDPage> pdPages = pdDocumentCatalog.getAllPages();
559    
560                            for (int i = 0; i < pdPages.size(); i++) {
561                                    PDPage pdPage = pdPages.get(i);
562    
563                                    if (generateThumbnail && (i == 0)) {
564                                            _generateImagesPB(fileVersion, pdPage, i);
565    
566                                            if (_log.isInfoEnabled()) {
567                                                    _log.info(
568                                                            "PDFBox generated a thumbnail for " +
569                                                                    fileVersion.getFileVersionId());
570                                            }
571                                    }
572    
573                                    if (!generatePreview) {
574                                            break;
575                                    }
576    
577                                    _generateImagesPB(fileVersion, pdPage, i + 1);
578                            }
579    
580                            if (_log.isInfoEnabled() && generatePreview) {
581                                    _log.info(
582                                            "PDFBox generated " +
583                                                    getPreviewFileCount(fileVersion) +
584                                                            " preview pages for " +
585                                                                    fileVersion.getFileVersionId());
586                            }
587                    }
588                    finally {
589                            if (pdDocument != null) {
590                                    pdDocument.close();
591                            }
592                    }
593            }
594    
595            private void _generateImagesPB(
596                            FileVersion fileVersion, PDPage pdPage, int index)
597                    throws Exception {
598    
599                    // Generate images
600    
601                    RenderedImage renderedImage = pdPage.convertToImage(
602                            BufferedImage.TYPE_INT_RGB,
603                            PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI);
604    
605                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT != 0) {
606                            renderedImage = ImageToolUtil.scale(
607                                    renderedImage,
608                                    PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH,
609                                    PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT);
610                    }
611                    else {
612                            renderedImage = ImageToolUtil.scale(
613                                    renderedImage,
614                                    PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH);
615                    }
616    
617                    // Store images
618    
619                    if (index == 0) {
620                            storeThumbnailImages(fileVersion, renderedImage);
621                    }
622                    else {
623                            File tempFile = null;
624    
625                            try {
626                                    String tempFileId = DLUtil.getTempFileId(
627                                            fileVersion.getFileEntryId(), fileVersion.getVersion());
628    
629                                    tempFile = getPreviewTempFile(tempFileId, index);
630    
631                                    tempFile.createNewFile();
632    
633                                    ImageIO.write(
634                                            renderedImage, PREVIEW_TYPE,
635                                            new FileOutputStream(tempFile));
636    
637                                    addFileToStore(
638                                            fileVersion.getCompanyId(), PREVIEW_PATH,
639                                            getPreviewFilePath(fileVersion, index), tempFile);
640                            }
641                            finally {
642                                    FileUtil.delete(tempFile);
643                            }
644                    }
645            }
646    
647            private boolean _hasImages(FileVersion fileVersion) throws Exception {
648                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED) {
649                            if (!hasPreview(fileVersion)) {
650                                    return false;
651                            }
652                    }
653    
654                    return hasThumbnails(fileVersion);
655            }
656    
657            private boolean _isGeneratePreview(FileVersion fileVersion)
658                    throws Exception {
659    
660                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED &&
661                            !hasPreview(fileVersion)) {
662    
663                            return true;
664                    }
665                    else {
666                            return false;
667                    }
668            }
669    
670            private boolean _isGenerateThumbnail(FileVersion fileVersion)
671                    throws Exception {
672    
673                    if (PropsValues.DL_FILE_ENTRY_THUMBNAIL_ENABLED &&
674                            !hasThumbnail(fileVersion, THUMBNAIL_INDEX_DEFAULT)) {
675    
676                            return true;
677                    }
678                    else {
679                            return false;
680                    }
681            }
682    
683            private void _queueGeneration(
684                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
685    
686                    if (_fileVersionIds.contains(
687                                    destinationFileVersion.getFileVersionId())) {
688    
689                            return;
690                    }
691    
692                    boolean generateImages = false;
693    
694                    String extension = destinationFileVersion.getExtension();
695    
696                    if (extension.equals("pdf")) {
697                            generateImages = true;
698                    }
699                    else if (DocumentConversionUtil.isEnabled()) {
700                            String[] conversions = DocumentConversionUtil.getConversions(
701                                    extension);
702    
703                            for (String conversion : conversions) {
704                                    if (conversion.equals("pdf")) {
705                                            generateImages = true;
706    
707                                            break;
708                                    }
709                            }
710                    }
711    
712                    if (generateImages) {
713                            _fileVersionIds.add(destinationFileVersion.getFileVersionId());
714    
715                            sendGenerationMessage(
716                                    DestinationNames.DOCUMENT_LIBRARY_PDF_PROCESSOR,
717                                    PropsValues.DL_FILE_ENTRY_PROCESSORS_TRIGGER_SYNCHRONOUSLY,
718                                    sourceFileVersion, destinationFileVersion);
719                    }
720            }
721    
722            private static Log _log = LogFactoryUtil.getLog(PDFProcessorImpl.class);
723    
724            private List<Long> _fileVersionIds = new Vector<Long>();
725            private boolean _ghostscriptInitialized = false;
726    
727    }