001
014
015 package com.liferay.portlet.documentlibrary.util;
016
017 import com.liferay.portal.kernel.configuration.Filter;
018 import com.liferay.portal.kernel.image.ImageToolUtil;
019 import com.liferay.portal.kernel.log.Log;
020 import com.liferay.portal.kernel.log.LogFactoryUtil;
021 import com.liferay.portal.kernel.messaging.DestinationNames;
022 import com.liferay.portal.kernel.messaging.MessageBusException;
023 import com.liferay.portal.kernel.messaging.MessageBusUtil;
024 import com.liferay.portal.kernel.process.ClassPathUtil;
025 import com.liferay.portal.kernel.process.ProcessCallable;
026 import com.liferay.portal.kernel.process.ProcessException;
027 import com.liferay.portal.kernel.process.ProcessExecutor;
028 import com.liferay.portal.kernel.repository.model.FileVersion;
029 import com.liferay.portal.kernel.util.ContentTypes;
030 import com.liferay.portal.kernel.util.FileUtil;
031 import com.liferay.portal.kernel.util.InstancePool;
032 import com.liferay.portal.kernel.util.MimeTypesUtil;
033 import com.liferay.portal.kernel.util.OSDetector;
034 import com.liferay.portal.kernel.util.PropsKeys;
035 import com.liferay.portal.kernel.util.StringBundler;
036 import com.liferay.portal.kernel.util.StringPool;
037 import com.liferay.portal.kernel.util.Validator;
038 import com.liferay.portal.repository.liferayrepository.model.LiferayFileVersion;
039 import com.liferay.portal.util.PrefsPropsUtil;
040 import com.liferay.portal.util.PropsUtil;
041 import com.liferay.portal.util.PropsValues;
042 import com.liferay.portlet.documentlibrary.NoSuchFileEntryException;
043 import com.liferay.portlet.documentlibrary.store.DLStoreUtil;
044
045 import java.awt.image.BufferedImage;
046 import java.awt.image.RenderedImage;
047
048 import java.io.File;
049 import java.io.FileInputStream;
050 import java.io.FileOutputStream;
051 import java.io.InputStream;
052
053 import java.util.Arrays;
054 import java.util.LinkedList;
055 import java.util.List;
056 import java.util.Set;
057 import java.util.Vector;
058
059 import javax.imageio.ImageIO;
060
061 import javax.portlet.PortletPreferences;
062
063 import org.apache.commons.lang.time.StopWatch;
064 import org.apache.pdfbox.pdmodel.PDDocument;
065 import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
066 import org.apache.pdfbox.pdmodel.PDPage;
067
068 import org.im4java.core.ConvertCmd;
069 import org.im4java.core.IMOperation;
070 import org.im4java.process.ProcessStarter;
071
072
078 public class PDFProcessorImpl
079 extends DefaultPreviewableProcessor implements PDFProcessor {
080
081 public static PDFProcessorImpl getInstance() {
082 return _instance;
083 }
084
085 public void generateImages(FileVersion fileVersion) throws Exception {
086 Initializer._initializedInstance._generateImages(fileVersion);
087 }
088
089 public String getGlobalSearchPath() throws Exception {
090 PortletPreferences preferences = PrefsPropsUtil.getPreferences();
091
092 String globalSearchPath = preferences.getValue(
093 PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, null);
094
095 if (Validator.isNotNull(globalSearchPath)) {
096 return globalSearchPath;
097 }
098
099 String filterName = null;
100
101 if (OSDetector.isApple()) {
102 filterName = "apple";
103 }
104 else if (OSDetector.isWindows()) {
105 filterName = "windows";
106 }
107 else {
108 filterName = "unix";
109 }
110
111 return PropsUtil.get(
112 PropsKeys.IMAGEMAGICK_GLOBAL_SEARCH_PATH, new Filter(filterName));
113 }
114
115 public InputStream getPreviewAsStream(FileVersion fileVersion, int index)
116 throws Exception {
117
118 return Initializer._initializedInstance.doGetPreviewAsStream(
119 fileVersion, index);
120 }
121
122 public int getPreviewFileCount(FileVersion fileVersion) {
123 try {
124 return Initializer._initializedInstance.doGetPreviewFileCount(
125 fileVersion);
126 }
127 catch (Exception e) {
128 _log.error(e, e);
129 }
130
131 return 0;
132 }
133
134 public long getPreviewFileSize(FileVersion fileVersion, int index)
135 throws Exception {
136
137 return Initializer._initializedInstance.doGetPreviewFileSize(
138 fileVersion, index);
139 }
140
141 public InputStream getThumbnailAsStream(
142 FileVersion fileVersion, int thumbnailIndex)
143 throws Exception {
144
145 return doGetThumbnailAsStream(fileVersion, thumbnailIndex);
146 }
147
148 public long getThumbnailFileSize(
149 FileVersion fileVersion, int thumbnailIndex)
150 throws Exception {
151
152 return doGetThumbnailFileSize(fileVersion, thumbnailIndex);
153 }
154
155 public boolean hasImages(FileVersion fileVersion) {
156 boolean hasImages = false;
157
158 try {
159 hasImages = _hasImages(fileVersion);
160
161 if (!hasImages && isSupported(fileVersion)) {
162 Initializer._initializedInstance._queueGeneration(fileVersion);
163 }
164 }
165 catch (Exception e) {
166 _log.error(e, e);
167 }
168
169 return hasImages;
170 }
171
172 public boolean isDocumentSupported(FileVersion fileVersion) {
173 return Initializer._initializedInstance.isSupported(fileVersion);
174 }
175
176 public boolean isDocumentSupported(String mimeType) {
177 return Initializer._initializedInstance.isSupported(mimeType);
178 }
179
180 public boolean isImageMagickEnabled() throws Exception {
181 if (PrefsPropsUtil.getBoolean(PropsKeys.IMAGEMAGICK_ENABLED)) {
182 return true;
183 }
184
185 if (!_warned) {
186 StringBundler sb = new StringBundler(5);
187
188 sb.append("Liferay is not configured to use ImageMagick for ");
189 sb.append("generating Document Library previews and will default ");
190 sb.append("to PDFBox. For better quality previews, install ");
191 sb.append("ImageMagick and enable it in portal-ext.properties.");
192
193 _log.warn(sb.toString());
194
195 _warned = true;
196 }
197
198 return false;
199 }
200
201 public boolean isSupported(String mimeType) {
202 if (Validator.isNull(mimeType)) {
203 return false;
204 }
205
206 if (mimeType.equals(ContentTypes.APPLICATION_PDF) ||
207 mimeType.equals(ContentTypes.APPLICATION_X_PDF)) {
208
209 return true;
210 }
211
212 if (DocumentConversionUtil.isEnabled()) {
213 Set<String> extensions = MimeTypesUtil.getExtensions(mimeType);
214
215 for (String extension : extensions) {
216 extension = extension.substring(1);
217
218 String[] targetExtensions =
219 DocumentConversionUtil.getConversions(extension);
220
221 if (Arrays.binarySearch(targetExtensions, "pdf") >= 0) {
222 return true;
223 }
224 }
225 }
226
227 return false;
228 }
229
230 public void reset() throws Exception {
231 if (isImageMagickEnabled()) {
232 _globalSearchPath = getGlobalSearchPath();
233
234 ProcessStarter.setGlobalSearchPath(_globalSearchPath);
235
236 _convertCmd = new ConvertCmd();
237 }
238 else {
239 _convertCmd = null;
240 }
241 }
242
243 public void trigger(FileVersion fileVersion) {
244 Initializer._initializedInstance._queueGeneration(fileVersion);
245 }
246
247 @Override
248 protected String getPreviewType(FileVersion fileVersion) {
249 return PREVIEW_TYPE;
250 }
251
252 @Override
253 protected String getThumbnailType(FileVersion fileVersion) {
254 return THUMBNAIL_TYPE;
255 }
256
257 protected void initialize() {
258 try {
259 FileUtil.mkdirs(PREVIEW_TMP_PATH);
260 FileUtil.mkdirs(THUMBNAIL_TMP_PATH);
261
262 reset();
263 }
264 catch (Exception e) {
265 _log.warn(e, e);
266 }
267 }
268
269 private PDFProcessorImpl() {
270 }
271
272 private void _generateImages(FileVersion fileVersion)
273 throws Exception {
274
275 try {
276 if (_hasImages(fileVersion)) {
277 return;
278 }
279
280 String extension = fileVersion.getExtension();
281
282 if (extension.equals("pdf")) {
283 if (fileVersion instanceof LiferayFileVersion) {
284 try {
285 LiferayFileVersion liferayFileVersion =
286 (LiferayFileVersion)fileVersion;
287
288 File file = liferayFileVersion.getFile(false);
289
290 _generateImages(fileVersion, file);
291
292 return;
293 }
294 catch (UnsupportedOperationException uoe) {
295 }
296 }
297
298 InputStream inputStream = fileVersion.getContentStream(false);
299
300 _generateImages(fileVersion, inputStream);
301 }
302 else if (DocumentConversionUtil.isEnabled()) {
303 InputStream inputStream = fileVersion.getContentStream(false);
304
305 String tempFileId = DLUtil.getTempFileId(
306 fileVersion.getFileEntryId(), fileVersion.getVersion());
307
308 File file = DocumentConversionUtil.convert(
309 tempFileId, inputStream, extension, "pdf");
310
311 _generateImages(fileVersion, file);
312 }
313 }
314 catch (NoSuchFileEntryException nsfee) {
315 }
316 finally {
317 _fileVersionIds.remove(fileVersion.getFileVersionId());
318 }
319 }
320
321 private void _generateImages(FileVersion fileVersion, File file)
322 throws Exception {
323
324 if (isImageMagickEnabled()) {
325 _generateImagesIM(fileVersion, file);
326 }
327 else {
328 _generateImagesPB(fileVersion, file);
329 }
330 }
331
332 private void _generateImages(
333 FileVersion fileVersion, InputStream inputStream)
334 throws Exception {
335
336 if (isImageMagickEnabled()) {
337 _generateImagesIM(fileVersion, inputStream);
338 }
339 else {
340 _generateImagesPB(fileVersion, inputStream);
341 }
342 }
343
344 private void _generateImagesIM(FileVersion fileVersion, File file)
345 throws Exception {
346
347 if (_isGeneratePreview(fileVersion)) {
348 StopWatch stopWatch = null;
349
350 if (_log.isInfoEnabled()) {
351 stopWatch = new StopWatch();
352
353 stopWatch.start();
354 }
355
356 _generateImagesIM(fileVersion, file, false);
357
358 if (_log.isInfoEnabled()) {
359 int previewFileCount = getPreviewFileCount(fileVersion);
360
361 _log.info(
362 "ImageMagick generated " + previewFileCount +
363 " preview pages for " + fileVersion.getTitle() +
364 " in " + stopWatch);
365 }
366 }
367
368 if (_isGenerateThumbnail(fileVersion)) {
369 StopWatch stopWatch = null;
370
371 if (_log.isInfoEnabled()) {
372 stopWatch = new StopWatch();
373
374 stopWatch.start();
375 }
376
377 _generateImagesIM(fileVersion, file, true);
378
379 if (_log.isInfoEnabled()) {
380 _log.info(
381 "ImageMagick generated a thumbnail for " +
382 fileVersion.getTitle() + " in " + stopWatch);
383 }
384 }
385 }
386
387 private void _generateImagesIM(
388 FileVersion fileVersion, File file, boolean thumbnail)
389 throws Exception {
390
391
392
393 String tempFileId = DLUtil.getTempFileId(
394 fileVersion.getFileEntryId(), fileVersion.getVersion());
395
396 IMOperation imOperation = new IMOperation();
397
398 imOperation.alpha("off");
399
400 imOperation.density(
401 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI,
402 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI);
403
404 if (PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT != 0) {
405 imOperation.adaptiveResize(
406 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH,
407 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT);
408 }
409 else {
410 imOperation.adaptiveResize(
411 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH);
412 }
413
414 imOperation.depth(PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DEPTH);
415
416 if (thumbnail) {
417 imOperation.addImage(file.getPath() + "[0]");
418 imOperation.addImage(getThumbnailTempFilePath(tempFileId));
419 }
420 else {
421 imOperation.addImage(file.getPath());
422 imOperation.addImage(getPreviewTempFilePath(tempFileId, -1));
423 }
424
425 if (_log.isInfoEnabled()) {
426 _log.info("Excecuting command 'convert " + imOperation + "'");
427 }
428
429 if (PropsValues.DL_FILE_ENTRY_PREVIEW_FORK_PROCESS_ENABLED) {
430 ProcessCallable<String> processCallable =
431 new ImageMagickProcessCallable(
432 _globalSearchPath, imOperation.getCmdArgs());
433
434 ProcessExecutor.execute(
435 processCallable, ClassPathUtil.getPortalClassPath());
436 }
437 else {
438 _convertCmd.run(imOperation);
439 }
440
441
442
443 if (thumbnail) {
444 File thumbnailTempFile = getThumbnailTempFile(tempFileId);
445
446 try {
447 storeThumbnailImages(fileVersion, thumbnailTempFile);
448 }
449 finally {
450 FileUtil.delete(thumbnailTempFile);
451 }
452 }
453 else {
454
455
456
457
458 File singlePagePreviewFile = getPreviewTempFile(tempFileId, -1);
459
460 if (singlePagePreviewFile.exists()) {
461 singlePagePreviewFile.renameTo(
462 getPreviewTempFile(tempFileId, 1));
463 }
464
465 int total = getPreviewTempFileCount(fileVersion);
466
467 for (int i = 0; i < total; i++) {
468 File previewTempFile = getPreviewTempFile(tempFileId, i + 1);
469
470 try {
471 addFileToStore(
472 fileVersion.getCompanyId(), PREVIEW_PATH,
473 getPreviewFilePath(fileVersion, i + 1),
474 previewTempFile);
475 }
476 finally {
477 FileUtil.delete(previewTempFile);
478 }
479 }
480 }
481 }
482
483 private void _generateImagesIM(
484 FileVersion fileVersion, InputStream inputStream)
485 throws Exception {
486
487 File file = FileUtil.createTempFile(inputStream);
488
489 try {
490 _generateImagesIM(fileVersion, file);
491 }
492 finally {
493 FileUtil.delete(file);
494 }
495 }
496
497 private void _generateImagesPB(FileVersion fileVersion, File file)
498 throws Exception {
499
500 _generateImagesPB(fileVersion, new FileInputStream(file));
501 }
502
503 private void _generateImagesPB(
504 FileVersion fileVersion, InputStream inputStream)
505 throws Exception {
506
507 boolean generatePreview = _isGeneratePreview(fileVersion);
508 boolean generateThumbnail = _isGenerateThumbnail(fileVersion);
509
510 PDDocument pdDocument = null;
511
512 try {
513 pdDocument = PDDocument.load(inputStream);
514
515 PDDocumentCatalog pdDocumentCatalog =
516 pdDocument.getDocumentCatalog();
517
518 List<PDPage> pdPages = pdDocumentCatalog.getAllPages();
519
520 for (int i = 0; i < pdPages.size(); i++) {
521 PDPage pdPage = pdPages.get(i);
522
523 if (generateThumbnail && (i == 0)) {
524 _generateImagesPB(fileVersion, pdPage, i);
525
526 if (_log.isInfoEnabled()) {
527 _log.info(
528 "PDFBox generated a thumbnail for " +
529 fileVersion.getFileVersionId());
530 }
531 }
532
533 if (!generatePreview) {
534 break;
535 }
536
537 _generateImagesPB(fileVersion, pdPage, i + 1);
538 }
539
540 if (_log.isInfoEnabled() && generatePreview) {
541 _log.info(
542 "PDFBox generated " +
543 getPreviewFileCount(fileVersion) +
544 " preview pages for " +
545 fileVersion.getFileVersionId());
546 }
547 }
548 finally {
549 if (pdDocument != null) {
550 pdDocument.close();
551 }
552 }
553 }
554
555 private void _generateImagesPB(
556 FileVersion fileVersion, PDPage pdPage, int index)
557 throws Exception {
558
559
560
561 RenderedImage renderedImage = pdPage.convertToImage(
562 BufferedImage.TYPE_INT_RGB,
563 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_DPI);
564
565 if (PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT != 0) {
566 renderedImage = ImageToolUtil.scale(
567 renderedImage,
568 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH,
569 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_HEIGHT);
570 }
571 else {
572 renderedImage = ImageToolUtil.scale(
573 renderedImage,
574 PropsValues.DL_FILE_ENTRY_PREVIEW_DOCUMENT_MAX_WIDTH);
575 }
576
577
578
579 if (index == 0) {
580 storeThumbnailImages(fileVersion, renderedImage);
581 }
582 else {
583 File tempFile = null;
584
585 try {
586 String tempFileId = DLUtil.getTempFileId(
587 fileVersion.getFileEntryId(), fileVersion.getVersion());
588
589 tempFile = getPreviewTempFile(tempFileId, index);
590
591 tempFile.createNewFile();
592
593 ImageIO.write(
594 renderedImage, PREVIEW_TYPE,
595 new FileOutputStream(tempFile));
596
597 addFileToStore(
598 fileVersion.getCompanyId(), PREVIEW_PATH,
599 getPreviewFilePath(fileVersion, index), tempFile);
600 }
601 finally {
602 FileUtil.delete(tempFile);
603 }
604 }
605 }
606
607 private boolean _hasImages(FileVersion fileVersion) throws Exception {
608 if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED) {
609 if (!DLStoreUtil.hasFile(
610 fileVersion.getCompanyId(), REPOSITORY_ID,
611 getPreviewFilePath(fileVersion, 1))) {
612
613 return false;
614 }
615 }
616
617 if (PropsValues.DL_FILE_ENTRY_THUMBNAIL_ENABLED) {
618 if (!hasThumbnail(fileVersion, THUMBNAIL_INDEX_DEFAULT)) {
619 return false;
620 }
621 }
622
623 try {
624 if (isCustomThumbnailsEnabled(1)) {
625 if (!hasThumbnail(fileVersion, THUMBNAIL_INDEX_CUSTOM_1)) {
626 return false;
627 }
628 }
629
630 if (isCustomThumbnailsEnabled(2)) {
631 if (!hasThumbnail(fileVersion, THUMBNAIL_INDEX_CUSTOM_2)) {
632 return false;
633 }
634 }
635 }
636 catch (Exception e) {
637 _log.error(e, e);
638 }
639
640 return true;
641 }
642
643 private boolean _isGeneratePreview(FileVersion fileVersion)
644 throws Exception {
645
646 String previewFilePath = getPreviewFilePath(fileVersion, 1);
647
648 if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED &&
649 !DLStoreUtil.hasFile(
650 fileVersion.getCompanyId(), REPOSITORY_ID, previewFilePath)) {
651
652 return true;
653 }
654 else {
655 return false;
656 }
657 }
658
659 private boolean _isGenerateThumbnail(FileVersion fileVersion)
660 throws Exception {
661
662 String thumbnailFilePath = getThumbnailFilePath(
663 fileVersion, THUMBNAIL_INDEX_DEFAULT);
664
665 if (PropsValues.DL_FILE_ENTRY_THUMBNAIL_ENABLED &&
666 !DLStoreUtil.hasFile(
667 fileVersion.getCompanyId(), REPOSITORY_ID, thumbnailFilePath)) {
668
669 return true;
670 }
671 else {
672 return false;
673 }
674 }
675
676 private void _queueGeneration(FileVersion fileVersion) {
677 if (_fileVersionIds.contains(fileVersion.getFileVersionId())) {
678 return;
679 }
680
681 boolean generateImages = false;
682
683 String extension = fileVersion.getExtension();
684
685 if (extension.equals("pdf")) {
686 generateImages = true;
687 }
688 else if (DocumentConversionUtil.isEnabled()) {
689 String[] conversions = DocumentConversionUtil.getConversions(
690 extension);
691
692 for (String conversion : conversions) {
693 if (conversion.equals("pdf")) {
694 generateImages = true;
695
696 break;
697 }
698 }
699 }
700
701 if (generateImages) {
702 _fileVersionIds.add(fileVersion.getFileVersionId());
703
704 if (PropsValues.DL_FILE_ENTRY_PROCESSORS_TRIGGER_SYNCHRONOUSLY) {
705 try {
706 MessageBusUtil.sendSynchronousMessage(
707 DestinationNames.DOCUMENT_LIBRARY_PDF_PROCESSOR,
708 fileVersion);
709 }
710 catch (MessageBusException mbe) {
711 if (_log.isWarnEnabled()) {
712 _log.warn(mbe, mbe);
713 }
714 }
715 }
716 else {
717 MessageBusUtil.sendMessage(
718 DestinationNames.DOCUMENT_LIBRARY_PDF_PROCESSOR,
719 fileVersion);
720 }
721 }
722 }
723
724 private static Log _log = LogFactoryUtil.getLog(PDFProcessorImpl.class);
725
726 private static PDFProcessorImpl _instance = new PDFProcessorImpl();
727
728 static {
729 InstancePool.put(PDFProcessorImpl.class.getName(), _instance);
730 }
731
732 private ConvertCmd _convertCmd;
733 private List<Long> _fileVersionIds = new Vector<Long>();
734 private String _globalSearchPath;
735 private boolean _warned;
736
737 private static class ImageMagickProcessCallable
738 implements ProcessCallable<String> {
739
740 public ImageMagickProcessCallable(
741 String globalSearchPath, LinkedList<String> commandArguments) {
742
743 _globalSearchPath = globalSearchPath;
744 _commandArguments = commandArguments;
745 }
746
747 public String call() throws ProcessException {
748 try {
749 LiferayConvertCmd.run(_globalSearchPath, _commandArguments);
750 }
751 catch (Exception e) {
752 throw new ProcessException(e);
753 }
754
755 return StringPool.BLANK;
756 }
757
758 private LinkedList<String> _commandArguments;
759 private String _globalSearchPath;
760
761 }
762
763 private static class Initializer {
764
765 private static PDFProcessorImpl _initializedInstance;
766
767 static {
768 _instance.initialize();
769
770 _initializedInstance = _instance;
771 }
772
773 }
774
775 }