001/* 002 * The MIT License 003 * Copyright (c) 2012 Microsoft Corporation 004 * 005 * Permission is hereby granted, free of charge, to any person obtaining a copy 006 * of this software and associated documentation files (the "Software"), to deal 007 * in the Software without restriction, including without limitation the rights 008 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 009 * copies of the Software, and to permit persons to whom the Software is 010 * furnished to do so, subject to the following conditions: 011 * 012 * The above copyright notice and this permission notice shall be included in 013 * all copies or substantial portions of the Software. 014 * 015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 018 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 020 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 021 * THE SOFTWARE. 022 */ 023 024package microsoft.exchange.webservices.data.core; 025 026import microsoft.exchange.webservices.data.ISelfValidate; 027import microsoft.exchange.webservices.data.attribute.EwsEnum; 028import microsoft.exchange.webservices.data.attribute.RequiredServerVersion; 029import microsoft.exchange.webservices.data.core.request.HttpWebRequest; 030import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithAttachmentParam; 031import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithServiceParam; 032import microsoft.exchange.webservices.data.core.service.ServiceObject; 033import microsoft.exchange.webservices.data.core.service.ServiceObjectInfo; 034import microsoft.exchange.webservices.data.core.service.item.Item; 035import microsoft.exchange.webservices.data.core.enumeration.notification.EventType; 036import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion; 037import microsoft.exchange.webservices.data.core.enumeration.service.FileAsMapping; 038import microsoft.exchange.webservices.data.core.enumeration.search.ItemTraversal; 039import microsoft.exchange.webservices.data.core.enumeration.property.MailboxType; 040import microsoft.exchange.webservices.data.core.enumeration.service.MeetingRequestsDeliveryScope; 041import microsoft.exchange.webservices.data.core.enumeration.property.RuleProperty; 042import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName; 043import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; 044import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException; 045import microsoft.exchange.webservices.data.core.exception.misc.ArgumentNullException; 046import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException; 047import microsoft.exchange.webservices.data.core.exception.misc.FormatException; 048import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException; 049import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException; 050import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException; 051import microsoft.exchange.webservices.data.misc.TimeSpan; 052import microsoft.exchange.webservices.data.property.complex.ItemAttachment; 053 054import org.apache.commons.logging.Log; 055import org.apache.commons.logging.LogFactory; 056import org.joda.time.Period; 057import org.joda.time.format.ISOPeriodFormat; 058 059import javax.xml.stream.XMLOutputFactory; 060import javax.xml.stream.XMLStreamException; 061import javax.xml.stream.XMLStreamWriter; 062 063import java.io.ByteArrayOutputStream; 064import java.io.IOException; 065import java.lang.reflect.Field; 066import java.math.BigDecimal; 067import java.math.BigInteger; 068import java.net.URISyntaxException; 069import java.text.DateFormat; 070import java.text.DecimalFormat; 071import java.text.ParseException; 072import java.text.SimpleDateFormat; 073import java.util.Date; 074import java.util.HashMap; 075import java.util.Iterator; 076import java.util.List; 077import java.util.Map; 078import java.util.TimeZone; 079import java.util.regex.Matcher; 080import java.util.regex.Pattern; 081 082/** 083 * EWS utilities. 084 */ 085public final class EwsUtilities { 086 087 private static final Log LOG = LogFactory.getLog(EwsUtilities.class); 088 089 /** 090 * The Constant XSFalse. 091 */ 092 public static final String XSFalse = "false"; 093 094 /** 095 * The Constant XSTrue. 096 */ 097 public static final String XSTrue = "true"; 098 099 /** 100 * The Constant EwsTypesNamespacePrefix. 101 */ 102 public static final String EwsTypesNamespacePrefix = "t"; 103 104 /** 105 * The Constant EwsMessagesNamespacePrefix. 106 */ 107 public static final String EwsMessagesNamespacePrefix = "m"; 108 109 /** 110 * The Constant EwsErrorsNamespacePrefix. 111 */ 112 public static final String EwsErrorsNamespacePrefix = "e"; 113 114 /** 115 * The Constant EwsSoapNamespacePrefix. 116 */ 117 public static final String EwsSoapNamespacePrefix = "soap"; 118 119 /** 120 * The Constant EwsXmlSchemaInstanceNamespacePrefix. 121 */ 122 public static final String EwsXmlSchemaInstanceNamespacePrefix = "xsi"; 123 124 /** 125 * The Constant PassportSoapFaultNamespacePrefix. 126 */ 127 public static final String PassportSoapFaultNamespacePrefix = "psf"; 128 129 /** 130 * The Constant WSTrustFebruary2005NamespacePrefix. 131 */ 132 public static final String WSTrustFebruary2005NamespacePrefix = "wst"; 133 134 /** 135 * The Constant WSAddressingNamespacePrefix. 136 */ 137 public static final String WSAddressingNamespacePrefix = "wsa"; 138 139 /** 140 * The Constant AutodiscoverSoapNamespacePrefix. 141 */ 142 public static final String AutodiscoverSoapNamespacePrefix = "a"; 143 144 /** 145 * The Constant WSSecurityUtilityNamespacePrefix. 146 */ 147 public static final String WSSecurityUtilityNamespacePrefix = "wsu"; 148 149 /** 150 * The Constant WSSecuritySecExtNamespacePrefix. 151 */ 152 public static final String WSSecuritySecExtNamespacePrefix = "wsse"; 153 154 /** 155 * The Constant EwsTypesNamespace. 156 */ 157 public static final String EwsTypesNamespace = 158 "http://schemas.microsoft.com/exchange/services/2006/types"; 159 160 /** 161 * The Constant EwsMessagesNamespace. 162 */ 163 public static final String EwsMessagesNamespace = 164 "http://schemas.microsoft.com/exchange/services/2006/messages"; 165 166 /** 167 * The Constant EwsErrorsNamespace. 168 */ 169 public static final String EwsErrorsNamespace = 170 "http://schemas.microsoft.com/exchange/services/2006/errors"; 171 172 /** 173 * The Constant EwsSoapNamespace. 174 */ 175 public static final String EwsSoapNamespace = 176 "http://schemas.xmlsoap.org/soap/envelope/"; 177 178 /** 179 * The Constant EwsSoap12Namespace. 180 */ 181 public static final String EwsSoap12Namespace = 182 "http://www.w3.org/2003/05/soap-envelope"; 183 184 /** 185 * The Constant EwsXmlSchemaInstanceNamespace. 186 */ 187 public static final String EwsXmlSchemaInstanceNamespace = 188 "http://www.w3.org/2001/XMLSchema-instance"; 189 190 /** 191 * The Constant PassportSoapFaultNamespace. 192 */ 193 public static final String PassportSoapFaultNamespace = 194 "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault"; 195 196 /** 197 * The Constant WSTrustFebruary2005Namespace. 198 */ 199 public static final String WSTrustFebruary2005Namespace = 200 "http://schemas.xmlsoap.org/ws/2005/02/trust"; 201 202 /** 203 * The Constant WSAddressingNamespace. 204 */ 205 public static final String WSAddressingNamespace = 206 "http://www.w3.org/2005/08/addressing"; 207 // "http://schemas.xmlsoap.org/ws/2004/08/addressing"; 208 209 /** 210 * The Constant AutodiscoverSoapNamespace. 211 */ 212 public static final String AutodiscoverSoapNamespace = 213 "http://schemas.microsoft.com/exchange/2010/Autodiscover"; 214 215 public static final String WSSecurityUtilityNamespace = 216 "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; 217 public static final String WSSecuritySecExtNamespace = 218 "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; 219 220 /** 221 * The service object info. 222 */ 223 private static final LazyMember<ServiceObjectInfo> SERVICE_OBJECT_INFO = 224 new LazyMember<ServiceObjectInfo>( 225 new ILazyMember<ServiceObjectInfo>() { 226 public ServiceObjectInfo createInstance() { 227 return new ServiceObjectInfo(); 228 } 229 } 230 ); 231 232 private static final String XML_SCHEMA_DATE_FORMAT = "yyyy-MM-dd'Z'"; 233 private static final String XML_SCHEMA_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; 234 235 private static final Pattern PATTERN_TIME_SPAN = Pattern.compile("-P"); 236 private static final Pattern PATTERN_YEAR = Pattern.compile("(\\d+)Y"); 237 private static final Pattern PATTERN_MONTH = Pattern.compile("(\\d+)M"); 238 private static final Pattern PATTERN_DAY = Pattern.compile("(\\d+)D"); 239 private static final Pattern PATTERN_HOUR = Pattern.compile("(\\d+)H"); 240 private static final Pattern PATTERN_MINUTES = Pattern.compile("(\\d+)M"); 241 private static final Pattern PATTERN_SECONDS = Pattern.compile("(\\d+)\\."); // Need to escape dot, otherwise it matches any char 242 private static final Pattern PATTERN_MILLISECONDS = Pattern.compile("(\\d+)S"); 243 244 245 private EwsUtilities() { 246 throw new UnsupportedOperationException(); 247 } 248 249 250 /** 251 * Gets the builds the version. 252 * 253 * @return the builds the version 254 */ 255 public static String getBuildVersion() { 256 return "0.0.0.0"; 257 } 258 259 /** 260 * The enum version dictionaries. 261 */ 262 private static final LazyMember<Map<Class<?>, Map<String, ExchangeVersion>>> 263 ENUM_VERSION_DICTIONARIES = 264 new LazyMember<Map<Class<?>, Map<String, ExchangeVersion>>>( 265 new ILazyMember<Map<Class<?>, Map<String, ExchangeVersion>>>() { 266 @Override 267 public Map<Class<?>, Map<String, ExchangeVersion>> 268 createInstance() { 269 Map<Class<?>, Map<String, ExchangeVersion>> enumDicts = 270 new HashMap<Class<?>, Map<String, 271 ExchangeVersion>>(); 272 enumDicts.put(WellKnownFolderName.class, 273 buildEnumDict(WellKnownFolderName.class)); 274 enumDicts.put(ItemTraversal.class, 275 buildEnumDict(ItemTraversal.class)); 276 enumDicts.put(FileAsMapping.class, 277 buildEnumDict(FileAsMapping.class)); 278 enumDicts.put(EventType.class, 279 buildEnumDict(EventType.class)); 280 enumDicts.put(MeetingRequestsDeliveryScope.class, 281 buildEnumDict(MeetingRequestsDeliveryScope. 282 class)); 283 return enumDicts; 284 } 285 }); 286 /** 287 * Dictionary of enum type to schema-name-to-enum-value maps. 288 */ 289 private static final LazyMember<Map<Class<?>, Map<String, String>>> 290 SCHEMA_TO_ENUM_DICTIONARIES = 291 new LazyMember<Map<Class<?>, Map<String, String>>>( 292 new ILazyMember<Map<Class<?>, Map<String, String>>>() { 293 @Override 294 public Map<Class<?>, Map<String, String>> createInstance() { 295 Map<Class<?>, Map<String, String>> enumDicts = 296 new HashMap<Class<?>, Map<String, String>>(); 297 enumDicts.put(EventType.class, 298 buildSchemaToEnumDict(EventType.class)); 299 enumDicts.put(MailboxType.class, 300 buildSchemaToEnumDict(MailboxType.class)); 301 enumDicts.put(FileAsMapping.class, 302 buildSchemaToEnumDict(FileAsMapping.class)); 303 enumDicts.put(RuleProperty.class, 304 buildSchemaToEnumDict(RuleProperty.class)); 305 return enumDicts; 306 307 } 308 }); 309 310 /** 311 * Dictionary of enum type to enum-value-to-schema-name maps. 312 */ 313 public static final LazyMember<Map<Class<?>, Map<String, String>>> 314 ENUM_TO_SCHEMA_DICTIONARIES = 315 new LazyMember<Map<Class<?>, Map<String, String>>>( 316 new ILazyMember<Map<Class<?>, Map<String, String>>>() { 317 @Override 318 public Map<Class<?>, Map<String, String>> createInstance() { 319 Map<Class<?>, Map<String, String>> enumDicts = 320 new HashMap<Class<?>, Map<String, String>>(); 321 enumDicts.put(EventType.class, 322 buildEnumToSchemaDict(EventType.class)); 323 enumDicts.put(MailboxType.class, 324 buildEnumToSchemaDict(MailboxType.class)); 325 enumDicts.put(FileAsMapping.class, 326 buildEnumToSchemaDict(FileAsMapping.class)); 327 enumDicts.put(RuleProperty.class, 328 buildEnumToSchemaDict(RuleProperty.class)); 329 return enumDicts; 330 } 331 }); 332 333 /** 334 * Regular expression for legal domain names. 335 */ 336 public static final String DomainRegex = "^[-a-zA-Z0-9_.]+$"; 337 338 /** 339 * Asserts that the specified condition if true. 340 * 341 * @param condition Assertion. 342 * @param caller The caller. 343 * @param message The message to use if assertion fails. 344 */ 345 public static void ewsAssert( 346 final boolean condition, final String caller, final String message 347 ) { 348 if (!condition) { 349 throw new RuntimeException(String.format("[%s] %s", caller, message)); 350 } 351 } 352 353 /** 354 * Gets the namespace prefix from an XmlNamespace enum value. 355 * 356 * @param xmlNamespace The XML namespace 357 * @return Namespace prefix string. 358 */ 359 public static String getNamespacePrefix(XmlNamespace xmlNamespace) { 360 return xmlNamespace.getNameSpacePrefix(); 361 } 362 363 /** 364 * Gets the namespace URI from an XmlNamespace enum value. 365 * 366 * @param xmlNamespace The XML namespace. 367 * @return Uri as string 368 */ 369 public static String getNamespaceUri(XmlNamespace xmlNamespace) { 370 return xmlNamespace.getNameSpaceUri(); 371 } 372 373 /** 374 * Gets the namespace from uri. 375 * 376 * @param namespaceUri the namespace uri 377 * @return the namespace from uri 378 */ 379 public static XmlNamespace getNamespaceFromUri(String namespaceUri) { 380 if (EwsErrorsNamespace.equals(namespaceUri)) { 381 return XmlNamespace.Errors; 382 } else if (EwsTypesNamespace.equals(namespaceUri)) { 383 return XmlNamespace.Types; 384 } else if (EwsMessagesNamespace.equals(namespaceUri)) { 385 return XmlNamespace.Messages; 386 } else if (EwsSoapNamespace.equals(namespaceUri)) { 387 return XmlNamespace.Soap; 388 } else if (EwsSoap12Namespace.equals(namespaceUri)) { 389 return XmlNamespace.Soap12; 390 } else if (EwsXmlSchemaInstanceNamespace.equals(namespaceUri)) { 391 return XmlNamespace.XmlSchemaInstance; 392 } else if (PassportSoapFaultNamespace.equals(namespaceUri)) { 393 return XmlNamespace.PassportSoapFault; 394 } else if (WSTrustFebruary2005Namespace.equals(namespaceUri)) { 395 return XmlNamespace.WSTrustFebruary2005; 396 } else if (WSAddressingNamespace.equals(namespaceUri)) { 397 return XmlNamespace.WSAddressing; 398 } else { 399 return XmlNamespace.NotSpecified; 400 } 401 } 402 403 /** 404 * Creates the ews object from xml element name. 405 * 406 * @param <TServiceObject> the generic type 407 * @param itemClass the item class 408 * @param service the service 409 * @param xmlElementName the xml element name 410 * @return the t service object 411 * @throws Exception the exception 412 */ 413 @SuppressWarnings("unchecked") 414 public static <TServiceObject extends ServiceObject> 415 TServiceObject createEwsObjectFromXmlElementName( 416 Class<?> itemClass, ExchangeService service, String xmlElementName) 417 throws Exception { 418 final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); 419 final Map<String, Class<?>> map = member.getXmlElementNameToServiceObjectClassMap(); 420 421 final Class<?> ic = map.get(xmlElementName); 422 if (ic != null) { 423 final Map<Class<?>, ICreateServiceObjectWithServiceParam> 424 serviceParam = member.getServiceObjectConstructorsWithServiceParam(); 425 final ICreateServiceObjectWithServiceParam creationDelegate = 426 serviceParam.get(ic); 427 428 if (creationDelegate != null) { 429 return (TServiceObject) creationDelegate 430 .createServiceObjectWithServiceParam(service); 431 } else { 432 throw new IllegalArgumentException("No appropriate constructor could be found for this item class."); 433 } 434 } 435 436 return (TServiceObject) itemClass.newInstance(); 437 } 438 439 /** 440 * Creates the item from item class. 441 * 442 * @param itemAttachment the item attachment 443 * @param itemClass the item class 444 * @param isNew the is new 445 * @return the item 446 * @throws Exception the exception 447 */ 448 public static Item createItemFromItemClass( 449 ItemAttachment itemAttachment, Class<?> itemClass, boolean isNew) 450 throws Exception { 451 final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); 452 final Map<Class<?>, ICreateServiceObjectWithAttachmentParam> 453 dataMap = member.getServiceObjectConstructorsWithAttachmentParam(); 454 final ICreateServiceObjectWithAttachmentParam creationDelegate = 455 dataMap.get(itemClass); 456 457 if (creationDelegate != null) { 458 return (Item) creationDelegate 459 .createServiceObjectWithAttachmentParam(itemAttachment, isNew); 460 } 461 throw new IllegalArgumentException("No appropriate constructor could be found for this item class."); 462 } 463 464 /** 465 * Creates the item from xml element name. 466 * 467 * @param itemAttachment the item attachment 468 * @param xmlElementName the xml element name 469 * @return the item 470 * @throws Exception the exception 471 */ 472 public static Item createItemFromXmlElementName( 473 ItemAttachment itemAttachment, String xmlElementName) 474 throws Exception { 475 final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); 476 final Map<String, Class<?>> map = 477 member.getXmlElementNameToServiceObjectClassMap(); 478 479 final Class<?> itemClass = map.get(xmlElementName); 480 if (itemClass != null) { 481 return createItemFromItemClass(itemAttachment, itemClass, false); 482 } 483 return null; 484 } 485 486 public static Class<?> getItemTypeFromXmlElementName(String xmlElementName) { 487 final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); 488 final Map<String, Class<?>> map = member.getXmlElementNameToServiceObjectClassMap(); 489 return map.get(xmlElementName); 490 } 491 492 /** 493 * Finds the first item of type TItem (not a descendant type) in the 494 * specified collection. 495 * 496 * @param <TItem> TItem is the type of the item to find. 497 * @param cls the cls 498 * @param items the item 499 * @return A TItem instance or null if no instance of TItem could be found. 500 */ 501 @SuppressWarnings("unchecked") 502 public static <TItem extends Item> TItem findFirstItemOfType( 503 Class<TItem> cls, Iterable<Item> items 504 ) { 505 for (Item item : items) { 506 // We're looking for an exact class match here. 507 final Class<? extends Item> itemClass = item.getClass(); 508 if (itemClass.equals(cls)) { 509 return (TItem) item; 510 } 511 } 512 513 return null; 514 } 515 516 /** 517 * Write trace start element. 518 * 519 * @param writer the writer to write the start element to 520 * @param traceTag the trace tag 521 * @param includeVersion if true, include build version attribute 522 * @throws XMLStreamException the XML stream exception 523 */ 524 private static void writeTraceStartElement( 525 XMLStreamWriter writer, 526 String traceTag, 527 boolean includeVersion) throws XMLStreamException { 528 writer.writeStartElement("Trace"); 529 writer.writeAttribute("Tag", traceTag); 530 writer.writeAttribute("Tid", Thread.currentThread().getId() + ""); 531 Date d = new Date(); 532 DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'"); 533 df.setTimeZone(TimeZone.getTimeZone("UTC")); 534 String formattedString = df.format(d); 535 writer.writeAttribute("Time", formattedString); 536 537 if (includeVersion) { 538 writer.writeAttribute("Version", EwsUtilities.getBuildVersion()); 539 } 540 } 541 542 /** 543 * . 544 * 545 * @param entryKind the entry kind 546 * @param logEntry the log entry 547 * @return the string 548 * @throws XMLStreamException the XML stream exception 549 * @throws IOException signals that an I/O exception has occurred. 550 */ 551 public static String formatLogMessage(String entryKind, String logEntry) 552 throws XMLStreamException, IOException { 553 String lineSeparator = System.getProperty("line.separator"); 554 ByteArrayOutputStream outStream = new ByteArrayOutputStream(); 555 XMLOutputFactory factory = XMLOutputFactory.newInstance(); 556 XMLStreamWriter writer = factory.createXMLStreamWriter(outStream); 557 EwsUtilities.writeTraceStartElement(writer, entryKind, false); 558 writer.writeCharacters(lineSeparator); 559 writer.writeCharacters(logEntry); 560 writer.writeCharacters(lineSeparator); 561 writer.writeEndElement(); 562 writer.writeCharacters(lineSeparator); 563 writer.flush(); 564 writer.close(); 565 outStream.flush(); 566 String formattedLogMessage = outStream.toString(); 567 formattedLogMessage = formattedLogMessage.replaceAll("'", "'"); 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}