001
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
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
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
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
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
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 }