KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > outerj > daisy > presavehook > image > ImagePreSaveHook


1 /*
2  * Copyright 2004 Outerthought bvba and Schaubroeck nv
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.outerj.daisy.presavehook.image;
17
18 import org.outerj.daisy.repository.*;
19 import org.apache.avalon.framework.service.Serviceable;
20 import org.apache.avalon.framework.service.ServiceManager;
21 import org.apache.avalon.framework.service.ServiceException;
22 import org.apache.avalon.framework.activity.Disposable;
23 import org.apache.avalon.framework.configuration.Configurable;
24 import org.apache.avalon.framework.configuration.Configuration;
25 import org.apache.avalon.framework.configuration.ConfigurationException;
26 import org.apache.avalon.framework.logger.LogEnabled;
27 import org.apache.avalon.framework.logger.Logger;
28
29 import javax.imageio.ImageIO JavaDoc;
30 import java.awt.image.BufferedImage JavaDoc;
31 import java.awt.*;
32 import java.io.InputStream JavaDoc;
33 import java.io.ByteArrayOutputStream JavaDoc;
34 import java.util.Map JavaDoc;
35 import java.util.HashMap JavaDoc;
36 import java.util.Iterator JavaDoc;
37 import java.util.Collections JavaDoc;
38
39 import EDU.oswego.cs.dl.util.concurrent.FIFOSemaphore;
40 import com.sun.image.codec.jpeg.JPEGImageDecoder;
41 import com.sun.image.codec.jpeg.JPEGCodec;
42 import com.sun.image.codec.jpeg.JPEGDecodeParam;
43 import com.drew.metadata.Metadata;
44 import com.drew.metadata.Directory;
45 import com.drew.metadata.MetadataException;
46 import com.drew.metadata.Tag;
47 import com.drew.metadata.exif.ExifDirectory;
48 import com.drew.imaging.jpeg.JpegMetadataReader;
49
50 /**
51  * A pre-save hook for extracting information from images and generating
52  * thumbnail & preview images. It can be configured to work for multiple
53  * document types.
54  *
55  * @avalon.component version="1.0" name="image-presavehook" lifestyle="singleton"
56  */

