001    /*
002     * The MIT License
003     * Copyright (c) 2012 Microsoft Corporation
004     *
005     * Permission is hereby granted, free of charge, to any person obtaining a copy
006     * of this software and associated documentation files (the "Software"), to deal
007     * in the Software without restriction, including without limitation the rights
008     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009     * copies of the Software, and to permit persons to whom the Software is
010     * furnished to do so, subject to the following conditions:
011     *
012     * The above copyright notice and this permission notice shall be included in
013     * all copies or substantial portions of the Software.
014     *
015     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021     * THE SOFTWARE.
022     */
023    
024    package microsoft.exchange.webservices.data.misc;
025    
026    import microsoft.exchange.webservices.data.core.EwsUtilities;
027    import microsoft.exchange.webservices.data.core.ILazyMember;
028    import microsoft.exchange.webservices.data.core.LazyMember;
029    import microsoft.exchange.webservices.data.core.enumeration.property.MapiPropertyType;
030    import microsoft.exchange.webservices.data.core.exception.misc.FormatException;
031    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
032    import org.apache.commons.logging.Log;
033    import org.apache.commons.logging.LogFactory;
034    
035    import java.text.DateFormat;
036    import java.text.ParseException;
037    import java.text.SimpleDateFormat;
038    import java.util.ArrayList;
039    import java.util.Date;
040    import java.util.Iterator;
041    import java.util.List;
042    import java.util.Map;
043    import java.util.UUID;
044    
045    /**
046     * Utility class to convert between MAPI Property type values and strings.
047     */
048    public class MapiTypeConverter {
049    
050      private static final Log LOG = LogFactory.getLog(MapiTypeConverter.class);
051    
052      private static final IFunction<String, Object> DATE_TIME_PARSER = new IFunction<String, Object>() {
053        public Object func(final String s) {
054          return parseDateTime(s);
055        }
056      };
057    
058      private static final IFunction<String, Object> MAPI_VALUE_PARSER = new IFunction<String, Object>() {
059        public Object func(final String s) {
060          return MapiTypeConverter.parseMapiIntegerValue(s);
061        }
062      };
063    
064      /**
065       * The mapi type converter map.
066       */
067      private static final LazyMember<MapiTypeConverterMap> MAPI_TYPE_CONVERTER_MAP =
068          new LazyMember<MapiTypeConverterMap>(new ILazyMember<MapiTypeConverterMap>() {
069             @Override
070             public MapiTypeConverterMap createInstance() {
071               MapiTypeConverterMap map = new MapiTypeConverterMap();
072    
073               map.put(MapiPropertyType.ApplicationTime, new MapiTypeConverterMapEntry(Double.class));
074    
075               MapiTypeConverterMapEntry mapitype = new MapiTypeConverterMapEntry(Double.class);
076               mapitype.setIsArray(true);
077               map.put(MapiPropertyType.ApplicationTimeArray, mapitype);
078    
079               mapitype = new MapiTypeConverterMapEntry(Byte[].class);
080               mapitype.setParse(IFunctions.Base64Decoder.INSTANCE);
081               mapitype.setConvertToString(IFunctions.Base64Encoder.INSTANCE);
082               map.put(MapiPropertyType.Binary, mapitype);
083    
084               mapitype = new MapiTypeConverterMapEntry(Byte[].class);
085               mapitype.setParse(IFunctions.Base64Decoder.INSTANCE);
086               mapitype.setConvertToString(IFunctions.Base64Encoder.INSTANCE);
087               mapitype.setIsArray(true);
088               map.put(MapiPropertyType.BinaryArray, mapitype);
089    
090               mapitype = new MapiTypeConverterMapEntry(Boolean.class);
091               mapitype.setParse(IFunctions.ToBoolean.INSTANCE);
092               mapitype.setConvertToString(IFunctions.ToLowerCase.INSTANCE);
093               map.put(MapiPropertyType.Boolean, mapitype);
094    
095               mapitype = new MapiTypeConverterMapEntry(UUID.class);
096               mapitype.setParse(IFunctions.ToUUID.INSTANCE);
097               mapitype.setConvertToString(IFunctions.ToString.INSTANCE);
098               map.put(MapiPropertyType.CLSID, mapitype);
099    
100               mapitype = new MapiTypeConverterMapEntry(UUID.class);
101               mapitype.setParse(IFunctions.ToUUID.INSTANCE);
102               mapitype.setConvertToString(IFunctions.ToString.INSTANCE);
103               mapitype.setIsArray(true);
104               map.put(MapiPropertyType.CLSIDArray, mapitype);
105    
106               map.put(MapiPropertyType.Currency, new MapiTypeConverterMapEntry(Long.class));
107    
108               mapitype = new MapiTypeConverterMapEntry(Long.class);
109               mapitype.setIsArray(true);
110               map.put(MapiPropertyType.CurrencyArray, mapitype);
111    
112               map.put(MapiPropertyType.Double, new MapiTypeConverterMapEntry(Double.class));
113    
114               mapitype = new MapiTypeConverterMapEntry(Double.class);
115               mapitype.setIsArray(true);
116               map.put(MapiPropertyType.DoubleArray, mapitype);
117    
118               map.put(MapiPropertyType.Error, new MapiTypeConverterMapEntry(Integer.class));
119               map.put(MapiPropertyType.Float, new MapiTypeConverterMapEntry(Float.class));
120    
121               mapitype = new MapiTypeConverterMapEntry(Float.class);
122               mapitype.setIsArray(true);
123               map.put(MapiPropertyType.FloatArray, mapitype);
124    
125               mapitype = new MapiTypeConverterMapEntry(Integer.class);
126               mapitype.setParse(MAPI_VALUE_PARSER);
127               map.put(MapiPropertyType.Integer, mapitype);
128    
129               mapitype = new MapiTypeConverterMapEntry(Integer.class);
130               mapitype.setIsArray(true);
131               map.put(MapiPropertyType.IntegerArray, mapitype);
132    
133               map.put(MapiPropertyType.Long, new MapiTypeConverterMapEntry(Long.class));
134    
135               mapitype = new MapiTypeConverterMapEntry(Long.class);
136               mapitype.setIsArray(true);
137               map.put(MapiPropertyType.LongArray, mapitype);
138    
139               mapitype = new MapiTypeConverterMapEntry(String.class);
140               mapitype.setParse(IFunctions.StringToObject.INSTANCE);
141               map.put(MapiPropertyType.Object, mapitype);
142    
143               mapitype = new MapiTypeConverterMapEntry(String.class);
144               mapitype.setParse(IFunctions.StringToObject.INSTANCE);
145               mapitype.setIsArray(true);
146               map.put(MapiPropertyType.ObjectArray, mapitype);
147    
148               map.put(MapiPropertyType.Short, new MapiTypeConverterMapEntry(Short.class));
149    
150               mapitype = new MapiTypeConverterMapEntry(Short.class);
151               mapitype.setIsArray(true);
152               map.put(MapiPropertyType.ShortArray, mapitype);
153    
154               mapitype = new MapiTypeConverterMapEntry(String.class);
155               mapitype.setParse(IFunctions.StringToObject.INSTANCE);
156               map.put(MapiPropertyType.String, mapitype);
157    
158               mapitype = new MapiTypeConverterMapEntry(String.class);
159               mapitype.setParse(IFunctions.StringToObject.INSTANCE);
160               mapitype.setIsArray(true);
161               map.put(MapiPropertyType.StringArray, mapitype);
162    
163               mapitype = new MapiTypeConverterMapEntry(Date.class);
164               mapitype.setParse(DATE_TIME_PARSER);
165               mapitype.setConvertToString(IFunctions.DateTimeToXSDateTime.INSTANCE);
166               map.put(MapiPropertyType.SystemTime, mapitype);
167    
168               mapitype = new MapiTypeConverterMapEntry(Date.class);
169               mapitype.setParse(DATE_TIME_PARSER);
170               mapitype.setConvertToString(IFunctions.DateTimeToXSDateTime.INSTANCE);
171               mapitype.setIsArray(true);
172               map.put(MapiPropertyType.SystemTimeArray, mapitype);
173    
174               return map;
175             }
176      });
177    
178    
179      /**
180       * Converts the string list to array.
181       *
182       * @param mapiPropType Type of the MAPI property.
183       * @param strings      the strings
184       * @return Array of objects.
185       * @throws Exception the exception
186       */
187      public static List<Object> convertToValue(MapiPropertyType mapiPropType, Iterator<String> strings) throws Exception {
188        EwsUtilities.validateParam(strings, "strings");
189    
190        MapiTypeConverterMapEntry typeConverter = getMapiTypeConverterMap()
191            .get(mapiPropType);
192        List<Object> array = new ArrayList<Object>();
193    
194        int index = 0;
195    
196        while (strings.hasNext()) {
197          Object value = typeConverter.convertToValueOrDefault(strings.next());
198          array.add(index, value);
199        }
200        return array;
201      }
202    
203      /**
204       * Converts a string to value consistent with MAPI type.
205       *
206       * @param mapiPropType the mapi prop type
207       * @param stringValue  the string value
208       * @return the object
209       * @throws ServiceXmlDeserializationException                  the service xml deserialization exception
210       * @throws FormatException the format exception
211       */
212      public static Object convertToValue(MapiPropertyType mapiPropType, String stringValue) throws ServiceXmlDeserializationException, FormatException {
213        return getMapiTypeConverterMap().get(mapiPropType).convertToValue(
214            stringValue);
215    
216      }
217    
218      /**
219       * Converts a value to a string.
220       *
221       * @param mapiPropType the mapi prop type
222       * @param value        the value
223       * @return String value.
224       */
225      public static String convertToString(MapiPropertyType mapiPropType, Object value) {
226                    /*
227                     * if(! (value instanceof FuncInterface<?,?>)){ return null; }
228                     */
229        return (value == null) ? "" : getMapiTypeConverterMap().get(
230            mapiPropType).getConvertToString().func(value);
231      }
232    
233      /**
234       * Change value to a value of compatible type.
235       *
236       * @param mapiType the mapi type
237       * @param value    the value
238       * @return the object
239       * @throws Exception the exception
240       */
241      public static Object changeType(MapiPropertyType mapiType, Object value)
242          throws Exception {
243        EwsUtilities.validateParam(value, "value");
244    
245        return getMapiTypeConverterMap().get(mapiType).changeType(value);
246      }
247    
248      /**
249       * Converts a MAPI Integer value.
250       * Usually the value is an integer but there are cases where the value has been "schematized" to an
251       * Enumeration value (e.g. NoData) which we have no choice but to fallback and represent as a string.
252       *
253       * @param s The string value.
254       * @return Integer value or the original string if the value could not be parsed as such.
255       */
256      protected static Object parseMapiIntegerValue(String s) {
257        int intValue;
258        try {
259          intValue = Integer.parseInt(s.trim());
260          return Integer.valueOf(intValue);
261        } catch (NumberFormatException e) {
262          return s;
263        }
264      }
265    
266      /**
267       * Determines whether MapiPropertyType is an array type.
268       *
269       * @param mapiType the mapi type
270       * @return true, if is array type
271       */
272      public static boolean isArrayType(MapiPropertyType mapiType) {
273        return getMapiTypeConverterMap().get(mapiType).getIsArray();
274      }
275    
276      /**
277       * Gets the MAPI type converter map.
278       *
279       * @return the mapi type converter map
280       */
281      public static Map<MapiPropertyType, MapiTypeConverterMapEntry>
282      getMapiTypeConverterMap() {
283    
284        return MAPI_TYPE_CONVERTER_MAP.getMember();
285      }
286    
287    
288      private static Object parseDateTime(String s) {
289        String utcPattern = "yyyy-MM-dd'T'HH:mm:ss'Z'";
290        String errMsg = String.format("Date String %s not in " + "valid UTC/local format", s);
291        DateFormat utcFormatter = new SimpleDateFormat(utcPattern);
292        Date dt;
293    
294        if (s.endsWith("Z")) {
295          try {
296            dt = utcFormatter.parse(s);
297          } catch (ParseException e) {
298            s = s.substring(0, 10) + "T12:00:00Z";
299            try {
300              dt = utcFormatter.parse(s);
301            } catch (ParseException e1) {
302              LOG.error(e);
303              throw new IllegalArgumentException(
304                  errMsg, e);
305            }
306          }
307        } else if (s.endsWith("z")) {
308          // String in UTC format yyyy-MM-ddTHH:mm:ssZ
309          utcFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'z'");
310          try {
311            dt = utcFormatter.parse(s);
312          } catch (ParseException e) {
313            throw new IllegalArgumentException(e);
314          }
315        } else {
316          utcFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
317          try {
318            dt = utcFormatter.parse(s);
319          } catch (ParseException e) {
320            throw new IllegalArgumentException(e);
321          }
322        }
323        return dt;
324      }
325    
326    }