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