57 public class ImagePreSaveHook implements PreSaveHook, Serviceable, Disposable, Configurable, LogEnabled {
58     private Logger logger;
59     private ServiceManager serviceManager;
60     private PreSaveHookRegistrar preSaveHookRegistrar;
61     private FIFOSemaphore imageProcessingSemaphore;
62     private Map JavaDoc docTypeConfigs = new HashMap JavaDoc();
63
64     public void enableLogging(Logger logger) {
65         this.logger = logger;
66     }
67
68     /**
69      * @avalon.dependency key="presavehook-registrar" type="org.outerj.daisy.repository.PreSaveHookRegistrar"
70      */

71     public void service(ServiceManager serviceManager) throws ServiceException {
72         this.serviceManager = serviceManager;
73         preSaveHookRegistrar = (PreSaveHookRegistrar)serviceManager.lookup("presavehook-registrar");
74         preSaveHookRegistrar.registerPreSaveHook(this);
75     }
76
77     public void configure(Configuration configuration) throws ConfigurationException {
78         int concurrentOperations = configuration.getChild("maxConcurrentOperations").getValueAsInteger();
79         imageProcessingSemaphore = new FIFOSemaphore(concurrentOperations);
80
81         Configuration[] configs = configuration.getChildren("config");
82         for (int i = 0; i < configs.length; i++) {
83             Configuration config = configs[i];
84             String JavaDoc docType = config.getAttribute("documentType");
85
86             if (docTypeConfigs.containsKey(docType)) {
87                 // user overwritten configs seem to come before built-in ones, so use the
88
// first one
89
continue;
90             }
91
92             DoctypeConfig dtconfig = new DoctypeConfig();
93             dtconfig.enabled = config.getChild("enabled").getValueAsBoolean(true);
94             dtconfig.maxImageSize = config.getChild("maxImageSize").getValueAsInteger(3000000);
95             dtconfig.imagePartName = config.getChild("imagePartName").getValue();
96             dtconfig.widthFieldName = config.getChild("widthFieldName").getValue(null);
97             dtconfig.heightFieldName = config.getChild("heightFieldName").getValue(null);
98             dtconfig.previewPartName = config.getChild("previewPartName").getValue(null);
99             dtconfig.previewMaxSize = config.getChild("previewMaxSize").getValueAsInteger(250);
100             dtconfig.thumbnailPartName = config.getChild("thumbnailPartName").getValue(null);
101             dtconfig.thumbnailMaxSize = config.getChild("thumbnailMaxSize").getValueAsInteger(125);
102
103             Configuration[] metadatas = config.getChildren("metadata");
104             for (int k = 0; k < metadatas.length; k++) {
105                 String JavaDoc type = metadatas[k].getAttribute("type");
106                 if (!METADATA_VALUE_GETTERS.containsKey(type))
107                     throw new ConfigurationException("ImagePreSaveHook: metadata/@type has an invalid value: " + type + " at " + metadatas[k].getLocation());
108                 dtconfig.addMetadata(metadatas[k].getAttribute("tag"), metadatas[k].getAttribute("field"), type);
109             }
110
111             docTypeConfigs.put(docType, dtconfig);
112         }
113     }
114
115     public void dispose() {
116         preSaveHookRegistrar.unregisterPreSaveHook(this);
117         serviceManager.release(preSaveHookRegistrar);
118     }
119
120     public String JavaDoc getName() {
121         return "image-pre-save-hook";
122     }
123
124     public void process(Document document, Repository repository) throws Exception JavaDoc {
125         // The semaphore is to avoid that dozens of threads would start concurrently generating image thumbnails,
126
// which makes little sense as this is not much I/O bound and could eat lots of memory
127
imageProcessingSemaphore.acquire();
128         try {
129             String JavaDoc documentTypeName = repository.getRepositorySchema().getDocumentTypeById(document.getDocumentTypeId(), false).getName();
130             DoctypeConfig dtconfig = (DoctypeConfig)docTypeConfigs.get(documentTypeName);
131             if (dtconfig == null)
132                 return;
133             if (!dtconfig.enabled)
134                 return;
135
136             // First, clear out all data which is automatically assigned, so that if extraction of image
137
// information fails for some reason, no old data is left in the automatically assigned parts and fields
138
if (dtconfig.previewPartName != null)
139                 document.deletePart(dtconfig.previewPartName);
140             if (dtconfig.thumbnailPartName != null)
141                 document.deletePart(dtconfig.thumbnailPartName);
142             if (dtconfig.widthFieldName != null)
143                 document.deleteField(dtconfig.widthFieldName);
144             if (dtconfig.heightFieldName != null)
145                 document.deleteField(dtconfig.heightFieldName);
146             Iterator JavaDoc metadataInfoIt = dtconfig.getMetadataInfoIterator();
147             while (metadataInfoIt.hasNext()) {
148                 MetadataInfo metadataInfo = (MetadataInfo)metadataInfoIt.next();
149                 document.deleteField(metadataInfo.field);
150             }
151
152             if (!document.hasPart(dtconfig.imagePartName))
153                 return;
154
155             Part part = document.getPart(dtconfig.imagePartName);
156
157             // Protection against too large images
158
long size = part.getSize();
159             if (size > dtconfig.maxImageSize) {
160                 if (logger.isInfoEnabled()) {
161                     logger.info("Skipped image information extraction as the image was too large.");
162                 }
163             } else if (size == 0) {
164                 logger.info("Skipped image information extraction as the image size was unknown.");
165             }
166
167             // Read the image
168
InputStream JavaDoc is = null;
169             BufferedImage JavaDoc sourceImage;
170             Metadata metadata = null;
171             try {
172                 is = part.getDataStream();
173                 String JavaDoc mimeType = part.getMimeType();
174                 if (mimeType.equals("image/jpeg") || mimeType.equals("image/x-jpeg")) {
175                     JPEGImageDecoder jpegDecoder = JPEGCodec.createJPEGDecoder(is);
176                     sourceImage = jpegDecoder.decodeAsBufferedImage();
177                     JPEGDecodeParam decodeParam = jpegDecoder.getJPEGDecodeParam();
178                     metadata = JpegMetadataReader.readMetadata(decodeParam);
179                 } else {
180                     sourceImage = ImageIO.read(is);
181                 }
182             } finally {
183                 if (is != null)
184                     is.close();
185             }
186
187             if (sourceImage == null) {
188                 // The image could not be read (unsupported format)
189
return;
190             }
191
192             // Extract size info
193
int width = sourceImage.getWidth();
194             int height = sourceImage.getHeight();
195
196             if (dtconfig.widthFieldName != null)
197                 document.setField(dtconfig.widthFieldName, new Long JavaDoc(width));
198             if (dtconfig.heightFieldName != null)
199                 document.setField(dtconfig.heightFieldName, new Long JavaDoc(height));
200
201             if (metadata != null && dtconfig.isSetMetadata()) {
202                 Directory dir = metadata.getDirectory(ExifDirectory.class);
203                 MetadataInfo metadataInfo;
204
205                 Iterator JavaDoc tagIt = dir.getTagIterator();
206                 while (tagIt.hasNext()) {
207                     Tag tag = (Tag)tagIt.next();
208                     if (logger.isDebugEnabled())
209                         logger.debug("[" + tag.getTagType() + "] " + tag.getTagName() + " : " + tag.getDescription() + " (" + dir.getObject(tag.getTagType()).getClass().getName() + ") (" + dir.getObject(tag.getTagType()) + ")");
210                     metadataInfo = dtconfig.getMetadataInfo(tag.getTagName());
211                     if (metadataInfo != null) {
212                         Object JavaDoc value = metadataInfo.getValueGetter().getValue(tag.getTagType(), dir);
213                         document.setField(metadataInfo.field, value);
214                     }
215                 }
216             }
217
218             // Create preview and thumbnail
219
if (dtconfig.previewPartName != null) {
220                 BufferedImage JavaDoc previewImage = resizeImage(sourceImage, dtconfig.previewMaxSize);
221                 sourceImage = previewImage; // start thumbnail from here, this is often significantly faster
222
ByteArrayOutputStream JavaDoc previewData = new ByteArrayOutputStream JavaDoc();
223                 ImageIO.write(previewImage, "jpg", previewData);
224                 document.setPart(dtconfig.previewPartName, "image/jpeg", previewData.toByteArray());
225             }
226
227             if (dtconfig.thumbnailPartName != null) {
228                 BufferedImage JavaDoc thumbnailImage = resizeImage(sourceImage, dtconfig.thumbnailMaxSize);
229                 ByteArrayOutputStream JavaDoc thumbnailData = new ByteArrayOutputStream JavaDoc();
230                 ImageIO.write(thumbnailImage, "jpg", thumbnailData);
231                 document.setPart(dtconfig.thumbnailPartName, "image/jpeg", thumbnailData.toByteArray());
232             }
233         } finally {
234             imageProcessingSemaphore.release();
235         }
236     }
237
238     private BufferedImage JavaDoc resizeImage(BufferedImage JavaDoc sourceImage, int maxSize) {
239         Image JavaDoc resizedImage;
240         int width = sourceImage.getWidth();
241         int height = sourceImage.getHeight();
242
243         // if it's already small enough, do nothing
244
if (Math.max(width, height) <= maxSize)
245             return sourceImage;
246
247         if (width > height)
248             resizedImage = sourceImage.getScaledInstance(maxSize, (maxSize * height) / width, Image.SCALE_SMOOTH);
249         else
250             resizedImage = sourceImage.getScaledInstance((maxSize * width) / height, maxSize, Image.SCALE_SMOOTH);
251
252         // Create the buffered image.
253
BufferedImage JavaDoc bufferedImage = new BufferedImage JavaDoc(resizedImage.getWidth(null), resizedImage.getHeight(null),
254                 BufferedImage.TYPE_INT_RGB);
255         // Copy image to buffered image.
256
Graphics g = bufferedImage.createGraphics();
257
258         // Clear background: important when having transparent images which otherwise use a black background
259
g.setColor(Color.white);
260         g.fillRect(0, 0, resizedImage.getWidth(null), resizedImage.getHeight(null));
261
262         g.drawImage(resizedImage, 0, 0, null);
263         g.dispose();
264         return bufferedImage;
265     }
266
267     static class DoctypeConfig {
268         public boolean enabled;
269         public int maxImageSize;
270         public String JavaDoc imagePartName;
271         public String JavaDoc widthFieldName;
272         public String JavaDoc heightFieldName;
273         public String JavaDoc previewPartName;
274         public int previewMaxSize;
275         public String JavaDoc thumbnailPartName;
276         public int thumbnailMaxSize;
277         private Map JavaDoc metadatas;
278
279         public boolean isSetMetadata() {
280             return metadatas != null;
281         }
282
283         public MetadataInfo getMetadataInfo(String JavaDoc tagName) {
284             if (metadatas != null)
285                 return (MetadataInfo)metadatas.get(tagName);
286             else
287                 return null;
288         }
289
290         public Iterator JavaDoc getMetadataInfoIterator() {
291             if (metadatas != null)
292                 return metadatas.values().iterator();
293             else
294                 return Collections.EMPTY_LIST.iterator();
295         }
296
297         public void addMetadata(String JavaDoc tagName, String JavaDoc field, String JavaDoc type) {
298             if (metadatas == null)
299                 metadatas = new HashMap JavaDoc();
300             MetadataInfo metadataInfo = new MetadataInfo();
301             metadataInfo.field = field;
302             metadataInfo.type = type;
303             metadatas.put(tagName, metadataInfo);
304         }
305     }
306
307     static class MetadataInfo {
308         public String JavaDoc field;
309         public String JavaDoc type;
310
311         public MetadataValueGetter getValueGetter() {
312             return (MetadataValueGetter)METADATA_VALUE_GETTERS.get(type);
313         }
314     }
315
316     static interface MetadataValueGetter {
317         Object JavaDoc getValue(int tagType, Directory directory) throws MetadataException;
318     }
319
320     static class StringValueGetter implements MetadataValueGetter {
321         public Object JavaDoc getValue(int tagType, Directory directory) {
322             return directory.getString(tagType);
323         }
324     }
325
326     static class DateValueGetter implements MetadataValueGetter {
327         public Object JavaDoc getValue(int tagType, Directory directory) throws MetadataException {
328             return directory.getDate(tagType);
329         }
330     }
331
332     static class LongValueGetter implements MetadataValueGetter {
333         public Object JavaDoc getValue(int tagType, Directory directory) throws MetadataException {
334             return new Long JavaDoc(directory.getLong(tagType));
335         }
336     }
337
338     static class DoubleValueGetter implements MetadataValueGetter {
339         public Object JavaDoc getValue(int tagType, Directory directory) throws MetadataException {
340             return new Double JavaDoc(directory.getDouble(tagType));
341         }
342     }
343
344     static class DescriptionValueGetter implements MetadataValueGetter {
345         public Object JavaDoc getValue(int tagType, Directory directory) throws MetadataException {
346             return directory.getDescription(tagType);
347         }
348     }
349
350     private static Map JavaDoc METADATA_VALUE_GETTERS;
351     static {
352         METADATA_VALUE_GETTERS = new HashMap JavaDoc();
353         METADATA_VALUE_GETTERS.put("description", new DescriptionValueGetter());
354         METADATA_VALUE_GETTERS.put("string", new StringValueGetter());
355         METADATA_VALUE_GETTERS.put("datetime", new DateValueGetter());
356         METADATA_VALUE_GETTERS.put("long", new LongValueGetter());
357         METADATA_VALUE_GETTERS.put("double", new DoubleValueGetter());
358     }
359 }
360
Popular Tags