View Javadoc

1   /*
2    * Copyright 2016-2016 Hippo B.V. (http://www.onehippo.com)
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.onehippo.forge.gallerymagick.cms.plugins.gallery.processor;
17  
18  import java.awt.Dimension;
19  import java.io.BufferedInputStream;
20  import java.io.File;
21  import java.io.FileInputStream;
22  import java.io.FileOutputStream;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.util.Calendar;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import javax.jcr.Binary;
30  import javax.jcr.Node;
31  import javax.jcr.RepositoryException;
32  
33  import org.apache.commons.io.FilenameUtils;
34  import org.apache.commons.io.IOUtils;
35  import org.apache.commons.lang.StringUtils;
36  import org.hippoecm.frontend.editor.plugins.resource.MimeTypeHelper;
37  import org.hippoecm.frontend.editor.plugins.resource.ResourceHelper;
38  import org.hippoecm.frontend.plugins.gallery.imageutil.ScalingParameters;
39  import org.hippoecm.frontend.plugins.gallery.model.GalleryException;
40  import org.hippoecm.frontend.plugins.gallery.model.GalleryProcessor;
41  import org.hippoecm.frontend.plugins.gallery.processor.AbstractGalleryProcessor;
42  import org.hippoecm.repository.HippoStdNodeType;
43  import org.hippoecm.repository.gallery.HippoGalleryNodeType;
44  import org.onehippo.forge.gallerymagick.core.ImageDimension;
45  import org.onehippo.forge.gallerymagick.core.command.GraphicsMagickCommandUtils;
46  import org.onehippo.forge.gallerymagick.core.command.ImageMagickCommandUtils;
47  import org.onehippo.forge.gallerymagick.core.command.MagickExecuteException;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  /**
52   * {@link GalleryProcessor} implementation using Gallery Magick Forge library.
53   */
54  public class MagickCommandGalleryProcessor extends AbstractGalleryProcessor {
55  
56      private static final long serialVersionUID = 1L;
57  
58      private static final Logger log = LoggerFactory.getLogger(MagickCommandGalleryProcessor.class);
59  
60      private static final String MAGICK_COMMAND_TEMP_FILE_PREFIX = "_magickproc";
61  
62      private static final String GALLERY_MAGICK_METADATA_PROP_NAME = "gallerymagick.metadata";
63  
64      private static ThreadLocal<File> tlSourceDataFile = new ThreadLocal<>();
65  
66      private final Map<String, ScalingParameters> scalingParametersMap = new HashMap<>();
67  
68      private final String magickImageProcessor;
69  
70      public MagickCommandGalleryProcessor(final String magickImageProcessor, final Map<String, ScalingParameters> initScalingParametersMap) {
71          this.magickImageProcessor = magickImageProcessor;
72  
73          if (initScalingParametersMap != null) {
74              scalingParametersMap.putAll(initScalingParametersMap);
75          }
76      }
77  
78      @Override
79      public void initGalleryNode(Node node, InputStream data, String mimeType, String fileName)
80              throws RepositoryException {
81          node.setProperty(HippoGalleryNodeType.IMAGE_SET_FILE_NAME, fileName);
82      }
83  
84      @Override
85      public void makeImage(Node node, InputStream data, String mimeType, String fileName)
86              throws GalleryException, RepositoryException {
87          File sourceFile = null;
88          InputStream sourceFileInput = null;
89  
90          try {
91              sourceFile = saveOriginalImageDataToFile(data, fileName);
92              extractAndSaveImageMetadata(node, sourceFile);
93              sourceFileInput = new FileInputStream(sourceFile);
94              tlSourceDataFile.set(sourceFile);
95              super.makeImage(node, sourceFileInput, mimeType, fileName);
96          } catch (IOException e) {
97              throw new GalleryException(e.toString(), e);
98          } finally {
99              tlSourceDataFile.remove();
100 
101             IOUtils.closeQuietly(sourceFileInput);
102             IOUtils.closeQuietly(data);
103 
104             if (sourceFile != null) {
105                 log.debug("Deleting the original image file at '{}'.", sourceFile);
106                 sourceFile.delete();
107             }
108         }
109     }
110 
111     @Override
112     public void initGalleryResource(final Node node, final InputStream data, final String mimeType,
113             final String fileName, final Calendar lastModified) throws RepositoryException {
114         node.setProperty("jcr:mimeType", mimeType);
115         node.setProperty("jcr:lastModified", lastModified);
116 
117         final String nodeName = node.getName();
118         boolean sourceFileCreated = false;
119         File sourceFile = tlSourceDataFile.get();
120 
121         if (sourceFile == null) {
122             // sourceFile can be null sometimes when a user clicks on 'Restore' button to restore thumbnail in UI.
123             try {
124                 sourceFile = saveOriginalImageDataToFile(data, fileName);
125                 sourceFileCreated = true;
126             } catch (IOException e) {
127                 throw new RuntimeException(e.toString(), e);
128             }
129         }
130 
131         File targetFile = null;
132         File targetTempFile = null;
133 
134         if (MimeTypeHelper.isImageMimeType(mimeType)) {
135             final ScalingParameters scalingParameters = getScalingParametersMap().get(nodeName);
136 
137             if (scalingParameters != null && scalingParameters.getWidth() > 0 && scalingParameters.getHeight() > 0) {
138                 try {
139                     targetTempFile = File.createTempFile(
140                             MAGICK_COMMAND_TEMP_FILE_PREFIX + "_" + StringUtils.replace(nodeName, ":", "_"),
141                             "." + FilenameUtils.getExtension(fileName));
142 
143                     ImageDimension dimension = ImageDimension.from(scalingParameters.getWidth(),
144                             scalingParameters.getHeight());
145                     log.debug("Resizing the original image file ('{}') to '{}' with dimension, {}.", sourceFile,
146                             targetTempFile, dimension);
147                     resizeImage(sourceFile, targetTempFile, dimension);
148                     targetFile = targetTempFile;
149                     targetTempFile = null;
150                 } catch (Exception e) {
151                     log.warn("Scaling failed, using original image instead", e);
152                 }
153             } else {
154                 log.debug(
155                         "No scaling parameters specified for {} or width or height is zero or negative. So use original image",
156                         nodeName);
157             }
158         } else {
159             log.debug("Unknown image MIME type: {}, using raw data", mimeType);
160         }
161 
162         ImageDimension dimension = null;
163         InputStream imageFileIn = null;
164         BufferedInputStream imageBufIn = null;
165         Binary imageBinary = null;
166 
167         try {
168             if (targetFile != null) {
169                 dimension = identifyDimension(targetFile);
170                 imageFileIn = new FileInputStream(targetFile);
171             } else {
172                 dimension = identifyDimension(sourceFile);
173                 imageFileIn = new FileInputStream(sourceFile);
174             }
175 
176             imageBufIn = new BufferedInputStream(imageFileIn);
177             imageBinary = ResourceHelper.getValueFactory(node).createBinary(imageBufIn);
178 
179             log.debug("Storing an image binary at '{}' from file at '{}'.", node.getPath(),
180                     targetFile != null ? targetFile : sourceFile);
181 
182             node.setProperty("jcr:data", imageBinary);
183             node.setProperty(HippoGalleryNodeType.IMAGE_WIDTH, (long) dimension.getWidth());
184             node.setProperty(HippoGalleryNodeType.IMAGE_HEIGHT, (long) dimension.getHeight());
185         } catch (IOException e) {
186             log.error("Failed to store an image variant due to IO error.", e);
187         } finally {
188             try {
189                 if (imageBinary != null) {
190                     imageBinary.dispose();
191                 }
192             } catch (Exception ignore) {
193             }
194 
195             IOUtils.closeQuietly(imageBufIn);
196             IOUtils.closeQuietly(imageFileIn);
197 
198             if (targetTempFile != null) {
199                 log.debug("Deleting the temporary resized target image file at '{}'.", targetTempFile);
200                 targetTempFile.delete();
201             }
202 
203             if (targetFile != null) {
204                 log.debug("Deleting the resized target image file at '{}'.", targetFile);
205                 targetFile.delete();
206             }
207 
208             if (sourceFileCreated && sourceFile != null) {
209                 log.debug("Deleting the original source image file at '{}'.", sourceFile);
210                 sourceFile.delete();
211             }
212         }
213     }
214 
215     @Override
216     public Dimension getDesiredResourceDimension(Node resource) throws GalleryException, RepositoryException {
217         String nodeName = resource.getName();
218         ScalingParameters params = scalingParametersMap.get(nodeName);
219 
220         if (params != null) {
221             int width = params.getWidth();
222             int height = params.getHeight();
223             return new Dimension(width, height);
224         } else {
225             return null;
226         }
227     }
228 
229     @Override
230     public Map<String, ScalingParameters> getScalingParametersMap() throws RepositoryException {
231         return scalingParametersMap;
232     }
233 
234     protected boolean isImageMagickImageProcessor() {
235         return StringUtils.equalsIgnoreCase("ImageMagick", magickImageProcessor);
236     }
237 
238     protected void resizeImage(File sourceFile, File targetFile, ImageDimension dimension) throws MagickExecuteException, IOException {
239         if (isImageMagickImageProcessor()) {
240             ImageMagickCommandUtils.resizeImage(sourceFile, targetFile, dimension);
241         } else {
242             GraphicsMagickCommandUtils.resizeImage(sourceFile, targetFile, dimension);
243         }
244     }
245 
246     protected String identifyAllMetadata(File sourceFile) throws MagickExecuteException, IOException {
247         if (isImageMagickImageProcessor()) {
248             return ImageMagickCommandUtils.identifyAllMetadata(sourceFile);
249         } else {
250             return GraphicsMagickCommandUtils.identifyAllMetadata(sourceFile);
251         }
252     }
253 
254     protected ImageDimension identifyDimension(File sourceFile) throws MagickExecuteException, IOException {
255         if (isImageMagickImageProcessor()) {
256             return ImageMagickCommandUtils.identifyDimension(sourceFile);
257         } else {
258             return GraphicsMagickCommandUtils.identifyDimension(sourceFile);
259         }
260     }
261 
262     protected void extractAndSaveImageMetadata(Node node, File sourceFile) {
263         String nodePath = null;
264 
265         try {
266             final String imageMetadata = identifyAllMetadata(sourceFile);
267             nodePath = node.getPath();
268 
269             if (!node.isNodeType(HippoStdNodeType.NT_RELAXED)) {
270                 node.addMixin(HippoStdNodeType.NT_RELAXED);
271                 node.setProperty(GALLERY_MAGICK_METADATA_PROP_NAME, StringUtils.defaultString(imageMetadata));
272             }
273         } catch (Exception e) {
274             log.error("Failed to extract image metadata or failed to store the metadata in '{}' property at '{}'.",
275                     GALLERY_MAGICK_METADATA_PROP_NAME, nodePath, e);
276         }
277     }
278 
279     private File saveOriginalImageDataToFile(final InputStream dataIput, final String fileName) throws IOException {
280         File sourceFile = null;
281         FileOutputStream fos = null;
282 
283         try {
284             sourceFile = File.createTempFile(MAGICK_COMMAND_TEMP_FILE_PREFIX,
285                     "." + FilenameUtils.getExtension(fileName));
286             fos = new FileOutputStream(sourceFile);
287             log.debug("Storing original image source file ('{}') to '{}'.", fileName, sourceFile);
288             IOUtils.copy(dataIput, fos);
289         } finally {
290             IOUtils.closeQuietly(fos);
291         }
292 
293         return sourceFile;
294     }
295 }