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.core.request.ServiceRequestBase; 028import microsoft.exchange.webservices.data.core.enumeration.property.BasePropertySet; 029import microsoft.exchange.webservices.data.core.enumeration.property.BodyType; 030import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion; 031import microsoft.exchange.webservices.data.core.enumeration.property.PropertyDefinitionFlags; 032import microsoft.exchange.webservices.data.core.enumeration.service.ServiceObjectType; 033import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; 034import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException; 035import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException; 036import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException; 037import microsoft.exchange.webservices.data.property.definition.PropertyDefinition; 038import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase; 039 040import javax.xml.stream.XMLStreamException; 041 042import java.util.ArrayList; 043import java.util.Arrays; 044import java.util.HashMap; 045import java.util.Iterator; 046import java.util.List; 047import java.util.Map; 048 049/** 050 * Represents a set of item or folder property. Property sets are used to 051 * indicate what property of an item or folder should be loaded when binding 052 * to an existing item or folder or when loading an item or folder's property. 053 */ 054public final class PropertySet implements ISelfValidate, 055 Iterable<PropertyDefinitionBase> { 056 057 /** 058 * The Constant IdOnly. 059 */ 060 public static final PropertySet IdOnly = PropertySet. 061 createReadonlyPropertySet(BasePropertySet.IdOnly); 062 063 /** 064 * Returns a predefined property set that only includes the Id property. 065 * 066 * @return Returns a predefined property set that only includes the Id 067 * property. 068 */ 069 public static PropertySet getIdOnly() { 070 return IdOnly; 071 } 072 073 /** 074 * The Constant FirstClassProperties. 075 */ 076 public static final PropertySet FirstClassProperties = PropertySet. 077 createReadonlyPropertySet(BasePropertySet.FirstClassProperties); 078 079 /** 080 * Returns a predefined property set that includes the first class 081 * property of an item or folder. 082 * 083 * @return A predefined property set that includes the first class 084 * property of an item or folder. 085 */ 086 public static PropertySet getFirstClassProperties() { 087 return FirstClassProperties; 088 } 089 090 /** 091 * Maps BasePropertySet values to EWS's BaseShape values. 092 */ 093 private static LazyMember<Map<BasePropertySet, String>> defaultPropertySetMap = 094 new LazyMember<Map<BasePropertySet, String>>(new 095 ILazyMember<Map<BasePropertySet, String>>() { 096 @Override 097 public Map<BasePropertySet, String> createInstance() { 098 Map<BasePropertySet, String> result = new 099 HashMap<BasePropertySet, String>(); 100 result.put(BasePropertySet.IdOnly, 101 BasePropertySet.IdOnly 102 .getBaseShapeValue()); 103 result.put(BasePropertySet.FirstClassProperties, 104 BasePropertySet.FirstClassProperties 105 .getBaseShapeValue()); 106 return result; 107 } 108 }); 109 /** 110 * The base property set this property set is based upon. 111 */ 112 private BasePropertySet basePropertySet; 113 114 /** 115 * The list of additional property included in this property set. 116 */ 117 private List<PropertyDefinitionBase> additionalProperties = new 118 ArrayList<PropertyDefinitionBase>(); 119 120 /** 121 * The requested body type for get and find operations. If null, the 122 * "best body" is returned. 123 */ 124 private BodyType requestedBodyType; 125 126 /** 127 * Value indicating whether or not the server should filter HTML content. 128 */ 129 private Boolean filterHtml; 130 131 /** 132 * Value indicating whether or not the server 133 * should convert HTML code page to UTF8. 134 */ 135 private Boolean convertHtmlCodePageToUTF8; 136 137 /** 138 * Value indicating whether or not this PropertySet can be modified. 139 */ 140 private boolean isReadOnly; 141 142 /** 143 * Initializes a new instance of PropertySet. 144 * 145 * @param basePropertySet The base property set to base the property set upon. 146 * @param additionalProperties Additional property to include in the property set. Property 147 * definitions are available as static members from schema 148 * classes (for example, EmailMessageSchema.Subject, 149 * AppointmentSchema.Start, ContactSchema.GivenName, etc.) 150 */ 151 public PropertySet(BasePropertySet basePropertySet, 152 PropertyDefinitionBase... additionalProperties) { 153 this.basePropertySet = basePropertySet; 154 if (null != additionalProperties) { 155 this.additionalProperties.addAll(Arrays.asList(additionalProperties)); 156 } 157 } 158 159 /** 160 * Initializes a new instance of PropertySet. 161 * 162 * @param basePropertySet The base property set to base the property set upon. 163 * @param additionalProperties Additional property to include in the property set. Property 164 * definitions are available as static members from schema 165 * classes (for example, EmailMessageSchema.Subject, 166 * AppointmentSchema.Start, ContactSchema.GivenName, etc.) 167 */ 168 public PropertySet(BasePropertySet basePropertySet, 169 Iterator<PropertyDefinitionBase> additionalProperties) { 170 this.basePropertySet = basePropertySet; 171 if (null != additionalProperties) { 172 while (additionalProperties.hasNext()) { 173 this.additionalProperties.add(additionalProperties.next()); 174 } 175 } 176 } 177 178 /** 179 * Initializes a new instance of PropertySet based upon 180 * BasePropertySet.IdOnly. 181 */ 182 public PropertySet() { 183 this.basePropertySet = BasePropertySet.IdOnly; 184 } 185 186 /** 187 * Initializes a new instance of PropertySet. 188 * 189 * @param basePropertySet The base property set to base the property set upon. 190 */ 191 public PropertySet(BasePropertySet basePropertySet) { 192 this.basePropertySet = basePropertySet; 193 } 194 195 /** 196 * Initializes a new instance of PropertySet based upon 197 * BasePropertySet.IdOnly. 198 * 199 * @param additionalProperties Additional property to include in the property set. Property 200 * definitions are available as static members from schema 201 * classes (for example, EmailMessageSchema.Subject, 202 * AppointmentSchema.Start, ContactSchema.GivenName, etc.) 203 */ 204 public PropertySet(PropertyDefinitionBase... additionalProperties) { 205 this(BasePropertySet.IdOnly, additionalProperties); 206 } 207 208 /** 209 * Initializes a new instance of PropertySet based upon 210 * BasePropertySet.IdOnly. 211 * 212 * @param additionalProperties Additional property to include in the property set. Property 213 * definitions are available as static members from schema 214 * classes (for example, EmailMessageSchema.Subject, 215 * AppointmentSchema.Start, ContactSchema.GivenName, etc.) 216 */ 217 public PropertySet(Iterator<PropertyDefinitionBase> additionalProperties) { 218 this(BasePropertySet.IdOnly, additionalProperties); 219 } 220 221 /** 222 * Implements an implicit conversion between 223 * PropertySet and BasePropertySet. 224 * 225 * @param basePropertySet The BasePropertySet value to convert from. 226 * @return A PropertySet instance based on the specified base property set. 227 */ 228 public static PropertySet getPropertySetFromBasePropertySet(BasePropertySet 229 basePropertySet) { 230 return new PropertySet(basePropertySet); 231 } 232 233 234 /** 235 * Adds the specified property to the property set. 236 * 237 * @param property The property to add. 238 * @throws Exception the exception 239 */ 240 public void add(PropertyDefinitionBase property) throws Exception { 241 this.throwIfReadonly(); 242 EwsUtilities.validateParam(property, "property"); 243 244 if (!this.additionalProperties.contains(property)) { 245 this.additionalProperties.add(property); 246 } 247 } 248 249 /** 250 * Adds the specified property to the property set. 251 * 252 * @param properties The property to add. 253 * @throws Exception the exception 254 */ 255 public void addRange(Iterable<PropertyDefinitionBase> properties) 256 throws Exception { 257 this.throwIfReadonly(); 258 Iterator<PropertyDefinitionBase> property = properties.iterator(); 259 EwsUtilities.validateParamCollection(property, "property"); 260 261 for (Iterator<PropertyDefinitionBase> it = properties.iterator(); it 262 .hasNext(); ) { 263 this.add(it.next()); 264 } 265 } 266 267 /** 268 * Remove all explicitly added property from the property set. 269 */ 270 public void clear() { 271 this.throwIfReadonly(); 272 this.additionalProperties.clear(); 273 } 274 275 /** 276 * Creates a read-only PropertySet. 277 * 278 * @param basePropertySet The base property set. 279 * @return PropertySet 280 */ 281 private static PropertySet createReadonlyPropertySet( 282 BasePropertySet basePropertySet) { 283 PropertySet propertySet = new PropertySet(basePropertySet); 284 propertySet.isReadOnly = true; 285 return propertySet; 286 } 287 288 /** 289 * Throws if readonly property set. 290 */ 291 private void throwIfReadonly() { 292 if (this.isReadOnly) { 293 throw new UnsupportedOperationException("This PropertySet is read-only and can't be modified."); 294 } 295 } 296 297 /** 298 * Determines whether the specified property has been explicitly added to 299 * this property set using the Add or AddRange methods. 300 * 301 * @param property The property. 302 * @return true if this property set contains the specified property 303 * otherwise, false 304 */ 305 public boolean contains(PropertyDefinitionBase property) { 306 return this.additionalProperties.contains(property); 307 } 308 309 /** 310 * Removes the specified property from the set. 311 * 312 * @param property The property to remove. 313 * @return true if the property was successfully removed, false otherwise. 314 */ 315 public boolean remove(PropertyDefinitionBase property) { 316 this.throwIfReadonly(); 317 return this.additionalProperties.remove(property); 318 } 319 320 /** 321 * Gets the base property set, the property set is based upon. 322 * 323 * @return the base property set 324 */ 325 public BasePropertySet getBasePropertySet() { 326 return this.basePropertySet; 327 } 328 329 /** 330 * Maps BasePropertySet values to EWS's BaseShape values. 331 * 332 * @return the base property set 333 */ 334 public static LazyMember<Map<BasePropertySet, String>> getDefaultPropertySetMap() { 335 return PropertySet.defaultPropertySetMap; 336 337 } 338 339 /** 340 * Sets the base property set, the property set is based upon. 341 * 342 * @param basePropertySet Base property set. 343 */ 344 public void setBasePropertySet(BasePropertySet basePropertySet) { 345 this.throwIfReadonly(); 346 this.basePropertySet = basePropertySet; 347 } 348 349 /** 350 * Gets type of body that should be loaded on item. If RequestedBodyType 351 * is null, body is returned as HTML if available, plain text otherwise. 352 * 353 * @return the requested body type 354 */ 355 public BodyType getRequestedBodyType() { 356 return this.requestedBodyType; 357 } 358 359 /** 360 * Sets type of body that should be loaded on item. If RequestedBodyType is 361 * null, body is returned as HTML if available, plain text otherwise. 362 * 363 * @param requestedBodyType Type of body that should be loaded on item. 364 */ 365 public void setRequestedBodyType(BodyType requestedBodyType) { 366 this.throwIfReadonly(); 367 this.requestedBodyType = requestedBodyType; 368 } 369 370 /** 371 * Gets the number of explicitly added property in this set. 372 * 373 * @return the count 374 */ 375 public int getCount() { 376 return this.additionalProperties.size(); 377 } 378 379 /** 380 * Gets value indicating whether or not to filter potentially unsafe HTML 381 * content from message bodies. 382 * 383 * @return the filter html content 384 */ 385 public Boolean getFilterHtmlContent() { 386 return this.filterHtml; 387 } 388 389 /** 390 * Sets value indicating whether or not to filter potentially unsafe HTML 391 * content from message bodies. 392 * 393 * @param filterHtml true to filter otherwise false. 394 */ 395 public void setFilterHtmlContent(Boolean filterHtml) { 396 this.throwIfReadonly(); 397 this.filterHtml = filterHtml; 398 } 399 400 401 402 /** 403 * Gets value indicating whether or not to convert 404 * HTML code page to UTF8 encoding. 405 */ 406 public Boolean getConvertHtmlCodePageToUTF8() { 407 return this.convertHtmlCodePageToUTF8; 408 409 } 410 411 /** 412 * Sets value indicating whether or not to 413 * convert HTML code page to UTF8 encoding. 414 */ 415 public void setConvertHtmlCodePageToUTF8(Boolean value) { 416 this.throwIfReadonly(); 417 this.convertHtmlCodePageToUTF8 = value; 418 419 } 420 421 422 /** 423 * Gets the PropertyDefinitionBase at the specified index. 424 * 425 * @param index Index. 426 * @return the property definition base at 427 */ 428 public PropertyDefinitionBase getPropertyDefinitionBaseAt(int index) { 429 return this.additionalProperties.get(index); 430 } 431 432 433 /** 434 * Validate. 435 * 436 * @throws ServiceValidationException the service validation exception 437 */ 438 @Override 439 public void validate() throws ServiceValidationException { 440 this.internalValidate(); 441 } 442 443 /** 444 * Writes additional property to XML. 445 * 446 * @param writer The writer to write to 447 * @param propertyDefinitions The property definitions to write 448 * @throws XMLStreamException the XML stream exception 449 * @throws ServiceXmlSerializationException the service xml serialization exception 450 */ 451 public static void writeAdditionalPropertiesToXml(EwsServiceXmlWriter writer, 452 Iterator<PropertyDefinitionBase> propertyDefinitions) 453 throws XMLStreamException, ServiceXmlSerializationException { 454 writer.writeStartElement(XmlNamespace.Types, 455 XmlElementNames.AdditionalProperties); 456 457 while (propertyDefinitions.hasNext()) { 458 PropertyDefinitionBase propertyDefinition = propertyDefinitions 459 .next(); 460 propertyDefinition.writeToXml(writer); 461 } 462 463 writer.writeEndElement(); 464 } 465 466 /** 467 * Validates this property set. 468 * 469 * @throws ServiceValidationException the service validation exception 470 */ 471 public void internalValidate() throws ServiceValidationException { 472 for (int i = 0; i < this.additionalProperties.size(); i++) { 473 if (this.additionalProperties.get(i) == null) { 474 throw new ServiceValidationException(String.format("The additional property at index %d is null.", i)); 475 } 476 } 477 } 478 479 /** 480 * Validates this property set instance for request to ensure that: 1. 481 * Properties are valid for the request server version 2. If only summary 482 * property are legal for this request (e.g. FindItem) then only summary 483 * property were specified. 484 * 485 * @param request The request. 486 * @param summaryPropertiesOnly if set to true then only summary property are allowed. 487 * @throws ServiceVersionException the service version exception 488 * @throws ServiceValidationException the service validation exception 489 */ 490 public void validateForRequest(ServiceRequestBase request, boolean summaryPropertiesOnly) throws ServiceVersionException, 491 ServiceValidationException { 492 for (PropertyDefinitionBase propDefBase : this.additionalProperties) { 493 if (propDefBase instanceof PropertyDefinition) { 494 PropertyDefinition propertyDefinition = 495 (PropertyDefinition) propDefBase; 496 if (propertyDefinition.getVersion().ordinal() > request 497 .getService().getRequestedServerVersion().ordinal()) { 498 throw new ServiceVersionException(String.format( 499 "The property %s is valid only for Exchange %s or later versions.", 500 propertyDefinition.getName(), propertyDefinition 501 .getVersion())); 502 } 503 504 if (summaryPropertiesOnly && 505 !propertyDefinition.hasFlag( 506 PropertyDefinitionFlags.CanFind, request. 507 getService().getRequestedServerVersion())) { 508 throw new ServiceValidationException(String.format("The property %s can't be used in %s request.", 509 propertyDefinition.getName(), request 510 .getXmlElementName())); 511 } 512 } 513 } 514 if (this.getFilterHtmlContent() != null) { 515 if (request.getService().getRequestedServerVersion().compareTo(ExchangeVersion.Exchange2010) < 0) { 516 throw new ServiceVersionException( 517 String.format("The property %s is valid only for Exchange %s or later versions.", 518 "FilterHtmlContent", 519 ExchangeVersion.Exchange2010)); 520 } 521 } 522 523 if (this.getConvertHtmlCodePageToUTF8() != null) { 524 if (request.getService().getRequestedServerVersion().compareTo(ExchangeVersion.Exchange2010_SP1) < 0) { 525 throw new ServiceVersionException( 526 String.format("The property %s is valid only for Exchange %s or later versions.", 527 "ConvertHtmlCodePageToUTF8", 528 ExchangeVersion.Exchange2010_SP1)); 529 } 530 } 531 } 532 533 /** 534 * Writes the property set to XML. 535 * 536 * @param writer The writer to write to 537 * @param serviceObjectType The type of service object the property set is emitted for 538 * @throws XMLStreamException the XML stream exception 539 * @throws ServiceXmlSerializationException the service xml serialization exception 540 */ 541 public void writeToXml(EwsServiceXmlWriter writer, ServiceObjectType serviceObjectType) throws XMLStreamException, ServiceXmlSerializationException { 542 writer 543 .writeStartElement( 544 XmlNamespace.Messages, 545 serviceObjectType == ServiceObjectType.Item ? 546 XmlElementNames.ItemShape 547 : XmlElementNames.FolderShape); 548 549 writer.writeElementValue(XmlNamespace.Types, XmlElementNames.BaseShape, 550 this.getBasePropertySet().getBaseShapeValue()); 551 552 if (serviceObjectType == ServiceObjectType.Item) { 553 if (this.getRequestedBodyType() != null) { 554 writer.writeElementValue(XmlNamespace.Types, 555 XmlElementNames.BodyType, this.getRequestedBodyType()); 556 } 557 558 if (this.getFilterHtmlContent() != null) { 559 writer.writeElementValue(XmlNamespace.Types, 560 XmlElementNames.FilterHtmlContent, this 561 .getFilterHtmlContent()); 562 } 563 if ((this.getConvertHtmlCodePageToUTF8() != null) && 564 writer.getService().getRequestedServerVersion(). 565 compareTo(ExchangeVersion.Exchange2010_SP1) >= 0) { 566 writer.writeElementValue( 567 XmlNamespace.Types, 568 XmlElementNames.ConvertHtmlCodePageToUTF8, 569 this.getConvertHtmlCodePageToUTF8()); 570 } 571 } 572 573 if (this.additionalProperties.size() > 0) { 574 writeAdditionalPropertiesToXml(writer, this.additionalProperties 575 .iterator()); 576 } 577 578 writer.writeEndElement(); // Item/FolderShape 579 } 580 581 /* 582 * (non-Javadoc) 583 * 584 * @see java.lang.Iterable#iterator() 585 */ 586 @Override 587 public Iterator<PropertyDefinitionBase> iterator() { 588 return this.additionalProperties.iterator(); 589 } 590 591}