001    /**
002     * Copyright (c) 2000-present 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.exception.PortalException;
018    import com.liferay.portal.kernel.image.ImageBag;
019    import com.liferay.portal.kernel.image.ImageTool;
020    import com.liferay.portal.kernel.image.ImageToolUtil;
021    import com.liferay.portal.kernel.log.Log;
022    import com.liferay.portal.kernel.log.LogFactoryUtil;
023    import com.liferay.portal.kernel.messaging.DestinationNames;
024    import com.liferay.portal.kernel.repository.model.FileEntry;
025    import com.liferay.portal.kernel.repository.model.FileVersion;
026    import com.liferay.portal.kernel.util.ContentTypes;
027    import com.liferay.portal.kernel.util.FileUtil;
028    import com.liferay.portal.kernel.util.SetUtil;
029    import com.liferay.portal.kernel.util.StreamUtil;
030    import com.liferay.portal.kernel.util.StringBundler;
031    import com.liferay.portal.kernel.util.StringPool;
032    import com.liferay.portal.kernel.util.Validator;
033    import com.liferay.portal.kernel.xml.Element;
034    import com.liferay.portal.util.PropsValues;
035    import com.liferay.portlet.documentlibrary.exception.NoSuchFileEntryException;
036    import com.liferay.portlet.documentlibrary.model.DLProcessorConstants;
037    import com.liferay.portlet.documentlibrary.store.DLStoreUtil;
038    import com.liferay.portlet.exportimport.lar.PortletDataContext;
039    
040    import java.awt.image.ColorModel;
041    import java.awt.image.RenderedImage;
042    
043    import java.io.File;
044    import java.io.FileOutputStream;
045    import java.io.InputStream;
046    
047    import java.util.List;
048    import java.util.Set;
049    import java.util.Vector;
050    import java.util.concurrent.Future;
051    
052    /**
053     * @author Sergio Gonz??lez
054     * @author Alexander Chow
055     * @author Ivica Cardic
056     */
057    public class ImageProcessorImpl
058            extends DLPreviewableProcessor implements ImageProcessor {
059    
060            @Override
061            public void afterPropertiesSet() {
062            }
063    
064            @Override
065            public void cleanUp(FileEntry fileEntry) {
066                    deleteFiles(fileEntry, null);
067            }
068    
069            @Override
070            public void cleanUp(FileVersion fileVersion) {
071                    String type = getThumbnailType(fileVersion);
072    
073                    deleteFiles(fileVersion, type);
074            }
075    
076            @Override
077            public void generateImages(
078                            FileVersion sourceFileVersion, FileVersion destinationFileVersion)
079                    throws Exception {
080    
081                    _generateImages(sourceFileVersion, destinationFileVersion);
082            }
083    
084            @Override
085            public Set<String> getImageMimeTypes() {
086                    return _imageMimeTypes;
087            }
088    
089            @Override
090            public InputStream getPreviewAsStream(FileVersion fileVersion)
091                    throws Exception {
092    
093                    if (_previewGenerationRequired(fileVersion)) {
094                            String type = getPreviewType(fileVersion);
095    
096                            return doGetPreviewAsStream(fileVersion, type);
097                    }
098    
099                    return fileVersion.getContentStream(false);
100            }
101    
102            @Override
103            public long getPreviewFileSize(FileVersion fileVersion) throws Exception {
104                    if (_previewGenerationRequired(fileVersion)) {
105                            String type = getPreviewType(fileVersion);
106    
107                            return doGetPreviewFileSize(fileVersion, type);
108                    }
109    
110                    return fileVersion.getSize();
111            }
112    
113            @Override
114            public String getPreviewType(FileVersion fileVersion) {
115                    return _getType(fileVersion);
116            }
117    
118            @Override
119            public InputStream getThumbnailAsStream(FileVersion fileVersion, int index)
120                    throws Exception {
121    
122                    return doGetThumbnailAsStream(fileVersion, index);
123            }
124    
125            @Override
126            public long getThumbnailFileSize(FileVersion fileVersion, int index)
127                    throws Exception {
128    
129                    return doGetThumbnailFileSize(fileVersion, index);
130            }
131    
132            @Override
133            public String getThumbnailType(FileVersion fileVersion) {
134                    return _getType(fileVersion);
135            }
136    
137            @Override
138            public String getType() {
139                    return DLProcessorConstants.IMAGE_PROCESSOR;
140            }
141    
142            @Override
143            public boolean hasImages(FileVersion fileVersion) {
144                    if (!PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED &&
145                            !PropsValues.DL_FILE_ENTRY_THUMBNAIL_ENABLED) {
146    
147                            return false;
148                    }
149    
150                    if (fileVersion.getSize() == 0) {
151                            return false;
152                    }
153    
154                    boolean hasImages = false;
155    
156                    try {
157                            if (_hasPreview(fileVersion) && hasThumbnails(fileVersion)) {
158                                    hasImages = true;
159                            }
160    
161                            if (!hasImages && isSupported(fileVersion)) {
162                                    _queueGeneration(null, fileVersion);
163                            }
164                    }
165                    catch (Exception e) {
166                            _log.error(e, e);
167                    }
168    
169                    return hasImages;
170            }
171    
172            @Override
173            public boolean isImageSupported(FileVersion fileVersion) {
174                    return isSupported(fileVersion);
175            }
176    
177            @Override
178            public boolean isImageSupported(String mimeType) {
179                    return isSupported(mimeType);
180            }
181    
182            @Override
183            public boolean isSupported(String mimeType) {
184                    return _imageMimeTypes.contains(mimeType);
185            }
186    
187            @Override
188            public void storeThumbnail(
189                            long companyId, long groupId, long fileEntryId, long fileVersionId,
190                            long custom1ImageId, long custom2ImageId, InputStream is,
191                            String type)
192                    throws Exception {
193    
194                    _storeThumbnail(
195                            companyId, groupId, fileEntryId, fileVersionId, custom1ImageId,
196                            custom2ImageId, is, type);
197            }
198    
199            @Override
200            public void trigger(
201                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
202    
203                    super.trigger(sourceFileVersion, destinationFileVersion);
204    
205                    _queueGeneration(sourceFileVersion, destinationFileVersion);
206            }
207    
208            @Override
209            protected void doExportGeneratedFiles(
210                            PortletDataContext portletDataContext, FileEntry fileEntry,
211                            Element fileEntryElement)
212                    throws Exception {
213    
214                    exportThumbnails(
215                            portletDataContext, fileEntry, fileEntryElement, "image");
216    
217                    exportPreview(portletDataContext, fileEntry, fileEntryElement);
218            }
219    
220            @Override
221            protected void doImportGeneratedFiles(
222                            PortletDataContext portletDataContext, FileEntry fileEntry,
223                            FileEntry importedFileEntry, Element fileEntryElement)
224                    throws Exception {
225    
226                    importThumbnails(
227                            portletDataContext, fileEntry, importedFileEntry, fileEntryElement,
228                            "image");
229    
230                    FileVersion importedFileVersion = importedFileEntry.getFileVersion();
231    
232                    if (!_previewGenerationRequired(importedFileVersion)) {
233                            return;
234                    }
235    
236                    importPreview(
237                            portletDataContext, fileEntry, importedFileEntry, fileEntryElement,
238                            "image", getPreviewType(importedFileVersion));
239            }
240    
241            protected void exportPreview(
242                            PortletDataContext portletDataContext, FileEntry fileEntry,
243                            Element fileEntryElement)
244                    throws Exception {
245    
246                    FileVersion fileVersion = fileEntry.getFileVersion();
247    
248                    if (!isSupported(fileVersion) ||
249                            !_previewGenerationRequired(fileVersion) ||
250                            !_hasPreview(fileVersion)) {
251    
252                            return;
253                    }
254    
255                    exportPreview(
256                            portletDataContext, fileEntry, fileEntryElement, "image",
257                            getPreviewType(fileVersion));
258            }
259    
260            @Override
261            protected List<Long> getFileVersionIds() {
262                    return _fileVersionIds;
263            }
264    
265            private void _generateImages(
266                            FileVersion sourceFileVersion, FileVersion destinationFileVersion)
267                    throws Exception {
268    
269                    InputStream inputStream = null;
270    
271                    try {
272                            if (sourceFileVersion != null) {
273                                    copy(sourceFileVersion, destinationFileVersion);
274    
275                                    return;
276                            }
277    
278                            if (!PropsValues.DL_FILE_ENTRY_THUMBNAIL_ENABLED &&
279                                    !PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED) {
280    
281                                    return;
282                            }
283    
284                            inputStream = destinationFileVersion.getContentStream(false);
285    
286                            byte[] bytes = FileUtil.getBytes(inputStream);
287    
288                            ImageBag imageBag = ImageToolUtil.read(bytes);
289    
290                            RenderedImage renderedImage = imageBag.getRenderedImage();
291    
292                            if (renderedImage == null) {
293                                    return;
294                            }
295    
296                            ColorModel colorModel = renderedImage.getColorModel();
297    
298                            if (colorModel.getNumColorComponents() == 4) {
299                                    Future<RenderedImage> future = ImageToolUtil.convertCMYKtoRGB(
300                                            bytes, imageBag.getType());
301    
302                                    if (future == null) {
303                                            return;
304                                    }
305    
306                                    String processIdentity = String.valueOf(
307                                            destinationFileVersion.getFileVersionId());
308    
309                                    futures.put(processIdentity, future);
310    
311                                    RenderedImage convertedRenderedImage = future.get();
312    
313                                    if (convertedRenderedImage != null) {
314                                            renderedImage = convertedRenderedImage;
315                                    }
316                            }
317    
318                            if (!_hasPreview(destinationFileVersion)) {
319                                    _storePreviewImage(destinationFileVersion, renderedImage);
320                            }
321    
322                            if (!hasThumbnails(destinationFileVersion)) {
323                                    storeThumbnailImages(destinationFileVersion, renderedImage);
324                            }
325                    }
326                    catch (NoSuchFileEntryException nsfee) {
327                            if (_log.isDebugEnabled()) {
328                                    _log.debug(nsfee, nsfee);
329                            }
330                    }
331                    finally {
332                            StreamUtil.cleanUp(inputStream);
333    
334                            _fileVersionIds.remove(destinationFileVersion.getFileVersionId());
335                    }
336            }
337    
338            private String _getType(FileVersion fileVersion) {
339                    String type = "png";
340    
341                    if (fileVersion == null) {
342                            return type;
343                    }
344    
345                    String mimeType = fileVersion.getMimeType();
346    
347                    if (mimeType.equals(ContentTypes.IMAGE_BMP)) {
348                            type = ImageTool.TYPE_BMP;
349                    }
350                    else if (mimeType.equals(ContentTypes.IMAGE_GIF)) {
351                            type = ImageTool.TYPE_GIF;
352                    }
353                    else if (mimeType.equals(ContentTypes.IMAGE_JPEG)) {
354                            type = ImageTool.TYPE_JPEG;
355                    }
356                    else if (mimeType.equals(ContentTypes.IMAGE_PNG)) {
357                            type = ImageTool.TYPE_PNG;
358                    }
359                    else if (!_previewGenerationRequired(fileVersion)) {
360                            type = fileVersion.getExtension();
361                    }
362    
363                    return type;
364            }
365    
366            private boolean _hasPreview(FileVersion fileVersion)
367                    throws PortalException {
368    
369                    if (PropsValues.DL_FILE_ENTRY_PREVIEW_ENABLED &&
370                            _previewGenerationRequired(fileVersion)) {
371    
372                            String type = getPreviewType(fileVersion);
373    
374                            String previewFilePath = getPreviewFilePath(fileVersion, type);
375    
376                            if (!DLStoreUtil.hasFile(
377                                            fileVersion.getCompanyId(), REPOSITORY_ID,
378                                            previewFilePath)) {
379    
380                                    return false;
381                            }
382                    }
383    
384                    return true;
385            }
386    
387            private boolean _previewGenerationRequired(FileVersion fileVersion) {
388                    String mimeType = fileVersion.getMimeType();
389    
390                    if (mimeType.contains("tiff") || mimeType.contains("tif")) {
391                            return true;
392                    }
393                    else {
394                            return false;
395                    }
396            }
397    
398            private void _queueGeneration(
399                    FileVersion sourceFileVersion, FileVersion destinationFileVersion) {
400    
401                    if (_fileVersionIds.contains(
402                                    destinationFileVersion.getFileVersionId()) ||
403                            !isSupported(destinationFileVersion)) {
404    
405                            return;
406                    }
407    
408                    _fileVersionIds.add(destinationFileVersion.getFileVersionId());
409    
410                    sendGenerationMessage(
411                            DestinationNames.DOCUMENT_LIBRARY_IMAGE_PROCESSOR,
412                            sourceFileVersion, destinationFileVersion);
413            }
414    
415            private void _storePreviewImage(
416                            FileVersion fileVersion, RenderedImage renderedImage)
417                    throws Exception {
418    
419                    String type = getPreviewType(fileVersion);
420    
421                    File file = null;
422    
423                    try {
424                            file = FileUtil.createTempFile(type);
425    
426                            try (FileOutputStream fos = new FileOutputStream(file)) {
427                                    ImageToolUtil.write(renderedImage, type, fos);
428                            }
429    
430                            addFileToStore(
431                                    fileVersion.getCompanyId(), PREVIEW_PATH,
432                                    getPreviewFilePath(fileVersion, type), file);
433                    }
434                    finally {
435                            FileUtil.delete(file);
436                    }
437            }
438    
439            private void _storeThumbnail(
440                            long companyId, long groupId, long fileEntryId, long fileVersionId,
441                            long custom1ImageId, long custom2ImageId, InputStream is,
442                            String type)
443                    throws Exception {
444    
445                    StringBundler sb = new StringBundler(5);
446    
447                    sb.append(getPathSegment(groupId, fileEntryId, fileVersionId, false));
448    
449                    if (custom1ImageId != 0) {
450                            sb.append(StringPool.DASH);
451                            sb.append(1);
452                    }
453                    else if (custom2ImageId != 0) {
454                            sb.append(StringPool.DASH);
455                            sb.append(2);
456                    }
457    
458                    if (Validator.isNotNull(type)) {
459                            sb.append(StringPool.PERIOD);
460                            sb.append(type);
461                    }
462    
463                    String filePath = sb.toString();
464    
465                    File file = null;
466    
467                    try {
468                            file = FileUtil.createTempFile(is);
469    
470                            addFileToStore(companyId, THUMBNAIL_PATH, filePath, file);
471                    }
472                    finally {
473                            FileUtil.delete(file);
474                    }
475            }
476    
477            private static final Log _log = LogFactoryUtil.getLog(
478                    ImageProcessorImpl.class);
479    
480            private final List<Long> _fileVersionIds = new Vector<>();
481            private final Set<String> _imageMimeTypes = SetUtil.fromArray(
482                    PropsValues.DL_FILE_ENTRY_PREVIEW_IMAGE_MIME_TYPES);
483    
484    }