/* * The MIT License * Copyright (c) 2012 Microsoft Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package microsoft.exchange.webservices.data.core; import microsoft.exchange.webservices.data.ISelfValidate; import microsoft.exchange.webservices.data.attribute.EwsEnum; import microsoft.exchange.webservices.data.attribute.RequiredServerVersion; import microsoft.exchange.webservices.data.core.request.HttpWebRequest; import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithAttachmentParam; import microsoft.exchange.webservices.data.core.service.ICreateServiceObjectWithServiceParam; import microsoft.exchange.webservices.data.core.service.ServiceObject; import microsoft.exchange.webservices.data.core.service.ServiceObjectInfo; import microsoft.exchange.webservices.data.core.service.item.Item; import microsoft.exchange.webservices.data.core.enumeration.notification.EventType; import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion; import microsoft.exchange.webservices.data.core.enumeration.service.FileAsMapping; import microsoft.exchange.webservices.data.core.enumeration.search.ItemTraversal; import microsoft.exchange.webservices.data.core.enumeration.property.MailboxType; import microsoft.exchange.webservices.data.core.enumeration.service.MeetingRequestsDeliveryScope; import microsoft.exchange.webservices.data.core.enumeration.property.RuleProperty; import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName; import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException; import microsoft.exchange.webservices.data.core.exception.misc.ArgumentNullException; import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException; import microsoft.exchange.webservices.data.core.exception.misc.FormatException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException; import microsoft.exchange.webservices.data.misc.TimeSpan; import microsoft.exchange.webservices.data.property.complex.ItemAttachment; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.joda.time.Period; import org.joda.time.format.ISOPeriodFormat; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URISyntaxException; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TimeZone; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * EWS utilities. */ public final class EwsUtilities { private static final Log LOG = LogFactory.getLog(EwsUtilities.class); /** * The Constant XSFalse. */ public static final String XSFalse = "false"; /** * The Constant XSTrue. */ public static final String XSTrue = "true"; /** * The Constant EwsTypesNamespacePrefix. */ public static final String EwsTypesNamespacePrefix = "t"; /** * The Constant EwsMessagesNamespacePrefix. */ public static final String EwsMessagesNamespacePrefix = "m"; /** * The Constant EwsErrorsNamespacePrefix. */ public static final String EwsErrorsNamespacePrefix = "e"; /** * The Constant EwsSoapNamespacePrefix. */ public static final String EwsSoapNamespacePrefix = "soap"; /** * The Constant EwsXmlSchemaInstanceNamespacePrefix. */ public static final String EwsXmlSchemaInstanceNamespacePrefix = "xsi"; /** * The Constant PassportSoapFaultNamespacePrefix. */ public static final String PassportSoapFaultNamespacePrefix = "psf"; /** * The Constant WSTrustFebruary2005NamespacePrefix. */ public static final String WSTrustFebruary2005NamespacePrefix = "wst"; /** * The Constant WSAddressingNamespacePrefix. */ public static final String WSAddressingNamespacePrefix = "wsa"; /** * The Constant AutodiscoverSoapNamespacePrefix. */ public static final String AutodiscoverSoapNamespacePrefix = "a"; /** * The Constant WSSecurityUtilityNamespacePrefix. */ public static final String WSSecurityUtilityNamespacePrefix = "wsu"; /** * The Constant WSSecuritySecExtNamespacePrefix. */ public static final String WSSecuritySecExtNamespacePrefix = "wsse"; /** * The Constant EwsTypesNamespace. */ public static final String EwsTypesNamespace = "http://schemas.microsoft.com/exchange/services/2006/types"; /** * The Constant EwsMessagesNamespace. */ public static final String EwsMessagesNamespace = "http://schemas.microsoft.com/exchange/services/2006/messages"; /** * The Constant EwsErrorsNamespace. */ public static final String EwsErrorsNamespace = "http://schemas.microsoft.com/exchange/services/2006/errors"; /** * The Constant EwsSoapNamespace. */ public static final String EwsSoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/"; /** * The Constant EwsSoap12Namespace. */ public static final String EwsSoap12Namespace = "http://www.w3.org/2003/05/soap-envelope"; /** * The Constant EwsXmlSchemaInstanceNamespace. */ public static final String EwsXmlSchemaInstanceNamespace = "http://www.w3.org/2001/XMLSchema-instance"; /** * The Constant PassportSoapFaultNamespace. */ public static final String PassportSoapFaultNamespace = "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault"; /** * The Constant WSTrustFebruary2005Namespace. */ public static final String WSTrustFebruary2005Namespace = "http://schemas.xmlsoap.org/ws/2005/02/trust"; /** * The Constant WSAddressingNamespace. */ public static final String WSAddressingNamespace = "http://www.w3.org/2005/08/addressing"; // "http://schemas.xmlsoap.org/ws/2004/08/addressing"; /** * The Constant AutodiscoverSoapNamespace. */ public static final String AutodiscoverSoapNamespace = "http://schemas.microsoft.com/exchange/2010/Autodiscover"; public static final String WSSecurityUtilityNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; public static final String WSSecuritySecExtNamespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; /** * The service object info. */ private static final LazyMember<ServiceObjectInfo> SERVICE_OBJECT_INFO = new LazyMember<ServiceObjectInfo>( new ILazyMember<ServiceObjectInfo>() { public ServiceObjectInfo createInstance() { return new ServiceObjectInfo(); } } ); private static final String XML_SCHEMA_DATE_FORMAT = "yyyy-MM-dd'Z'"; private static final String XML_SCHEMA_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; private static final Pattern PATTERN_TIME_SPAN = Pattern.compile("-P"); private static final Pattern PATTERN_YEAR = Pattern.compile("(\\d+)Y"); private static final Pattern PATTERN_MONTH = Pattern.compile("(\\d+)M"); private static final Pattern PATTERN_DAY = Pattern.compile("(\\d+)D"); private static final Pattern PATTERN_HOUR = Pattern.compile("(\\d+)H"); private static final Pattern PATTERN_MINUTES = Pattern.compile("(\\d+)M"); private static final Pattern PATTERN_SECONDS = Pattern.compile("(\\d+)\\."); // Need to escape dot, otherwise it matches any char private static final Pattern PATTERN_MILLISECONDS = Pattern.compile("(\\d+)S"); private EwsUtilities() { throw new UnsupportedOperationException(); } /** * Gets the builds the version. * * @return the builds the version */ public static String getBuildVersion() { return "0.0.0.0"; } /** * The enum version dictionaries. */ private static final LazyMember<Map<Class<?>, Map<String, ExchangeVersion>>> ENUM_VERSION_DICTIONARIES = new LazyMember<Map<Class<?>, Map<String, ExchangeVersion>>>( new ILazyMember<Map<Class<?>, Map<String, ExchangeVersion>>>() { @Override public Map<Class<?>, Map<String, ExchangeVersion>> createInstance() { Map<Class<?>, Map<String, ExchangeVersion>> enumDicts = new HashMap<Class<?>, Map<String, ExchangeVersion>>(); enumDicts.put(WellKnownFolderName.class, buildEnumDict(WellKnownFolderName.class)); enumDicts.put(ItemTraversal.class, buildEnumDict(ItemTraversal.class)); enumDicts.put(FileAsMapping.class, buildEnumDict(FileAsMapping.class)); enumDicts.put(EventType.class, buildEnumDict(EventType.class)); enumDicts.put(MeetingRequestsDeliveryScope.class, buildEnumDict(MeetingRequestsDeliveryScope. class)); return enumDicts; } }); /** * Dictionary of enum type to schema-name-to-enum-value maps. */ private static final LazyMember<Map<Class<?>, Map<String, String>>> SCHEMA_TO_ENUM_DICTIONARIES = new LazyMember<Map<Class<?>, Map<String, String>>>( new ILazyMember<Map<Class<?>, Map<String, String>>>() { @Override public Map<Class<?>, Map<String, String>> createInstance() { Map<Class<?>, Map<String, String>> enumDicts = new HashMap<Class<?>, Map<String, String>>(); enumDicts.put(EventType.class, buildSchemaToEnumDict(EventType.class)); enumDicts.put(MailboxType.class, buildSchemaToEnumDict(MailboxType.class)); enumDicts.put(FileAsMapping.class, buildSchemaToEnumDict(FileAsMapping.class)); enumDicts.put(RuleProperty.class, buildSchemaToEnumDict(RuleProperty.class)); return enumDicts; } }); /** * Dictionary of enum type to enum-value-to-schema-name maps. */ public static final LazyMember<Map<Class<?>, Map<String, String>>> ENUM_TO_SCHEMA_DICTIONARIES = new LazyMember<Map<Class<?>, Map<String, String>>>( new ILazyMember<Map<Class<?>, Map<String, String>>>() { @Override public Map<Class<?>, Map<String, String>> createInstance() { Map<Class<?>, Map<String, String>> enumDicts = new HashMap<Class<?>, Map<String, String>>(); enumDicts.put(EventType.class, buildEnumToSchemaDict(EventType.class)); enumDicts.put(MailboxType.class, buildEnumToSchemaDict(MailboxType.class)); enumDicts.put(FileAsMapping.class, buildEnumToSchemaDict(FileAsMapping.class)); enumDicts.put(RuleProperty.class, buildEnumToSchemaDict(RuleProperty.class)); return enumDicts; } }); /** * Regular expression for legal domain names. */ public static final String DomainRegex = "^[-a-zA-Z0-9_.]+$"; /** * Asserts that the specified condition if true. * * @param condition Assertion. * @param caller The caller. * @param message The message to use if assertion fails. */ public static void ewsAssert( final boolean condition, final String caller, final String message ) { if (!condition) { throw new RuntimeException(String.format("[%s] %s", caller, message)); } } /** * Gets the namespace prefix from an XmlNamespace enum value. * * @param xmlNamespace The XML namespace * @return Namespace prefix string. */ public static String getNamespacePrefix(XmlNamespace xmlNamespace) { return xmlNamespace.getNameSpacePrefix(); } /** * Gets the namespace URI from an XmlNamespace enum value. * * @param xmlNamespace The XML namespace. * @return Uri as string */ public static String getNamespaceUri(XmlNamespace xmlNamespace) { return xmlNamespace.getNameSpaceUri(); } /** * Gets the namespace from uri. * * @param namespaceUri the namespace uri * @return the namespace from uri */ public static XmlNamespace getNamespaceFromUri(String namespaceUri) { if (EwsErrorsNamespace.equals(namespaceUri)) { return XmlNamespace.Errors; } else if (EwsTypesNamespace.equals(namespaceUri)) { return XmlNamespace.Types; } else if (EwsMessagesNamespace.equals(namespaceUri)) { return XmlNamespace.Messages; } else if (EwsSoapNamespace.equals(namespaceUri)) { return XmlNamespace.Soap; } else if (EwsSoap12Namespace.equals(namespaceUri)) { return XmlNamespace.Soap12; } else if (EwsXmlSchemaInstanceNamespace.equals(namespaceUri)) { return XmlNamespace.XmlSchemaInstance; } else if (PassportSoapFaultNamespace.equals(namespaceUri)) { return XmlNamespace.PassportSoapFault; } else if (WSTrustFebruary2005Namespace.equals(namespaceUri)) { return XmlNamespace.WSTrustFebruary2005; } else if (WSAddressingNamespace.equals(namespaceUri)) { return XmlNamespace.WSAddressing; } else { return XmlNamespace.NotSpecified; } } /** * Creates the ews object from xml element name. * * @param <TServiceObject> the generic type * @param itemClass the item class * @param service the service * @param xmlElementName the xml element name * @return the t service object * @throws Exception the exception */ @SuppressWarnings("unchecked") public static <TServiceObject extends ServiceObject> TServiceObject createEwsObjectFromXmlElementName( Class<?> itemClass, ExchangeService service, String xmlElementName) throws Exception { final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); final Map<String, Class<?>> map = member.getXmlElementNameToServiceObjectClassMap(); final Class<?> ic = map.get(xmlElementName); if (ic != null) { final Map<Class<?>, ICreateServiceObjectWithServiceParam> serviceParam = member.getServiceObjectConstructorsWithServiceParam(); final ICreateServiceObjectWithServiceParam creationDelegate = serviceParam.get(ic); if (creationDelegate != null) { return (TServiceObject) creationDelegate .createServiceObjectWithServiceParam(service); } else { throw new IllegalArgumentException("No appropriate constructor could be found for this item class."); } } return (TServiceObject) itemClass.newInstance(); } /** * Creates the item from item class. * * @param itemAttachment the item attachment * @param itemClass the item class * @param isNew the is new * @return the item * @throws Exception the exception */ public static Item createItemFromItemClass( ItemAttachment itemAttachment, Class<?> itemClass, boolean isNew) throws Exception { final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); final Map<Class<?>, ICreateServiceObjectWithAttachmentParam> dataMap = member.getServiceObjectConstructorsWithAttachmentParam(); final ICreateServiceObjectWithAttachmentParam creationDelegate = dataMap.get(itemClass); if (creationDelegate != null) { return (Item) creationDelegate .createServiceObjectWithAttachmentParam(itemAttachment, isNew); } throw new IllegalArgumentException("No appropriate constructor could be found for this item class."); } /** * Creates the item from xml element name. * * @param itemAttachment the item attachment * @param xmlElementName the xml element name * @return the item * @throws Exception the exception */ public static Item createItemFromXmlElementName( ItemAttachment itemAttachment, String xmlElementName) throws Exception { final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); final Map<String, Class<?>> map = member.getXmlElementNameToServiceObjectClassMap(); final Class<?> itemClass = map.get(xmlElementName); if (itemClass != null) { return createItemFromItemClass(itemAttachment, itemClass, false); } return null; } public static Class<?> getItemTypeFromXmlElementName(String xmlElementName) { final ServiceObjectInfo member = EwsUtilities.SERVICE_OBJECT_INFO.getMember(); final Map<String, Class<?>> map = member.getXmlElementNameToServiceObjectClassMap(); return map.get(xmlElementName); } /** * Finds the first item of type TItem (not a descendant type) in the * specified collection. * * @param <TItem> TItem is the type of the item to find. * @param cls the cls * @param items the item * @return A TItem instance or null if no instance of TItem could be found. */ @SuppressWarnings("unchecked") public static <TItem extends Item> TItem findFirstItemOfType( Class<TItem> cls, Iterable<Item> items ) { for (Item item : items) { // We're looking for an exact class match here. final Class<? extends Item> itemClass = item.getClass(); if (itemClass.equals(cls)) { return (TItem) item; } } return null; } /** * Write trace start element. * * @param writer the writer to write the start element to * @param traceTag the trace tag * @param includeVersion if true, include build version attribute * @throws XMLStreamException the XML stream exception */ private static void writeTraceStartElement( XMLStreamWriter writer, String traceTag, boolean includeVersion) throws XMLStreamException { writer.writeStartElement("Trace"); writer.writeAttribute("Tag", traceTag); writer.writeAttribute("Tid", Thread.currentThread().getId() + ""); Date d = new Date(); DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss'Z'"); df.setTimeZone(TimeZone.getTimeZone("UTC")); String formattedString = df.format(d); writer.writeAttribute("Time", formattedString); if (includeVersion) { writer.writeAttribute("Version", EwsUtilities.getBuildVersion()); } } /** * . * * @param entryKind the entry kind * @param logEntry the log entry * @return the string * @throws XMLStreamException the XML stream exception * @throws IOException signals that an I/O exception has occurred. */ public static String formatLogMessage(String entryKind, String logEntry) throws XMLStreamException, IOException { String lineSeparator = System.getProperty("line.separator"); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); XMLOutputFactory factory = XMLOutputFactory.newInstance(); XMLStreamWriter writer = factory.createXMLStreamWriter(outStream); EwsUtilities.writeTraceStartElement(writer, entryKind, false); writer.writeCharacters(lineSeparator); writer.writeCharacters(logEntry); writer.writeCharacters(lineSeparator); writer.writeEndElement(); writer.writeCharacters(lineSeparator); writer.flush(); writer.close(); outStream.flush(); String formattedLogMessage = outStream.toString(); formattedLogMessage = formattedLogMessage.replaceAll("'", "'"); formattedLogMessage = formattedLogMessage.replaceAll(""", "\""); formattedLogMessage = formattedLogMessage.replaceAll(">", ">"); formattedLogMessage = formattedLogMessage.replaceAll("<", "<"); formattedLogMessage = formattedLogMessage.replaceAll("&", "&"); outStream.close(); return formattedLogMessage; } /** * Format http response headers. * * @param response the response * @return the string * @throws EWSHttpException the EWS http exception */ public static String formatHttpResponseHeaders(HttpWebRequest response) throws EWSHttpException { final int code = response.getResponseCode(); final String contentType = response.getResponseContentType(); final Map<String, String> headers = response.getResponseHeaders(); return code + " " + contentType + "\n" + EwsUtilities.formatHttpHeaders(headers) + "\n"; } /** * Format request HTTP headers. * * @param request The HTTP request. */ public static String formatHttpRequestHeaders(HttpWebRequest request) throws URISyntaxException, EWSHttpException { final String method = request.getRequestMethod().toUpperCase(); final String path = request.getUrl().toURI().getPath(); final Map<String, String> property = request.getRequestProperty(); final String headers = EwsUtilities.formatHttpHeaders(property); return String.format("%s %s HTTP/%s\n", method, path, "1.1") + headers + "\n"; } /** * Formats HTTP headers. * * @param headers The headers. * @return Headers as a string */ private static String formatHttpHeaders(Map<String, String> headers) { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> header : headers.entrySet()) { sb.append(String.format("%s : %s\n", header.getKey(), header.getValue())); } return sb.toString(); } /** * Format XML content in a MemoryStream for message. * * @param traceTypeStr Kind of the entry. * @param stream The memory stream. * @return XML log entry as a string. */ public static String formatLogMessageWithXmlContent(String traceTypeStr, ByteArrayOutputStream stream) { try { return formatLogMessage(traceTypeStr, stream.toString()); } catch (Exception e) { return stream.toString(); } } /** * Convert bool to XML Schema bool. * * @param value Bool value. * @return String representing bool value in XML Schema. */ public static String boolToXSBool(Boolean value) { return value ? EwsUtilities.XSTrue : EwsUtilities.XSFalse; } /** * Parses an enum value list. * * @param <T> the generic type * @param c the c * @param list the list * @param value the value * @param separators the separators */ public static <T extends Enum<?>> void parseEnumValueList(Class<T> c, List<T> list, String value, char... separators) { EwsUtilities.ewsAssert(c.isEnum(), "EwsUtilities.ParseEnumValueList", "T is not an enum type."); StringBuilder regexp = new StringBuilder(); regexp.append("["); for (char s : separators) { regexp.append("["); regexp.append(Pattern.quote(s + "")); regexp.append("]"); } regexp.append("]"); String[] enumValues = value.split(regexp.toString()); for (String enumValue : enumValues) { for (T o : c.getEnumConstants()) { if (o.toString().equals(enumValue)) { list.add(o); } } } } /** * Converts an enum to a string, using the mapping dictionaries if * appropriate. * * @param value The enum value to be serialized * @return String representation of enum to be used in the protocol */ public static String serializeEnum(Object value) { String strValue = value.toString(); final Map<Class<?>, Map<String, String>> member = ENUM_TO_SCHEMA_DICTIONARIES.getMember(); final Map<String, String> enumToStringDict = member.get(value.getClass()); if (enumToStringDict != null) { final Enum<?> e = (Enum<?>) value; final String enumStr = enumToStringDict.get(e.name()); if (enumStr != null) { strValue = enumStr; } } return strValue; } /** * Parses the. * * @param <T> the generic type * @param cls the cls * @param value the value * @return the t * @throws java.text.ParseException the parse exception */ @SuppressWarnings("unchecked") public static <T> T parse(Class<T> cls, String value) throws ParseException { if (cls.isEnum()) { final Map<Class<?>, Map<String, String>> member = SCHEMA_TO_ENUM_DICTIONARIES.getMember(); String val = value; final Map<String, String> stringToEnumDict = member.get(cls); if (stringToEnumDict != null) { final String strEnumName = stringToEnumDict.get(value); if (strEnumName != null) { val = strEnumName; } } for (T o : cls.getEnumConstants()) { if (o.toString().equals(val)) { return o; } } return null; }else if (Number.class.isAssignableFrom(cls)){ if (Double.class.isAssignableFrom(cls)){ return (T) ((Double) Double.parseDouble(value)); }else if (Integer.class.isAssignableFrom(cls)) { return (T) ((Integer) Integer.parseInt(value)); }else if (Long.class.isAssignableFrom(cls)){ return (T) ((Long) Long.parseLong(value)); }else if (Float.class.isAssignableFrom(cls)){ return (T) ((Float) Float.parseFloat(value)); }else if (Byte.class.isAssignableFrom(cls)){ return (T) ((Byte) Byte.parseByte(value)); }else if (Short.class.isAssignableFrom(cls)){ return (T) ((Short) Short.parseShort(value)); }else if (BigInteger.class.isAssignableFrom(cls)){ return (T) (new BigInteger(value)); }else if (BigDecimal.class.isAssignableFrom(cls)){ return (T) (new BigDecimal(value)); } } else if (Date.class.isAssignableFrom(cls)) { DateFormat df = createDateFormat(XML_SCHEMA_DATE_TIME_FORMAT); return (T) df.parse(value); } else if (Boolean.class.isAssignableFrom(cls)) { return (T) ((Boolean) Boolean.parseBoolean(value)); } else if (String.class.isAssignableFrom(cls)) { return (T) value; } return null; } /** * Builds the schema to enum mapping dictionary. * * @param <E> Type of the enum. * @param c Class * @return The mapping from enum to schema name */ private static <E extends Enum<E>> Map<String, String> buildSchemaToEnumDict(Class<E> c) { Map<String, String> dict = new HashMap<String, String>(); Field[] fields = c.getDeclaredFields(); for (Field f : fields) { if (f.isEnumConstant() && f.isAnnotationPresent(EwsEnum.class)) { EwsEnum ewsEnum = f.getAnnotation(EwsEnum.class); String fieldName = f.getName(); String schemaName = ewsEnum.schemaName(); if (!schemaName.isEmpty()) { dict.put(schemaName, fieldName); } } } return dict; } /** * Validate param collection. * * @param eventTypes the event types * @param paramName the param name * @throws Exception the exception */ public static void validateParamCollection(EventType[] eventTypes, String paramName) throws Exception { validateParam(eventTypes, paramName); int count = 0; for (EventType event : eventTypes) { try { validateParam(event, String.format("collection[%d] , ", count)); } catch (Exception e) { throw new IllegalArgumentException(String.format( "The element at position %d is invalid", count), e); } count++; } if (count == 0) { throw new IllegalArgumentException( String.format("The collection \"%s\" is empty.", paramName) ); } } /** * Convert DateTime to XML Schema date. * * @param date the date * @return String representation of DateTime. */ public static String dateTimeToXSDate(Date date) { return formatDate(date, XML_SCHEMA_DATE_FORMAT); } /** * Dates the DateTime into an XML schema date time. * * @param date the date * @return String representation of DateTime. */ public static String dateTimeToXSDateTime(Date date) { return formatDate(date, XML_SCHEMA_DATE_TIME_FORMAT); } /** * Takes a System.TimeSpan structure and converts it into an xs:duration * string as defined by the W3 Consortiums Recommendation * "XML Schema Part 2: Datatypes Second Edition", * http://www.w3.org/TR/xmlschema-2/#duration * * @param timeOffset structure to convert * @return xs:duration formatted string */ public static String getTimeSpanToXSDuration(TimeSpan timeOffset) { // Optional '-' offset String offsetStr = (timeOffset.getTotalSeconds() < 0) ? "-" : ""; long days = Math.abs(timeOffset.getDays()); long hours = Math.abs(timeOffset.getHours()); long minutes = Math.abs(timeOffset.getMinutes()); long seconds = Math.abs(timeOffset.getSeconds()); long milliseconds = Math.abs(timeOffset.getMilliseconds()); // The TimeSpan structure does not have a Year or Month // property, therefore we wouldn't be able to return an xs:duration // string from a TimeSpan that included the nY or nM components. return offsetStr + "P" + days + "DT" + hours + "H" + minutes + "M" + seconds + "." + milliseconds + "S"; } /** * Takes an xs:duration string as defined by the W3 Consortiums * Recommendation "XML Schema Part 2: Datatypes Second Edition", * http://www.w3.org/TR/xmlschema-2/#duration, and converts it into a * System.TimeSpan structure This method uses the following approximations: * 1 year = 365 days 1 month = 30 days Additionally, it only allows for four * decimal points of seconds precision. * * @param xsDuration xs:duration string to convert * @return System.TimeSpan structure */ public static TimeSpan getXSDurationToTimeSpan(String xsDuration) { // TODO: Need to check whether this should be the equivalent or not Matcher m = PATTERN_TIME_SPAN.matcher(xsDuration); boolean negative = false; if (m.find()) { negative = true; } // Removing leading '-' if (negative) { xsDuration = xsDuration.replace("-P", "P"); } Period period = Period.parse(xsDuration, ISOPeriodFormat.standard()); long retval = period.toStandardDuration().getMillis(); if (negative) { retval = -retval; } return new TimeSpan(retval); } /** * Time span to xs time. * * @param timeSpan the time span * @return the string */ public static String timeSpanToXSTime(TimeSpan timeSpan) { DecimalFormat myFormatter = new DecimalFormat("00"); return String.format("%s:%s:%s", myFormatter.format(timeSpan.getHours()), myFormatter.format(timeSpan .getMinutes()), myFormatter.format(timeSpan.getSeconds())); } /** * Gets the domain name from an email address. * * @param emailAddress The email address. * @return Domain name. * @throws FormatException the format exception */ public static String domainFromEmailAddress(String emailAddress) throws FormatException { String[] emailAddressParts = emailAddress.split("@"); if (emailAddressParts.length != 2 || (emailAddressParts[1] == null || emailAddressParts[1] .isEmpty())) { throw new FormatException("The e-mail address is formed incorrectly."); } return emailAddressParts[1]; } public static int getDim(Object array) { int dim = 0; Class<?> c = array.getClass(); while (c.isArray()) { c = c.getComponentType(); dim++; } return (dim); } /** * Validates parameter (and allows null value). * * @param param The param. * @param paramName Name of the param. * @throws Exception the exception */ public static void validateParamAllowNull(Object param, String paramName) throws Exception { if (param instanceof ISelfValidate) { ISelfValidate selfValidate = (ISelfValidate) param; try { selfValidate.validate(); } catch (ServiceValidationException e) { throw new Exception(String.format("%s %s", "Validation failed.", paramName), e); } } if (param instanceof ServiceObject) { ServiceObject ewsObject = (ServiceObject) param; if (ewsObject.isNew()) { throw new Exception(String.format("%s %s", "This service object doesn't have an ID.", paramName)); } } } /** * Validates parameter (null value not allowed). * * @param param The param. * @param paramName Name of the param. * @throws Exception the exception */ public static void validateParam(Object param, String paramName) throws Exception { boolean isValid; if (param instanceof String) { String strParam = (String) param; isValid = !strParam.isEmpty(); } else { isValid = param != null; } if (!isValid) { throw new Exception(String.format("Argument %s not valid", paramName)); } validateParamAllowNull(param, paramName); } /** * Validates parameter collection. * * @param <T> the generic type * @param collection The collection. * @param paramName Name of the param. * @throws Exception the exception */ public static <T> void validateParamCollection(Iterator<T> collection, String paramName) throws Exception { validateParam(collection, paramName); int count = 0; while (collection.hasNext()) { T obj = collection.next(); try { validateParam(obj, String.format("collection[%d],", count)); } catch (Exception e) { throw new IllegalArgumentException(String.format( "The element at position %d is invalid", count), e); } count++; } if (count == 0) { throw new IllegalArgumentException( String.format("The collection \"%s\" is empty.", paramName) ); } } /** * Validates string parameter to be non-empty string (null value allowed). * * @param param The string parameter. * @param paramName Name of the parameter. * @throws ArgumentException * @throws ServiceLocalException */ public static void validateNonBlankStringParamAllowNull(String param, String paramName) throws ArgumentException, ServiceLocalException { if (param != null) { // Non-empty string has at least one character //which is *not* a whitespace character if (param.length() == countMatchingChars(param, new IPredicate<Character>() { @Override public boolean predicate(Character obj) { return Character.isWhitespace(obj); } })) { throw new ArgumentException("The string argument contains only white space characters.", paramName); } } } /** * Validates string parameter to be * non-empty string (null value not allowed). * * @param param The string parameter. * @param paramName Name of the parameter. * @throws ArgumentNullException * @throws ArgumentException * @throws ServiceLocalException */ public static void validateNonBlankStringParam(String param, String paramName) throws ArgumentNullException, ArgumentException, ServiceLocalException { if (param == null) { throw new ArgumentNullException(paramName); } validateNonBlankStringParamAllowNull(param, paramName); } /** * Validate enum version value. * * @param enumValue the enum value * @param requestVersion the request version * @throws ServiceVersionException the service version exception */ public static void validateEnumVersionValue(Enum<?> enumValue, ExchangeVersion requestVersion) throws ServiceVersionException { final Map<Class<?>, Map<String, ExchangeVersion>> member = ENUM_VERSION_DICTIONARIES.getMember(); final Map<String, ExchangeVersion> enumVersionDict = member.get(enumValue.getClass()); final ExchangeVersion enumVersion = enumVersionDict.get(enumValue.toString()); if (enumVersion != null) { final int i = requestVersion.compareTo(enumVersion); if (i < 0) { throw new ServiceVersionException( String.format( "Enumeration value %s in enumeration type %s is only valid for Exchange version %s or later.", enumValue.toString(), enumValue.getClass().getName(), enumVersion ) ); } } } /** * Validates service object version against the request version. * * @param serviceObject The service object. * @param requestVersion The request version. * @throws ServiceVersionException Raised if this service object type requires a later version * of Exchange. */ public static void validateServiceObjectVersion( ServiceObject serviceObject, ExchangeVersion requestVersion) throws ServiceVersionException { ExchangeVersion minimumRequiredServerVersion = serviceObject .getMinimumRequiredServerVersion(); if (requestVersion.ordinal() < minimumRequiredServerVersion.ordinal()) { String msg = String.format( "The object type %s is only valid for Exchange Server version %s or later versions.", serviceObject.getClass().getName(), minimumRequiredServerVersion.toString()); throw new ServiceVersionException(msg); } } /** * Validates property version against the request version. * * @param service The Exchange service. * @param minimumServerVersion The minimum server version * @param propertyName The property name * @throws ServiceVersionException The service version exception */ public static void validatePropertyVersion( ExchangeService service, ExchangeVersion minimumServerVersion, String propertyName) throws ServiceVersionException { if (service.getRequestedServerVersion().ordinal() < minimumServerVersion.ordinal()) { throw new ServiceVersionException( String.format("The property %s is valid only for Exchange %s or later versions.", propertyName, minimumServerVersion)); } } /** * Validate method version. * * @param service the service * @param minimumServerVersion the minimum server version * @param methodName the method name * @throws ServiceVersionException the service version exception */ public static void validateMethodVersion(ExchangeService service, ExchangeVersion minimumServerVersion, String methodName) throws ServiceVersionException { if (service.getRequestedServerVersion().ordinal() < minimumServerVersion.ordinal()) { throw new ServiceVersionException(String.format( "Method %s is only valid for Exchange Server version %s or later.", methodName, minimumServerVersion)); } } /** * Validates class version against the request version. * * @param service the service * @param minimumServerVersion The minimum server version that supports the method. * @param className Name of the class. * @throws ServiceVersionException */ public static void validateClassVersion( ExchangeService service, ExchangeVersion minimumServerVersion, String className) throws ServiceVersionException { if (service.getRequestedServerVersion().ordinal() < minimumServerVersion.ordinal()) { throw new ServiceVersionException( String.format("Class %s is only valid for Exchange version %s or later.", className, minimumServerVersion)); } } /** * Validates domain name (null value allowed) * * @param domainName Domain name. * @param paramName Parameter name. * @throws ArgumentException */ public static void validateDomainNameAllowNull(String domainName, String paramName) throws ArgumentException { if (domainName != null) { Pattern domainNamePattern = Pattern.compile(DomainRegex); Matcher domainNameMatcher = domainNamePattern.matcher(domainName); if (!domainNameMatcher.find()) { throw new ArgumentException(String.format("'%s' is not a valid domain name.", domainName), paramName); } } } /** * Builds the enum dict. * * @param <E> the element type * @param c the c * @return the map */ private static <E extends Enum<E>> Map<String, ExchangeVersion> buildEnumDict(Class<E> c) { Map<String, ExchangeVersion> dict = new HashMap<String, ExchangeVersion>(); Field[] fields = c.getDeclaredFields(); for (Field f : fields) { if (f.isEnumConstant() && f.isAnnotationPresent(RequiredServerVersion.class)) { RequiredServerVersion ewsEnum = f .getAnnotation(RequiredServerVersion.class); String fieldName = f.getName(); ExchangeVersion exchangeVersion = ewsEnum.version(); dict.put(fieldName, exchangeVersion); } } return dict; } /** * Builds the enum to schema mapping dictionary. * * @param c class type * @return The mapping from enum to schema name */ private static Map<String, String> buildEnumToSchemaDict(Class<?> c) { Map<String, String> dict = new HashMap<String, String>(); Field[] fields = c.getFields(); for (Field f : fields) { if (f.isEnumConstant() && f.isAnnotationPresent(EwsEnum.class)) { EwsEnum ewsEnum = f.getAnnotation(EwsEnum.class); String fieldName = f.getName(); String schemaName = ewsEnum.schemaName(); if (!schemaName.isEmpty()) { dict.put(fieldName, schemaName); } } } return dict; } /** * Gets the enumerated object count. * * @param <T> the generic type * @param objects The objects. * @return Count of objects in iterator. */ public static <T> int getEnumeratedObjectCount(Iterator<T> objects) { int count = 0; while (objects != null && objects.hasNext()) { objects.next(); count++; } return count; } /** * Gets the enumerated object at. * * @param <T> the generic type * @param objects the objects * @param index the index * @return the enumerated object at */ public static <T> Object getEnumeratedObjectAt(Iterable<T> objects, int index) { int count = 0; for (Object obj : objects) { if (count == index) { return obj; } count++; } throw new IndexOutOfBoundsException("The IEnumerable doesn't contain that many objects."); } /** * Count characters in string that match a condition. * * @param str The string. * @param charPredicate Predicate to evaluate for each character in the string. * @return Count of characters that match condition expressed by predicate. * @throws ServiceLocalException */ public static int countMatchingChars( String str, IPredicate<Character> charPredicate ) throws ServiceLocalException { int count = 0; for (int i = 0; i < str.length(); i++) { if (charPredicate.predicate(str.charAt(i))) { count++; } } return count; } /** * Determines whether every element in the collection * matches the conditions defined by the specified predicate. * * @param <T> Entry type. * @param collection The collection. * @param predicate Predicate that defines the conditions to check against the elements. * @return True if every element in the collection matches * the conditions defined by the specified predicate; otherwise, false. * @throws ServiceLocalException */ public static <T> boolean trueForAll(Iterable<T> collection, IPredicate<T> predicate) throws ServiceLocalException { for (T entry : collection) { if (!predicate.predicate(entry)) { return false; } } return true; } /** * Call an action for each member of a collection. * * @param <T> Collection element type. * @param collection The collection. * @param action The action to apply. */ public static <T> void forEach(Iterable<T> collection, IAction<T> action) { for (T entry : collection) { action.action(entry); } } private static String formatDate(Date date, String format) { final DateFormat utcFormatter = createDateFormat(format); return utcFormatter.format(date); } private static DateFormat createDateFormat(String format) { final DateFormat utcFormatter = new SimpleDateFormat(format); utcFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); return utcFormatter; } }