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