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("'", "'");
568 formattedLogMessage = formattedLogMessage.replaceAll(""", "\"");
569 formattedLogMessage = formattedLogMessage.replaceAll(">", ">");
570 formattedLogMessage = formattedLogMessage.replaceAll("<", "<");
571 formattedLogMessage = formattedLogMessage.replaceAll("&", "&");
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 }