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.core;
025
026import microsoft.exchange.webservices.data.ISelfValidate;
027import microsoft.exchange.webservices.data.attribute.EwsEnum;
028import microsoft.exchange.webservices.data.attribute.RequiredServerVersion;
029import microsoft.exchange.webservices.data.core.request.HttpWebRequest;
030import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithAttachmentParam;
031import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithServiceParam;
032import microsoft.exchange.webservices.data.core.service.ServiceObject;
033import microsoft.exchange.webservices.data.core.service.ServiceObjectInfo;
034import microsoft.exchange.webservices.data.core.service.item.Item;
035import microsoft.exchange.webservices.data.core.enumeration.notification.EventType;
036import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
037import microsoft.exchange.webservices.data.core.enumeration.service.FileAsMapping;
038import microsoft.exchange.webservices.data.core.enumeration.search.ItemTraversal;
039import microsoft.exchange.webservices.data.core.enumeration.property.MailboxType;
040import microsoft.exchange.webservices.data.core.enumeration.service.MeetingRequestsDeliveryScope;
041import microsoft.exchange.webservices.data.core.enumeration.property.RuleProperty;
042import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName;
043import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
044import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
045import microsoft.exchange.webservices.data.core.exception.misc.ArgumentNullException;
046import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
047import microsoft.exchange.webservices.data.core.exception.misc.FormatException;
048import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
049import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
050import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
051import microsoft.exchange.webservices.data.misc.TimeSpan;
052import microsoft.exchange.webservices.data.property.complex.ItemAttachment;
053
054import org.apache.commons.logging.Log;
055import org.apache.commons.logging.LogFactory;
056import org.joda.time.Period;
057import org.joda.time.format.ISOPeriodFormat;
058
059import javax.xml.stream.XMLOutputFactory;
060import javax.xml.stream.XMLStreamException;
061import javax.xml.stream.XMLStreamWriter;
062
063import java.io.ByteArrayOutputStream;
064import java.io.IOException;
065import java.lang.reflect.Field;
066import java.math.BigDecimal;
067import java.math.BigInteger;
068import java.net.URISyntaxException;
069import java.text.DateFormat;
070import java.text.DecimalFormat;
071import java.text.ParseException;
072import java.text.SimpleDateFormat;
073import java.util.Date;
074import java.util.HashMap;
075import java.util.Iterator;
076import java.util.List;
077import java.util.Map;
078import java.util.TimeZone;
079import java.util.regex.Matcher;
080import java.util.regex.Pattern;
081
082/**
083 * EWS utilities.
084 */
085public final class EwsUtilities {
086
087  private static final Log LOG = LogFactory.getLog(EwsUtilities.class);
088
089  /**
090   * The Constant XSFalse.
091   */
092  public static final String XSFalse = "false";
093
094  /**
095   * The Constant XSTrue.
096   */
097  public static final String XSTrue = "true";
098
099  /**
100   * The Constant EwsTypesNamespacePrefix.
101   */
102  public static final String EwsTypesNamespacePrefix = "t";
103
104  /**
105   * The Constant EwsMessagesNamespacePrefix.
106   */
107  public static final String EwsMessagesNamespacePrefix = "m";
108
109  /**
110   * The Constant EwsErrorsNamespacePrefix.
111   */
112  public static final String EwsErrorsNamespacePrefix = "e";
113
114  /**
115   * The Constant EwsSoapNamespacePrefix.
116   */
117  public static final String EwsSoapNamespacePrefix = "soap";
118
119  /**
120   * The Constant EwsXmlSchemaInstanceNamespacePrefix.
121   */
122  public static final String EwsXmlSchemaInstanceNamespacePrefix = "xsi";
123
124  /**
125   * The Constant PassportSoapFaultNamespacePrefix.
126   */
127  public static final String PassportSoapFaultNamespacePrefix = "psf";
128
129  /**
130   * The Constant WSTrustFebruary2005NamespacePrefix.
131   */
132  public static final String WSTrustFebruary2005NamespacePrefix = "wst";
133
134  /**
135   * The Constant WSAddressingNamespacePrefix.
136   */
137  public static final String WSAddressingNamespacePrefix = "wsa";
138
139  /**
140   * The Constant AutodiscoverSoapNamespacePrefix.
141   */
142  public static final String AutodiscoverSoapNamespacePrefix = "a";
143
144  /**
145   * The Constant WSSecurityUtilityNamespacePrefix.
146   */
147  public static final String WSSecurityUtilityNamespacePrefix = "wsu";
148
149  /**
150   * The Constant WSSecuritySecExtNamespacePrefix.
151   */
152  public static final String WSSecuritySecExtNamespacePrefix = "wsse";
153
154  /**
155   * The Constant EwsTypesNamespace.
156   */
157  public static final String EwsTypesNamespace =
158      "http://schemas.microsoft.com/exchange/services/2006/types";
159
160  /**
161   * The Constant EwsMessagesNamespace.
162   */
163  public static final String EwsMessagesNamespace =
164      "http://schemas.microsoft.com/exchange/services/2006/messages";
165
166  /**
167   * The Constant EwsErrorsNamespace.
168   */
169  public static final String EwsErrorsNamespace =
170      "http://schemas.microsoft.com/exchange/services/2006/errors";
171
172  /**
173   * The Constant EwsSoapNamespace.
174   */
175  public static final String EwsSoapNamespace =
176      "http://schemas.xmlsoap.org/soap/envelope/";
177
178  /**
179   * The Constant EwsSoap12Namespace.
180   */
181  public static final String EwsSoap12Namespace =
182      "http://www.w3.org/2003/05/soap-envelope";
183
184  /**
185   * The Constant EwsXmlSchemaInstanceNamespace.
186   */
187  public static final String EwsXmlSchemaInstanceNamespace =
188      "http://www.w3.org/2001/XMLSchema-instance";
189
190  /**
191   * The Constant PassportSoapFaultNamespace.
192   */
193  public static final String PassportSoapFaultNamespace =
194      "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault";
195
196  /**
197   * The Constant WSTrustFebruary2005Namespace.
198   */
199  public static final String WSTrustFebruary2005Namespace =
200      "http://schemas.xmlsoap.org/ws/2005/02/trust";
201
202  /**
203   * The Constant WSAddressingNamespace.
204   */
205  public static final String WSAddressingNamespace =
206      "http://www.w3.org/2005/08/addressing";
207  // "http://schemas.xmlsoap.org/ws/2004/08/addressing";
208
209  /**
210   * The Constant AutodiscoverSoapNamespace.
211   */
212  public static final String AutodiscoverSoapNamespace =
213      "http://schemas.microsoft.com/exchange/2010/Autodiscover";
214
215  public static final String WSSecurityUtilityNamespace =
216      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
217  public static final String WSSecuritySecExtNamespace =
218      "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
219
220  /**
221   * The service object info.
222   */
223  private static final LazyMember<ServiceObjectInfo> SERVICE_OBJECT_INFO =
224      new LazyMember<ServiceObjectInfo>(
225        new ILazyMember<ServiceObjectInfo>() {
226          public ServiceObjectInfo createInstance() {
227            return new ServiceObjectInfo();
228          }
229        }
230      );
231
232  private static final String XML_SCHEMA_DATE_FORMAT = "yyyy-MM-dd'Z'";
233  private static final String XML_SCHEMA_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
234
235  private static final Pattern PATTERN_TIME_SPAN = Pattern.compile("-P");
236  private static final Pattern PATTERN_YEAR = Pattern.compile("(\\d+)Y");
237  private static final Pattern PATTERN_MONTH = Pattern.compile("(\\d+)M");
238  private static final Pattern PATTERN_DAY = Pattern.compile("(\\d+)D");
239  private static final Pattern PATTERN_HOUR = Pattern.compile("(\\d+)H");
240  private static final Pattern PATTERN_MINUTES = Pattern.compile("(\\d+)M");
241  private static final Pattern PATTERN_SECONDS = Pattern.compile("(\\d+)\\."); // Need to escape dot, otherwise it matches any char
242  private static final Pattern PATTERN_MILLISECONDS = Pattern.compile("(\\d+)S");
243
244
245  private EwsUtilities() {
246    throw new UnsupportedOperationException();
247  }
248
249
250  /**
251   * Gets the builds the version.
252   *
253   * @return the builds the version
254   */
255  public static String getBuildVersion() {
256    return "0.0.0.0";
257  }
258
259  /**
260   * The enum version dictionaries.
261   */
262  private static final LazyMember<Map<Class<?>, Map<String, ExchangeVersion>>>
263      ENUM_VERSION_DICTIONARIES =
264      new LazyMember<Map<Class<?>, Map<String, ExchangeVersion>>>(
265          new ILazyMember<Map<Class<?>, Map<String, ExchangeVersion>>>() {
266            @Override
267            public Map<Class<?>, Map<String, ExchangeVersion>>
268            createInstance() {
269              Map<Class<?>, Map<String, ExchangeVersion>> enumDicts =
270                  new HashMap<Class<?>, Map<String,
271                      ExchangeVersion>>();
272              enumDicts.put(WellKnownFolderName.class,
273                  buildEnumDict(WellKnownFolderName.class));
274              enumDicts.put(ItemTraversal.class,
275                  buildEnumDict(ItemTraversal.class));
276              enumDicts.put(FileAsMapping.class,
277                  buildEnumDict(FileAsMapping.class));
278              enumDicts.put(EventType.class,
279                  buildEnumDict(EventType.class));
280              enumDicts.put(MeetingRequestsDeliveryScope.class,
281                  buildEnumDict(MeetingRequestsDeliveryScope.
282                      class));
283              return enumDicts;
284            }
285          });
286  /**
287   * Dictionary of enum type to schema-name-to-enum-value maps.
288   */
289  private static final LazyMember<Map<Class<?>, Map<String, String>>>
290      SCHEMA_TO_ENUM_DICTIONARIES =
291      new LazyMember<Map<Class<?>, Map<String, String>>>(
292          new ILazyMember<Map<Class<?>, Map<String, String>>>() {
293            @Override
294            public Map<Class<?>, Map<String, String>> createInstance() {
295              Map<Class<?>, Map<String, String>> enumDicts =
296                  new HashMap<Class<?>, Map<String, String>>();
297              enumDicts.put(EventType.class,
298                  buildSchemaToEnumDict(EventType.class));
299              enumDicts.put(MailboxType.class,
300                  buildSchemaToEnumDict(MailboxType.class));
301              enumDicts.put(FileAsMapping.class,
302                  buildSchemaToEnumDict(FileAsMapping.class));
303              enumDicts.put(RuleProperty.class,
304                  buildSchemaToEnumDict(RuleProperty.class));
305              return enumDicts;
306
307            }
308          });
309
310  /**
311   * Dictionary of enum type to enum-value-to-schema-name maps.
312   */
313  public static final LazyMember<Map<Class<?>, Map<String, String>>>
314      ENUM_TO_SCHEMA_DICTIONARIES =
315      new LazyMember<Map<Class<?>, Map<String, String>>>(
316          new ILazyMember<Map<Class<?>, Map<String, String>>>() {
317            @Override
318            public Map<Class<?>, Map<String, String>> createInstance() {
319              Map<Class<?>, Map<String, String>> enumDicts =
320                  new HashMap<Class<?>, Map<String, String>>();
321              enumDicts.put(EventType.class,
322                  buildEnumToSchemaDict(EventType.class));
323              enumDicts.put(MailboxType.class,
324                  buildEnumToSchemaDict(MailboxType.class));
325              enumDicts.put(FileAsMapping.class,
326                  buildEnumToSchemaDict(FileAsMapping.class));
327              enumDicts.put(RuleProperty.class,
328                  buildEnumToSchemaDict(RuleProperty.class));
329              return enumDicts;
330            }
331          });
332
333  /**
334   * Regular expression for legal domain names.
335   */
336  public static final String DomainRegex = "^[-a-zA-Z0-9_.]+$";
337
338  /**
339   * Asserts that the specified condition if true.
340   *
341   * @param condition Assertion.
342   * @param caller    The caller.
343   * @param message   The message to use if assertion fails.
344   */
345  public static void ewsAssert(
346    final boolean condition, final String caller, final String message
347  ) {
348    if (!condition) {
349      throw new RuntimeException(String.format("[%s] %s", caller, message));
350    }
351  }
352
353  /**
354   * Gets the namespace prefix from an XmlNamespace enum value.
355   *
356   * @param xmlNamespace The XML namespace
357   * @return Namespace prefix string.
358   */
359  public static String getNamespacePrefix(XmlNamespace xmlNamespace) {
360    return xmlNamespace.getNameSpacePrefix();
361  }
362
363  /**
364   * Gets the namespace URI from an XmlNamespace enum value.
365   *
366   * @param xmlNamespace The XML namespace.
367   * @return Uri as string
368   */
369  public static String getNamespaceUri(XmlNamespace xmlNamespace) {
370    return xmlNamespace.getNameSpaceUri();
371  }
372
373  /**
374   * Gets the namespace from uri.
375   *
376   * @param namespaceUri the namespace uri
377   * @return the namespace from uri
378   */
379  public static XmlNamespace getNamespaceFromUri(String namespaceUri) {
380    if (EwsErrorsNamespace.equals(namespaceUri)) {
381      return XmlNamespace.Errors;
382    } else if (EwsTypesNamespace.equals(namespaceUri)) {
383      return XmlNamespace.Types;
384    } else if (EwsMessagesNamespace.equals(namespaceUri)) {
385      return XmlNamespace.Messages;
386    } else if (EwsSoapNamespace.equals(namespaceUri)) {
387      return XmlNamespace.Soap;
388    } else if (EwsSoap12Namespace.equals(namespaceUri)) {
389      return XmlNamespace.Soap12;
390    } else if (EwsXmlSchemaInstanceNamespace.equals(namespaceUri)) {
391      return XmlNamespace.XmlSchemaInstance;
392    } else if (PassportSoapFaultNamespace.equals(namespaceUri)) {
393      return XmlNamespace.PassportSoapFault;
394    } else if (WSTrustFebruary2005Namespace.equals(namespaceUri)) {
395      return XmlNamespace.WSTrustFebruary2005;
396    } else if (WSAddressingNamespace.equals(namespaceUri)) {
397      return XmlNamespace.WSAddressing;
398    } else {
399      return XmlNamespace.NotSpecified;
400    }
401  }
402
403  /**
404   * Creates the ews object from xml element name.
405   *
406   * @param <TServiceObject> the generic type
407   * @param itemClass        the item class
408   * @param service          the service
409   * @param xmlElementName   the xml element name
410   * @return the t service object
411   * @throws Exception the exception
412   */
413  @SuppressWarnings("unchecked")
414  public static <TServiceObject extends ServiceObject>
415  TServiceObject createEwsObjectFromXmlElementName(
416      Class<?> itemClass, ExchangeService service, String xmlElementName)
417      throws Exception {
418    final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember();
419    final Map<String, Class<?>> map = member.getXmlElementNameToServiceObjectClassMap();
420
421    final Class<?> ic = map.get(xmlElementName);
422    if (ic != null) {
423      final Map<Class<?>, ICreateServiceObjectWithServiceParam>
424          serviceParam = member.getServiceObjectConstructorsWithServiceParam();
425      final ICreateServiceObjectWithServiceParam creationDelegate =
426          serviceParam.get(ic);
427
428      if (creationDelegate != null) {
429        return (TServiceObject) creationDelegate
430            .createServiceObjectWithServiceParam(service);
431      } else {
432        throw new IllegalArgumentException("No appropriate constructor could be found for this item class.");
433      }
434    }
435
436    return (TServiceObject) itemClass.newInstance();
437  }
438
439  /**
440   * Creates the item from item class.
441   *
442   * @param itemAttachment the item attachment
443   * @param itemClass      the item class
444   * @param isNew          the is new
445   * @return the item
446   * @throws Exception the exception
447   */
448  public static Item createItemFromItemClass(
449      ItemAttachment itemAttachment, Class<?> itemClass, boolean isNew)
450      throws Exception {
451    final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember();
452    final Map<Class<?>, ICreateServiceObjectWithAttachmentParam>
453      dataMap = member.getServiceObjectConstructorsWithAttachmentParam();
454    final ICreateServiceObjectWithAttachmentParam creationDelegate =
455      dataMap.get(itemClass);
456
457    if (creationDelegate != null) {
458      return (Item) creationDelegate
459          .createServiceObjectWithAttachmentParam(itemAttachment, isNew);
460    }
461    throw new IllegalArgumentException("No appropriate constructor could be found for this item class.");
462  }
463
464  /**
465   * Creates the item from xml element name.
466   *
467   * @param itemAttachment the item attachment
468   * @param xmlElementName the xml element name
469   * @return the item
470   * @throws Exception the exception
471   */
472  public static Item createItemFromXmlElementName(
473      ItemAttachment itemAttachment, String xmlElementName)
474      throws Exception {
475    final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember();
476    final Map<String, Class<?>> map =
477      member.getXmlElementNameToServiceObjectClassMap();
478
479    final Class<?> itemClass = map.get(xmlElementName);
480    if (itemClass != null) {
481      return createItemFromItemClass(itemAttachment, itemClass, false);
482    }
483    return null;
484  }
485
486  public static Class<?> getItemTypeFromXmlElementName(String xmlElementName) {
487    final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember();
488    final Map<String, Class<?>> map = member.getXmlElementNameToServiceObjectClassMap();
489    return map.get(xmlElementName);
490  }
491
492  /**
493   * Finds the first item of type TItem (not a descendant type) in the
494   * specified collection.
495   *
496   * @param <TItem> TItem is the type of the item to find.
497   * @param cls     the cls
498   * @param items   the item
499   * @return A TItem instance or null if no instance of TItem could be found.
500   */
501  @SuppressWarnings("unchecked")
502  public static <TItem extends Item> TItem findFirstItemOfType(
503    Class<TItem> cls, Iterable<Item> items
504  ) {
505    for (Item item : items) {
506      // We're looking for an exact class match here.
507      final Class<? extends Item> itemClass = item.getClass();
508      if (itemClass.equals(cls)) {
509        return (TItem) item;
510      }
511    }
512
513    return null;
514  }
515
516  /**
517   * Write trace start element.
518   *
519   * @param writer         the writer to write the start element to
520   * @param traceTag       the trace tag
521   * @param includeVersion if true, include build version attribute
522   * @throws XMLStreamException the XML stream exception
523   */
524  private static void writeTraceStartElement(
525      XMLStreamWriter writer,
526      String traceTag,
527      boolean includeVersion) throws XMLStreamException {
528    writer.writeStartElement("Trace");
529    writer.writeAttribute("Tag", traceTag);
530    writer.writeAttribute("Tid", Thread.currentThread().getId() + "");
531    Date d = new Date();
532    DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'");
533    df.setTimeZone(TimeZone.getTimeZone("UTC"));
534    String formattedString = df.format(d);
535    writer.writeAttribute("Time", formattedString);
536
537    if (includeVersion) {
538      writer.writeAttribute("Version", EwsUtilities.getBuildVersion());
539    }
540  }
541
542  /**
543   * .
544   *
545   * @param entryKind the entry kind
546   * @param logEntry  the log entry
547   * @return the string
548   * @throws XMLStreamException the XML stream exception
549   * @throws IOException signals that an I/O exception has occurred.
550   */
551  public static String formatLogMessage(String entryKind, String logEntry)
552      throws XMLStreamException, IOException {
553    String lineSeparator = System.getProperty("line.separator");
554    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
555    XMLOutputFactory factory = XMLOutputFactory.newInstance();
556    XMLStreamWriter writer = factory.createXMLStreamWriter(outStream);
557    EwsUtilities.writeTraceStartElement(writer, entryKind, false);
558    writer.writeCharacters(lineSeparator);
559    writer.writeCharacters(logEntry);
560    writer.writeCharacters(lineSeparator);
561    writer.writeEndElement();
562    writer.writeCharacters(lineSeparator);
563    writer.flush();
564    writer.close();
565    outStream.flush();
566    String formattedLogMessage = outStream.toString();
567    formattedLogMessage = formattedLogMessage.replaceAll("&apos;", "'");
568    formattedLogMessage = formattedLogMessage.replaceAll("&quot;", "\"");
569    formattedLogMessage = formattedLogMessage.replaceAll("&gt;", ">");
570    formattedLogMessage = formattedLogMessage.replaceAll("&lt;", "<");
571    formattedLogMessage = formattedLogMessage.replaceAll("&amp;", "&");
572    outStream.close();
573    return formattedLogMessage;
574  }
575
576  /**
577   * Format http response headers.
578   *
579   * @param response the response
580   * @return the string
581   * @throws EWSHttpException the EWS http exception
582   */
583  public static String formatHttpResponseHeaders(HttpWebRequest response)
584      throws EWSHttpException {
585    final int code = response.getResponseCode();
586    final String contentType = response.getResponseContentType();
587    final Map<String, String> headers = response.getResponseHeaders();
588
589    return code + " " + contentType + "\n"
590       + EwsUtilities.formatHttpHeaders(headers) + "\n";
591  }
592
593  /**
594   * Format request HTTP headers.
595   *
596   * @param request The HTTP request.
597   */
598  public static String formatHttpRequestHeaders(HttpWebRequest request)
599      throws URISyntaxException, EWSHttpException {
600    final String method = request.getRequestMethod().toUpperCase();
601    final String path = request.getUrl().toURI().getPath();
602    final Map<String, String> property = request.getRequestProperty();
603    final String headers = EwsUtilities.formatHttpHeaders(property);
604
605    return String.format("%s %s HTTP/%s\n", method, path, "1.1") + headers + "\n";
606  }
607
608  /**
609   * Formats HTTP headers.
610   *
611   * @param headers The headers.
612   * @return Headers as a string
613   */
614  private static String formatHttpHeaders(Map<String, String> headers) {
615    StringBuilder sb = new StringBuilder();
616    for (Map.Entry<String, String> header : headers.entrySet()) {
617      sb.append(String.format("%s : %s\n", header.getKey(), header.getValue()));
618    }
619    return sb.toString();
620  }
621
622  /**
623   * Format XML content in a MemoryStream for message.
624   *
625   * @param traceTypeStr Kind of the entry.
626   * @param stream       The memory stream.
627   * @return XML log entry as a string.
628   */
629  public static String formatLogMessageWithXmlContent(String traceTypeStr,
630      ByteArrayOutputStream stream) {
631    try {
632      return formatLogMessage(traceTypeStr, stream.toString());
633    } catch (Exception e) {
634      return stream.toString();
635    }
636  }
637
638  /**
639   * Convert bool to XML Schema bool.
640   *
641   * @param value Bool value.
642   * @return String representing bool value in XML Schema.
643   */
644  public static String boolToXSBool(Boolean value) {
645    return value ? EwsUtilities.XSTrue : EwsUtilities.XSFalse;
646  }
647
648  /**
649   * Parses an enum value list.
650   *
651   * @param <T>        the generic type
652   * @param c          the c
653   * @param list       the list
654   * @param value      the value
655   * @param separators the separators
656   */
657  public static <T extends Enum<?>> void parseEnumValueList(Class<T> c,
658      List<T> list, String value, char... separators) {
659    EwsUtilities.ewsAssert(c.isEnum(), "EwsUtilities.ParseEnumValueList", "T is not an enum type.");
660
661    StringBuilder regexp = new StringBuilder();
662    regexp.append("[");
663    for (char s : separators) {
664      regexp.append("[");
665      regexp.append(Pattern.quote(s + ""));
666      regexp.append("]");
667    }
668    regexp.append("]");
669
670    String[] enumValues = value.split(regexp.toString());
671
672    for (String enumValue : enumValues) {
673      for (T o : c.getEnumConstants()) {
674        if (o.toString().equals(enumValue)) {
675          list.add(o);
676        }
677      }
678    }
679  }
680
681  /**
682   * Converts an enum to a string, using the mapping dictionaries if
683   * appropriate.
684   *
685   * @param value The enum value to be serialized
686   * @return String representation of enum to be used in the protocol
687   */
688  public static String serializeEnum(Object value) {
689    String strValue = value.toString();
690    final Map<Class<?>, Map<String, String>> member =
691      ENUM_TO_SCHEMA_DICTIONARIES.getMember();
692
693    final Map<String, String> enumToStringDict = member.get(value.getClass());
694    if (enumToStringDict != null) {
695      final Enum<?> e = (Enum<?>) value;
696      final String enumStr = enumToStringDict.get(e.name());
697      if (enumStr != null) {
698        strValue = enumStr;
699      }
700    }
701    return strValue;
702  }
703
704  /**
705   * Parses the.
706   *
707   * @param <T>   the generic type
708   * @param cls   the cls
709   * @param value the value
710   * @return the t
711   * @throws java.text.ParseException the parse exception
712   */
713  @SuppressWarnings("unchecked")
714  public static <T> T parse(Class<T> cls, String value) throws ParseException {
715    if (cls.isEnum()) {
716      final Map<Class<?>, Map<String, String>> member = SCHEMA_TO_ENUM_DICTIONARIES.getMember();
717
718      String val = value;
719      final Map<String, String> stringToEnumDict = member.get(cls);
720      if (stringToEnumDict != null) {
721        final String strEnumName = stringToEnumDict.get(value);
722        if (strEnumName != null) {
723          val = strEnumName;
724        }
725      }
726      for (T o : cls.getEnumConstants()) {
727        if (o.toString().equals(val)) {
728          return o;
729        }
730      }
731      return null;
732    }else if (Number.class.isAssignableFrom(cls)){
733      if (Double.class.isAssignableFrom(cls)){
734        return (T) ((Double) Double.parseDouble(value));
735      }else if (Integer.class.isAssignableFrom(cls)) {
736        return (T) ((Integer) Integer.parseInt(value));
737      }else if (Long.class.isAssignableFrom(cls)){
738        return (T) ((Long) Long.parseLong(value));
739      }else if (Float.class.isAssignableFrom(cls)){
740        return (T) ((Float) Float.parseFloat(value));
741      }else if (Byte.class.isAssignableFrom(cls)){
742        return (T) ((Byte) Byte.parseByte(value));
743      }else if (Short.class.isAssignableFrom(cls)){
744        return (T) ((Short) Short.parseShort(value));
745      }else if (BigInteger.class.isAssignableFrom(cls)){
746        return (T) (new BigInteger(value));
747      }else if (BigDecimal.class.isAssignableFrom(cls)){
748        return (T) (new BigDecimal(value));
749      }
750    } else if (Date.class.isAssignableFrom(cls)) {
751      DateFormat df = createDateFormat(XML_SCHEMA_DATE_TIME_FORMAT);
752      return (T) df.parse(value);
753    } else if (Boolean.class.isAssignableFrom(cls)) {
754      return (T) ((Boolean) Boolean.parseBoolean(value));
755    } else if (String.class.isAssignableFrom(cls)) {
756      return (T) value;
757    }
758    return null;
759  }
760
761
762
763  /**
764   * Builds the schema to enum mapping dictionary.
765   *
766   * @param <E> Type of the enum.
767   * @param c   Class
768   * @return The mapping from enum to schema name
769   */
770  private static <E extends Enum<E>> Map<String, String>
771  buildSchemaToEnumDict(Class<E> c) {
772    Map<String, String> dict = new HashMap<String, String>();
773
774    Field[] fields = c.getDeclaredFields();
775    for (Field f : fields) {
776      if (f.isEnumConstant() && f.isAnnotationPresent(EwsEnum.class)) {
777        EwsEnum ewsEnum = f.getAnnotation(EwsEnum.class);
778        String fieldName = f.getName();
779        String schemaName = ewsEnum.schemaName();
780        if (!schemaName.isEmpty()) {
781          dict.put(schemaName, fieldName);
782        }
783      }
784    }
785    return dict;
786  }
787
788  /**
789   * Validate param collection.
790   *
791   * @param eventTypes the event types
792   * @param paramName  the param name
793   * @throws Exception the exception
794   */
795  public static void validateParamCollection(EventType[] eventTypes,
796      String paramName) throws Exception {
797    validateParam(eventTypes, paramName);
798    int count = 0;
799
800    for (EventType event : eventTypes) {
801      try {
802        validateParam(event, String.format("collection[%d] , ", count));
803      } catch (Exception e) {
804        throw new IllegalArgumentException(String.format(
805            "The element at position %d is invalid", count), e);
806      }
807      count++;
808    }
809
810    if (count == 0) {
811      throw new IllegalArgumentException(
812        String.format("The collection \"%s\" is empty.", paramName)
813      );
814    }
815  }
816
817  /**
818   * Convert DateTime to XML Schema date.
819   *
820   * @param date the date
821   * @return String representation of DateTime.
822   */
823  public static String dateTimeToXSDate(Date date) {
824    return formatDate(date, XML_SCHEMA_DATE_FORMAT);
825  }
826
827  /**
828   * Dates the DateTime into an XML schema date time.
829   *
830   * @param date the date
831   * @return String representation of DateTime.
832   */
833  public static String dateTimeToXSDateTime(Date date) {
834    return formatDate(date, XML_SCHEMA_DATE_TIME_FORMAT);
835  }
836
837  /**
838   * Takes a System.TimeSpan structure and converts it into an xs:duration
839   * string as defined by the W3 Consortiums Recommendation
840   * "XML Schema Part 2: Datatypes Second Edition",
841   * http://www.w3.org/TR/xmlschema-2/#duration
842   *
843   * @param timeOffset structure to convert
844   * @return xs:duration formatted string
845   */
846  public static String getTimeSpanToXSDuration(TimeSpan timeOffset) {
847    // Optional '-' offset
848    String offsetStr = (timeOffset.getTotalSeconds() < 0) ? "-" : "";
849    long days = Math.abs(timeOffset.getDays());
850    long hours = Math.abs(timeOffset.getHours());
851    long minutes = Math.abs(timeOffset.getMinutes());
852    long seconds = Math.abs(timeOffset.getSeconds());
853    long milliseconds = Math.abs(timeOffset.getMilliseconds());
854
855    // The TimeSpan structure does not have a Year or Month
856    // property, therefore we wouldn't be able to return an xs:duration
857    // string from a TimeSpan that included the nY or nM components.
858    return offsetStr + "P" + days + "DT" + hours + "H" + minutes + "M"
859       + seconds + "." + milliseconds + "S";
860  }
861
862  /**
863   * Takes an xs:duration string as defined by the W3 Consortiums
864   * Recommendation "XML Schema Part 2: Datatypes Second Edition",
865   * http://www.w3.org/TR/xmlschema-2/#duration, and converts it into a
866   * System.TimeSpan structure This method uses the following approximations:
867   * 1 year = 365 days 1 month = 30 days Additionally, it only allows for four
868   * decimal points of seconds precision.
869   *
870   * @param xsDuration xs:duration string to convert
871   * @return System.TimeSpan structure
872   */
873  public static TimeSpan getXSDurationToTimeSpan(String xsDuration) {
874    // TODO: Need to check whether this should be the equivalent or not
875    Matcher m = PATTERN_TIME_SPAN.matcher(xsDuration);
876    boolean negative = false;
877    if (m.find()) {
878      negative = true;
879    }
880
881    // Removing leading '-'
882    if (negative) {
883      xsDuration = xsDuration.replace("-P", "P");
884    }
885
886    Period period = Period.parse(xsDuration, ISOPeriodFormat.standard());
887      
888    long retval = period.toStandardDuration().getMillis();
889    
890    if (negative) {
891      retval = -retval;
892    }
893
894    return new TimeSpan(retval);
895
896  }
897
898  /**
899   * Time span to xs time.
900   *
901   * @param timeSpan the time span
902   * @return the string
903   */
904  public static String timeSpanToXSTime(TimeSpan timeSpan) {
905    DecimalFormat myFormatter = new DecimalFormat("00");
906    return String.format("%s:%s:%s", myFormatter.format(timeSpan.getHours()), myFormatter.format(timeSpan
907        .getMinutes()), myFormatter.format(timeSpan.getSeconds()));
908  }
909
910  /**
911   * Gets the domain name from an email address.
912   *
913   * @param emailAddress The email address.
914   * @return Domain name.
915   * @throws FormatException the format exception
916   */
917  public static String domainFromEmailAddress(String emailAddress)
918      throws FormatException {
919    String[] emailAddressParts = emailAddress.split("@");
920
921    if (emailAddressParts.length != 2
922        || (emailAddressParts[1] == null || emailAddressParts[1]
923        .isEmpty())) {
924      throw new FormatException("The e-mail address is formed incorrectly.");
925    }
926
927    return emailAddressParts[1];
928  }
929
930  public static int getDim(Object array) {
931    int dim = 0;
932    Class<?> c = array.getClass();
933    while (c.isArray()) {
934      c = c.getComponentType();
935      dim++;
936    }
937    return (dim);
938  }
939
940  /**
941   * Validates parameter (and allows null value).
942   *
943   * @param param     The param.
944   * @param paramName Name of the param.
945   * @throws Exception the exception
946   */
947  public static void validateParamAllowNull(Object param, String paramName)
948      throws Exception {
949    if (param instanceof ISelfValidate) {
950      ISelfValidate selfValidate = (ISelfValidate) param;
951      try {
952        selfValidate.validate();
953      } catch (ServiceValidationException e) {
954        throw new Exception(String.format("%s %s", "Validation failed.", paramName), e);
955      }
956    }
957
958    if (param instanceof ServiceObject) {
959      ServiceObject ewsObject = (ServiceObject) param;
960      if (ewsObject.isNew()) {
961        throw new Exception(String.format("%s %s", "This service object doesn't have an ID.", paramName));
962      }
963    }
964  }
965
966  /**
967   * Validates parameter (null value not allowed).
968   *
969   * @param param     The param.
970   * @param paramName Name of the param.
971   * @throws Exception the exception
972   */
973  public static void validateParam(Object param, String paramName) throws Exception {
974    boolean isValid;
975
976    if (param instanceof String) {
977      String strParam = (String) param;
978      isValid = !strParam.isEmpty();
979    } else {
980      isValid = param != null;
981    }
982
983    if (!isValid) {
984      throw new Exception(String.format("Argument %s not valid",
985          paramName));
986    }
987    validateParamAllowNull(param, paramName);
988  }
989
990  /**
991   * Validates parameter collection.
992   *
993   * @param <T>        the generic type
994   * @param collection The collection.
995   * @param paramName  Name of the param.
996   * @throws Exception the exception
997   */
998  public static <T> void validateParamCollection(Iterator<T> collection, String paramName) throws Exception {
999    validateParam(collection, paramName);
1000    int count = 0;
1001
1002    while (collection.hasNext()) {
1003      T obj = collection.next();
1004      try {
1005        validateParam(obj, String.format("collection[%d],", count));
1006      } catch (Exception e) {
1007        throw new IllegalArgumentException(String.format(
1008            "The element at position %d is invalid", count), e);
1009      }
1010      count++;
1011    }
1012
1013    if (count == 0) {
1014      throw new IllegalArgumentException(
1015        String.format("The collection \"%s\" is empty.", paramName)
1016      );
1017    }
1018  }
1019
1020  /**
1021   * Validates string parameter to be non-empty string (null value allowed).
1022   *
1023   * @param param     The string parameter.
1024   * @param paramName Name of the parameter.
1025   * @throws ArgumentException
1026   * @throws ServiceLocalException
1027   */
1028  public static void validateNonBlankStringParamAllowNull(String param,
1029      String paramName) throws ArgumentException, ServiceLocalException {
1030    if (param != null) {
1031      // Non-empty string has at least one character
1032      //which is *not* a whitespace character
1033      if (param.length() == countMatchingChars(param,
1034          new IPredicate<Character>() {
1035            @Override
1036            public boolean predicate(Character obj) {
1037              return Character.isWhitespace(obj);
1038            }
1039          })) {
1040        throw new ArgumentException("The string argument contains only white space characters.", paramName);
1041      }
1042    }
1043  }
1044
1045
1046  /**
1047   * Validates string parameter to be
1048   * non-empty string (null value not allowed).
1049   *
1050   * @param param     The string parameter.
1051   * @param paramName Name of the parameter.
1052   * @throws ArgumentNullException
1053   * @throws ArgumentException
1054   * @throws ServiceLocalException
1055   */
1056  public static void validateNonBlankStringParam(String param,
1057      String paramName) throws ArgumentNullException, ArgumentException, ServiceLocalException {
1058    if (param == null) {
1059      throw new ArgumentNullException(paramName);
1060    }
1061
1062    validateNonBlankStringParamAllowNull(param, paramName);
1063  }
1064
1065  /**
1066   * Validate enum version value.
1067   *
1068   * @param enumValue      the enum value
1069   * @param requestVersion the request version
1070   * @throws ServiceVersionException the service version exception
1071   */
1072  public static void validateEnumVersionValue(Enum<?> enumValue,
1073      ExchangeVersion requestVersion) throws ServiceVersionException {
1074    final Map<Class<?>, Map<String, ExchangeVersion>> member =
1075      ENUM_VERSION_DICTIONARIES.getMember();
1076    final Map<String, ExchangeVersion> enumVersionDict =
1077      member.get(enumValue.getClass());
1078
1079    final ExchangeVersion enumVersion = enumVersionDict.get(enumValue.toString());
1080    if (enumVersion != null) {
1081      final int i = requestVersion.compareTo(enumVersion);
1082      if (i < 0) {
1083        throw new ServiceVersionException(
1084          String.format(
1085            "Enumeration value %s in enumeration type %s is only valid for Exchange version %s or later.",
1086            enumValue.toString(),
1087            enumValue.getClass().getName(),
1088            enumVersion
1089          )
1090        );
1091      }
1092    }
1093  }
1094
1095  /**
1096   * Validates service object version against the request version.
1097   *
1098   * @param serviceObject  The service object.
1099   * @param requestVersion The request version.
1100   * @throws ServiceVersionException Raised if this service object type requires a later version
1101   *                                 of Exchange.
1102   */
1103  public static void validateServiceObjectVersion(
1104      ServiceObject serviceObject, ExchangeVersion requestVersion)
1105      throws ServiceVersionException {
1106    ExchangeVersion minimumRequiredServerVersion = serviceObject
1107        .getMinimumRequiredServerVersion();
1108
1109    if (requestVersion.ordinal() < minimumRequiredServerVersion.ordinal()) {
1110      String msg = String.format(
1111          "The object type %s is only valid for Exchange Server version %s or later versions.",
1112          serviceObject.getClass().getName(), minimumRequiredServerVersion.toString());
1113      throw new ServiceVersionException(msg);
1114    }
1115  }
1116
1117  /**
1118   * Validates property version against the request version.
1119   *
1120   * @param service              The Exchange service.
1121   * @param minimumServerVersion The minimum server version
1122   * @param propertyName         The property name
1123   * @throws ServiceVersionException The service version exception
1124   */
1125  public static void validatePropertyVersion(
1126      ExchangeService service,
1127      ExchangeVersion minimumServerVersion,
1128      String propertyName) throws ServiceVersionException {
1129    if (service.getRequestedServerVersion().ordinal() <
1130        minimumServerVersion.ordinal()) {
1131      throw new ServiceVersionException(
1132          String.format("The property %s is valid only for Exchange %s or later versions.",
1133              propertyName,
1134              minimumServerVersion));
1135    }
1136  }
1137
1138  /**
1139   * Validate method version.
1140   *
1141   * @param service              the service
1142   * @param minimumServerVersion the minimum server version
1143   * @param methodName           the method name
1144   * @throws ServiceVersionException the service version exception
1145   */
1146  public static void validateMethodVersion(ExchangeService service,
1147      ExchangeVersion minimumServerVersion, String methodName)
1148      throws ServiceVersionException {
1149    if (service.getRequestedServerVersion().ordinal() <
1150        minimumServerVersion.ordinal())
1151
1152    {
1153      throw new ServiceVersionException(String.format(
1154          "Method %s is only valid for Exchange Server version %s or later.", methodName,
1155          minimumServerVersion));
1156    }
1157  }
1158
1159  /**
1160   * Validates class version against the request version.
1161   *
1162   * @param service              the service
1163   * @param minimumServerVersion The minimum server version that supports the method.
1164   * @param className            Name of the class.
1165   * @throws ServiceVersionException
1166   */
1167  public static void validateClassVersion(
1168      ExchangeService service,
1169      ExchangeVersion minimumServerVersion,
1170      String className) throws ServiceVersionException {
1171    if (service.getRequestedServerVersion().ordinal() <
1172        minimumServerVersion.ordinal()) {
1173      throw new ServiceVersionException(
1174          String.format("Class %s is only valid for Exchange version %s or later.",
1175              className,
1176              minimumServerVersion));
1177    }
1178  }
1179
1180  /**
1181   * Validates domain name (null value allowed)
1182   *
1183   * @param domainName Domain name.
1184   * @param paramName  Parameter name.
1185   * @throws ArgumentException
1186   */
1187  public static void validateDomainNameAllowNull(String domainName, String paramName) throws
1188                                                                                      ArgumentException {
1189    if (domainName != null) {
1190      Pattern domainNamePattern = Pattern.compile(DomainRegex);
1191      Matcher domainNameMatcher = domainNamePattern.matcher(domainName);
1192      if (!domainNameMatcher.find()) {
1193        throw new ArgumentException(String.format("'%s' is not a valid domain name.", domainName), paramName);
1194      }
1195    }
1196  }
1197
1198  /**
1199   * Builds the enum dict.
1200   *
1201   * @param <E> the element type
1202   * @param c   the c
1203   * @return the map
1204   */
1205  private static <E extends Enum<E>> Map<String, ExchangeVersion>
1206  buildEnumDict(Class<E> c) {
1207    Map<String, ExchangeVersion> dict =
1208        new HashMap<String, ExchangeVersion>();
1209    Field[] fields = c.getDeclaredFields();
1210    for (Field f : fields) {
1211      if (f.isEnumConstant()
1212          && f.isAnnotationPresent(RequiredServerVersion.class)) {
1213        RequiredServerVersion ewsEnum = f
1214            .getAnnotation(RequiredServerVersion.class);
1215        String fieldName = f.getName();
1216        ExchangeVersion exchangeVersion = ewsEnum.version();
1217        dict.put(fieldName, exchangeVersion);
1218      }
1219    }
1220    return dict;
1221  }
1222
1223  /**
1224   * Builds the enum to schema mapping dictionary.
1225   *
1226   * @param c class type
1227   * @return The mapping from enum to schema name
1228   */
1229  private static Map<String, String> buildEnumToSchemaDict(Class<?> c) {
1230    Map<String, String> dict = new HashMap<String, String>();
1231    Field[] fields = c.getFields();
1232    for (Field f : fields) {
1233      if (f.isEnumConstant() && f.isAnnotationPresent(EwsEnum.class)) {
1234        EwsEnum ewsEnum = f.getAnnotation(EwsEnum.class);
1235        String fieldName = f.getName();
1236        String schemaName = ewsEnum.schemaName();
1237        if (!schemaName.isEmpty()) {
1238          dict.put(fieldName, schemaName);
1239        }
1240      }
1241    }
1242    return dict;
1243  }
1244
1245  /**
1246   * Gets the enumerated object count.
1247   *
1248   * @param <T>     the generic type
1249   * @param objects The objects.
1250   * @return Count of objects in iterator.
1251   */
1252  public static <T> int getEnumeratedObjectCount(Iterator<T> objects) {
1253    int count = 0;
1254    while (objects != null && objects.hasNext()) {
1255      objects.next();
1256      count++;
1257    }
1258    return count;
1259  }
1260
1261  /**
1262   * Gets the enumerated object at.
1263   *
1264   * @param <T>     the generic type
1265   * @param objects the objects
1266   * @param index   the index
1267   * @return the enumerated object at
1268   */
1269  public static <T> Object getEnumeratedObjectAt(Iterable<T> objects, int index) {
1270    int count = 0;
1271    for (Object obj : objects) {
1272      if (count == index) {
1273        return obj;
1274      }
1275      count++;
1276    }
1277    throw new IndexOutOfBoundsException("The IEnumerable doesn't contain that many objects.");
1278  }
1279
1280
1281  /**
1282   * Count characters in string that match a condition.
1283   *
1284   * @param str           The string.
1285   * @param charPredicate Predicate to evaluate for each character in the string.
1286   * @return Count of characters that match condition expressed by predicate.
1287   * @throws ServiceLocalException
1288   */
1289  public static int countMatchingChars(
1290    String str, IPredicate<Character> charPredicate
1291  ) throws ServiceLocalException {
1292    int count = 0;
1293    for (int i = 0; i < str.length(); i++) {
1294      if (charPredicate.predicate(str.charAt(i))) {
1295        count++;
1296      }
1297    }
1298    return count;
1299  }
1300
1301  /**
1302   * Determines whether every element in the collection
1303   * matches the conditions defined by the specified predicate.
1304   *
1305   * @param <T>        Entry type.
1306   * @param collection The collection.
1307   * @param predicate  Predicate that defines the conditions to check against the elements.
1308   * @return True if every element in the collection matches
1309   * the conditions defined by the specified predicate; otherwise, false.
1310   * @throws ServiceLocalException
1311   */
1312  public static <T> boolean trueForAll(Iterable<T> collection,
1313      IPredicate<T> predicate) throws ServiceLocalException {
1314    for (T entry : collection) {
1315      if (!predicate.predicate(entry)) {
1316        return false;
1317      }
1318    }
1319
1320    return true;
1321  }
1322
1323  /**
1324   * Call an action for each member of a collection.
1325   *
1326   * @param <T>        Collection element type.
1327   * @param collection The collection.
1328   * @param action     The action to apply.
1329   */
1330  public static <T> void forEach(Iterable<T> collection, IAction<T> action) {
1331    for (T entry : collection) {
1332      action.action(entry);
1333    }
1334  }
1335
1336
1337  private static String formatDate(Date date, String format) {
1338    final DateFormat utcFormatter = createDateFormat(format);
1339    return utcFormatter.format(date);
1340  }
1341
1342  private static DateFormat createDateFormat(String format) {
1343    final DateFormat utcFormatter = new SimpleDateFormat(format);
1344    utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
1345    return utcFormatter;
1346  }
1347
1348}