1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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 }