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.core;
025    
026    import microsoft.exchange.webservices.data.ISelfValidate;
027    import microsoft.exchange.webservices.data.attribute.EwsEnum;
028    import microsoft.exchange.webservices.data.attribute.RequiredServerVersion;
029    import microsoft.exchange.webservices.data.core.request.HttpWebRequest;
030    import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithAttachmentParam;
031    import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithServiceParam;
032    import microsoft.exchange.webservices.data.core.service.ServiceObject;
033    import microsoft.exchange.webservices.data.core.service.ServiceObjectInfo;
034    import microsoft.exchange.webservices.data.core.service.item.Item;
035    import microsoft.exchange.webservices.data.core.enumeration.notification.EventType;
036    import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
037    import microsoft.exchange.webservices.data.core.enumeration.service.FileAsMapping;
038    import microsoft.exchange.webservices.data.core.enumeration.search.ItemTraversal;
039    import microsoft.exchange.webservices.data.core.enumeration.property.MailboxType;
040    import microsoft.exchange.webservices.data.core.enumeration.service.MeetingRequestsDeliveryScope;
041    import microsoft.exchange.webservices.data.core.enumeration.property.RuleProperty;
042    import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName;
043    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
044    import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
045    import microsoft.exchange.webservices.data.core.exception.misc.ArgumentNullException;
046    import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
047    import microsoft.exchange.webservices.data.core.exception.misc.FormatException;
048    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
049    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
050    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
051    import microsoft.exchange.webservices.data.misc.TimeSpan;
052    import microsoft.exchange.webservices.data.property.complex.ItemAttachment;
053    
054    import org.apache.commons.logging.Log;
055    import org.apache.commons.logging.LogFactory;
056    import org.joda.time.Period;
057    import org.joda.time.format.ISOPeriodFormat;
058    
059    import javax.xml.stream.XMLOutputFactory;
060    import javax.xml.stream.XMLStreamException;
061    import javax.xml.stream.XMLStreamWriter;
062    
063    import java.io.ByteArrayOutputStream;
064    import java.io.IOException;
065    import java.lang.reflect.Field;
066    import java.math.BigDecimal;
067    import java.math.BigInteger;
068    import java.net.URISyntaxException;
069    import java.text.DateFormat;
070    import java.text.DecimalFormat;
071    import java.text.ParseException;
072    import java.text.SimpleDateFormat;
073    import java.util.Date;
074    import java.util.HashMap;
075    import java.util.Iterator;
076    import java.util.List;
077    import java.util.Map;
078    import java.util.TimeZone;
079    import java.util.regex.Matcher;
080    import java.util.regex.Pattern;
081    
082    /**
083     * EWS utilities.
084     */
085    public 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    }