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
024package microsoft.exchange.webservices.data.misc;
025
026import microsoft.exchange.webservices.data.core.EwsUtilities;
027import microsoft.exchange.webservices.data.core.ILazyMember;
028import microsoft.exchange.webservices.data.core.LazyMember;
029import microsoft.exchange.webservices.data.core.enumeration.property.MapiPropertyType;
030import microsoft.exchange.webservices.data.core.exception.misc.FormatException;
031import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034
035import java.text.DateFormat;
036import java.text.ParseException;
037import java.text.SimpleDateFormat;
038import java.util.ArrayList;
039import java.util.Date;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Map;
043import java.util.UUID;
044
045/**
046 * Utility class to convert between MAPI Property type values and strings.
047 */
048public 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}