This project has retired. For details please refer to its Attic page.
PropertyMapperExif xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.mapper;
20  
21  import java.lang.reflect.Array;
22  import java.text.DateFormat;
23  import java.text.ParseException;
24  import java.text.SimpleDateFormat;
25  import java.util.Date;
26  import java.util.GregorianCalendar;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Properties;
30  import java.util.Set;
31  
32  import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
33  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
34  import org.apache.chemistry.opencmis.commons.enums.PropertyType;
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  
38  import com.drew.imaging.PhotographicConversions;
39  import com.drew.lang.Rational;
40  import com.drew.metadata.Directory;
41  import com.drew.metadata.Tag;
42  import com.drew.metadata.exif.ExifDirectory;
43  import com.drew.metadata.exif.GpsDirectory;
44  import com.drew.metadata.jpeg.JpegDirectory;
45  
46  public class PropertyMapperExif extends AbstractPropertyMapper {
47  
48      private static final Log LOG = LogFactory.getLog(PropertyMapperExif.class.getName());
49      private static String EXIF_DATE_FORMAT = "yyyy:MM:dd HH:mm:ss";
50  
51      private Map<String, String> propMapExif = new HashMap<String, String> (); // tag to property id
52      private Map<String, String> propMapGps = new HashMap<String, String> ();  // tag to property id
53      private Map<String, String> propMapJpeg = new HashMap<String, String> (); // tag to property id
54      
55      protected Map<String, Object> propMap;
56  
57      public boolean initialize(String cfgPrefix, String typeKey, Properties properties) {
58          super.initialize(cfgPrefix, typeKey, properties);
59          reset();
60          dateFormat = EXIF_DATE_FORMAT;
61          buildIdMap("exif", propMapExif, properties);
62          buildIdMap("gps", propMapGps, properties);
63          buildIdMap("jpeg", propMapJpeg, properties);
64          
65          return true;
66      }
67  
68      public void reset() {
69          propMap = new HashMap<String, Object> ();
70      }
71      
72  
73      public Map<String, Object> getMappedProperties() {
74          return propMap;
75      }
76      
77      private void buildIdMap(String dirKey, Map<String, String> propMap, Properties properties) {
78          Set<String> keys = properties.stringPropertyNames(); 
79          String prefix = propPrefix + "." + dirKey + ".id.";
80          for (String key : keys) {
81              if (key.startsWith(prefix)) {
82                  String id = key.substring(prefix.length());
83                  String cmisPropId = properties.getProperty(key).trim();
84                  if (null == cmisPropId)
85                      throw new MapperException("Configuration key " + key + " must have a value assigned");
86                  LOG.debug("Found mapping for type " + dirKey + " with " + id + " to " + cmisPropId);
87                  propMap.put(id,  cmisPropId);
88              }
89          }
90      }
91  
92      public String getMappedTypeId(String mimeType) {
93          return cmisTypeId;
94      }
95  
96      public String getMappedPropertyId(String key) {
97          return null;
98      }
99  
100     public Object convertValue(String id, PropertyDefinition<?> propDef, String strValue) {
101         return null;
102     }
103 
104     private String getHexString(int tagType) {
105         StringBuffer hexStr = new StringBuffer();
106         hexStr.append(Integer.toHexString(tagType));
107         while (hexStr.length() < 4)
108             hexStr.insert(0, "0");
109         hexStr.insert(0, "0x");
110         return hexStr.toString();
111     }
112     /**
113      * store the property id mapped to this tag in a JPEG file in a local map
114      * @param dir
115      *      directory of tag
116      * @param tag
117      *      tag
118      */
119     
120     public void mapTagAndConvert(Directory dir, Tag tag, TypeDefinition td) {
121         String propId = null;
122         String hexStr = getHexString(tag.getTagType());
123         
124         if (GpsDirectory.class.equals(dir.getClass())) {
125             propId = propMapGps.get(hexStr);
126         } else if (ExifDirectory.class.equals(dir.getClass())) {
127             propId = propMapExif.get(hexStr);
128         } else if (JpegDirectory.class.equals(dir.getClass())) {
129             propId = propMapJpeg.get(hexStr);
130         } else
131             propId = null;
132         
133         if (null != propId) {
134             if (null != td) {
135                 PropertyDefinition<?> pd = td.getPropertyDefinitions().get(propId);
136                 if (null == pd)
137                     throw new MapperException("Unknown property id " + propId + " in type definition " + td.getId());
138                 PropertyType pt = pd.getPropertyType();
139                 Object convValue = convertValue(dir, tag, pt);
140                 propMap.put(propId, convValue);
141             } else
142                 propMap.put(propId, dir.getObject(tag.getTagType())); // omit conversion if no type definition is available
143         }
144     }
145     
146     public Object convertValue(Directory dir, Tag tag, PropertyType propType) {
147         
148         Object res = null;
149         String hexStr = getHexString(tag.getTagType());
150 
151         // Handle all tags corresponding to their directory specifics
152         if (GpsDirectory.class.equals(dir.getClass())) {
153             // first check for those tags that need special consideration:
154             if ( GpsDirectory.TAG_GPS_LONGITUDE == tag.getTagType()) {
155                 Object ref = dir.getObject(GpsDirectory.TAG_GPS_DEST_LONGITUDE_REF);
156                 boolean mustInv = ref != null && ref.equals('W');
157                 return convertGps(tag, dir, mustInv);
158             } else if ( GpsDirectory.TAG_GPS_LATITUDE == tag.getTagType()) {
159                 Object ref = dir.getObject(GpsDirectory.TAG_GPS_DEST_LONGITUDE_REF);
160                 boolean mustInv = ref != null && ref.equals('S');
161                 return convertGps(tag, dir, mustInv);
162             } else {
163                 String propId = propMapGps.get(hexStr);
164                 LOG.debug("Found GPS tag '" + tag + "\', property mapped is: " + propId);
165                 if (null == propId) {
166                     LOG.info("Ignoring EXIF tag '" + tag + "\' no property mapped to this tag.");
167                 } else if (propType == null) {
168                     // should not happen and is a configuration error: we have a property id but no type
169                     LOG.error("Ignoring EXIF tag '" + tag + "\' no property type mapped to this tag.");
170                 }
171                 Object src = dir.getObject(tag.getTagType());        
172                 Class<?> clazz = src.getClass();
173                 if (clazz.equals(Rational.class)) {
174                     // expect a CMIS decimal property
175                     if (propType != PropertyType.DECIMAL)
176                         throw new MapperException("Tag value has type Rational and expected CMIS Decimal, but found: " + propType + " for tag: " + tag);
177                     double d = ((Rational) src).doubleValue();
178                     res = d;
179                 } else if (clazz.equals(String.class)) {
180                     if (propType != PropertyType.STRING && propType != PropertyType.ID && propType != PropertyType.URI &&
181                             propType != PropertyType.HTML && propType != PropertyType.DATETIME)
182                         throw new MapperException("Tag value has type String and expected CMIS String, but found: " + propType + " for tag: " + tag);
183                     String s = ((String) src);
184                     res = s;
185                 } else
186                 res = null;
187             }
188         } else if (ExifDirectory.class.equals(dir.getClass())) {
189             // is there a property mapped to this tag?
190             String propId = propMapExif.get(hexStr);
191             LOG.debug("Found EXIF tag '" + tag + "\', property mapped is: " + propId);
192 
193             if (null == propId) {
194                 LOG.debug("Ignoring EXIF tag '" + tag + "\' no property mapped to this tag.");
195             } else if (propType == null) {
196                 // should not happen and is a configuration error: we have a property id but no type
197                 LOG.error("Ignoring EXIF tag '" + tag + "\' no property type mapped to this tag.");
198             } else {
199                 Object src = dir.getObject(tag.getTagType());        
200                 Class<?> clazz = src.getClass();
201                 // handle arrays and map them to multi-value properties
202                 if (clazz.isArray()) {
203                     LOG.error("Found a multi-value tag " + tag + ": multi value not implemented");
204                     return null;
205                 }
206                 if (clazz.equals(Rational.class)) {
207                     // expect a CMIS decimal property
208                     if (propType != PropertyType.DECIMAL)
209                         throw new MapperException("Tag value has type Rational and expected CMIS Decimal, but found: " + propType + " for tag: " + tag);
210                     
211                     if (tag.getTagType() == ExifDirectory.TAG_SHUTTER_SPEED) {
212                         // requires special handling, see Tika impl.
213                         double apexValue = ((Rational) src).doubleValue();
214                         res = PhotographicConversions.shutterSpeedToExposureTime(apexValue);
215                     } else if (tag.getTagType() == (ExifDirectory.TAG_APERTURE)) {
216                         double aperture =((Rational) src).doubleValue();
217                         double fStop = PhotographicConversions.apertureToFStop(aperture);
218                         res = fStop;
219                     } else {
220                         // convert to a double
221                         double d = ((Rational) src).doubleValue();
222                         res = d;
223                     }
224                 } else if (clazz.equals(Integer.class)) {
225                     if (propType != PropertyType.INTEGER)
226                         throw new MapperException("Tag value has type Integer and expected CMIS Integer, but found: " + propType + " for tag: " + tag);
227                     // convert to a long
228                     long l = ((Integer) src).longValue();
229                     res = l;                                        
230                 } else if (clazz.equals(String.class)) {
231                     if (propType != PropertyType.STRING && propType != PropertyType.ID && propType != PropertyType.URI &&
232                             propType != PropertyType.HTML && propType != PropertyType.DATETIME)
233                         throw new MapperException("Tag value has type String and expected CMIS String, but found: " + propType + " for tag: " + tag);
234                     // convert to a String
235                     if (propType == PropertyType.DATETIME) {
236                         // parse format: 2012:02:25 16:23:16
237                         DateFormat formatter = new SimpleDateFormat(dateFormat);
238                         GregorianCalendar cal;
239                         try {
240                             Date date = (Date)formatter.parse((String) src);
241                             // convert date to GreogorianCalendar as CMIS expects
242                             cal = new GregorianCalendar();
243                             cal.setTime(date);
244                         } catch (ParseException e) {
245                             LOG.error(e);
246                             throw new MapperException("Unrecognized date format in EXIF date tag: " + src + " for tag: " + tag + " expected: yyyy:MM:dd HH:mm:ss");
247                         }
248                         res = cal;                     
249                     } else {
250                         String s = ((String) src);
251                         res = s;
252                     }
253                 } else if (clazz.equals(Date.class)) {
254                     if (propType != PropertyType.DATETIME)
255                         throw new MapperException("Tag value has type Date and expected CMIS DateTime, but found: " + propType + " for tag: " + tag);
256                     // convert to a String
257                     Date date = ((Date) src);
258                     // convert date to GreogorianCalendar as CMIS expects
259                     GregorianCalendar  cal= new GregorianCalendar();
260                     cal.setTime(date);
261                     res = cal;                                        
262                 } else if (clazz.equals(Boolean.class)) {
263                     if (propType != PropertyType.BOOLEAN)
264                         throw new MapperException("Tag value has type Boolean and expected CMIS Boolean, but found: " + propType + " for tag: " + tag);
265                     // convert to a String
266                     Boolean b = ((Boolean) src);
267                     res = b;                                        
268                 } else {
269                     LOG.debug("Tag value has unsupported type: " + clazz.getName() + " for EXIF tag: " + tag);
270                     // throw new MapperException("Tag value has unsupported type: " + clazz.getName() + " for tag: " + tag);
271                 }                
272             }            
273         } else if (JpegDirectory.class.equals(dir.getClass())) {
274             // is there a property mapped to this tag?
275             String propId = propMapJpeg.get(hexStr);
276             LOG.debug("Found JPEG tag '" + tag + "\', property mapped is: " + propId);
277 
278             if (null == propId) {
279                 LOG.info("Ignoring JPEG tag '" + tag + "\' no property mapped to this tag.");
280             } else if (propType == null) {
281                 // should not happen and is a configuration error: we have a property id but no type
282                 LOG.error("Ignoring JPEG tag '" + tag + "\' no property type mapped to this tag.");
283             } else {
284                 Object src = dir.getObject(tag.getTagType());        
285                 Class<?> clazz = src.getClass();
286 
287                 if (clazz.equals(Integer.class)) {
288                     if (propType != PropertyType.INTEGER)
289                         throw new MapperException("Tag value has type Integer and expected CMIS Integer, but found: " + propType + " for tag: " + tag);
290                     // convert to a long
291                     long l = ((Integer) src).longValue();
292                     res = l;                                        
293                 } else {
294                     LOG.debug("Tag value has unsupported type: " + clazz.getName() + " for JPEG tag: " + tag);
295                 }
296             }            
297         }
298             
299         return res;        
300     }
301     
302     private static Object convertGps(Tag tag, Directory dir, boolean mustInvert) {
303         Double res = null; 
304         Object src = dir.getObject(tag.getTagType());
305         
306         Class<?> stringArrayClass = src.getClass();
307         Class<?> stringArrayComponentType = stringArrayClass.getComponentType();
308         if (!stringArrayClass.isArray() || null == stringArrayComponentType || Array.getLength(src) != 3) 
309             throw new MapperException("GPS coordinate \"" + tag + "\" has unknown type.");
310         if (!stringArrayComponentType.equals(Rational.class))
311             throw new MapperException("GPS coordinate \"" + tag + "\" has unknown component type (expected Rational, found: " + 
312                     stringArrayComponentType.getName() + ")");
313         // do conversion
314         Rational[] components;
315         components = (Rational[]) src;
316         int deg = components[0].intValue();
317         double min = components[1].doubleValue();
318         double sec = components[2].doubleValue();
319         Double d = (deg + min / 60 + sec / 3600);
320         if (d > 0.0 && mustInvert)
321             d = -d;
322         res = d;
323         return res;
324     }
325 }