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.service.ServiceObject; 028import microsoft.exchange.webservices.data.core.service.item.Item; 029import microsoft.exchange.webservices.data.core.enumeration.property.BasePropertySet; 030import microsoft.exchange.webservices.data.core.enumeration.property.PropertyDefinitionFlags; 031import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; 032import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException; 033import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException; 034import microsoft.exchange.webservices.data.core.exception.service.local.ServiceObjectPropertyException; 035import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException; 036import microsoft.exchange.webservices.data.misc.OutParam; 037import microsoft.exchange.webservices.data.property.complex.ComplexProperty; 038import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChanged; 039import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChangedDelegate; 040import microsoft.exchange.webservices.data.property.complex.IOwnedProperty; 041import microsoft.exchange.webservices.data.property.definition.ComplexPropertyDefinitionBase; 042import microsoft.exchange.webservices.data.property.definition.PropertyDefinition; 043import microsoft.exchange.webservices.data.security.XmlNodeType; 044 045import java.util.ArrayList; 046import java.util.HashMap; 047import java.util.Iterator; 048import java.util.List; 049import java.util.Map; 050import java.util.Map.Entry; 051 052/** 053 * Represents a property bag keyed on PropertyDefinition objects. 054 */ 055public class PropertyBag implements IComplexPropertyChanged, IComplexPropertyChangedDelegate { 056 057 /** 058 * The owner. 059 */ 060 private ServiceObject owner; 061 062 /** 063 * The is dirty. 064 */ 065 private boolean isDirty; 066 067 /** 068 * The loading. 069 */ 070 private boolean loading; 071 072 /** 073 * The only summary property requested. 074 */ 075 private boolean onlySummaryPropertiesRequested; 076 077 /** 078 * The loaded property. 079 */ 080 private List<PropertyDefinition> loadedProperties = 081 new ArrayList<PropertyDefinition>(); 082 083 /** 084 * The property. 085 */ 086 private Map<PropertyDefinition, Object> properties = 087 new HashMap<PropertyDefinition, Object>(); 088 089 /** 090 * The deleted property. 091 */ 092 private Map<PropertyDefinition, Object> deletedProperties = 093 new HashMap<PropertyDefinition, Object>(); 094 095 /** 096 * The modified property. 097 */ 098 private List<PropertyDefinition> modifiedProperties = 099 new ArrayList<PropertyDefinition>(); 100 101 /** 102 * The added property. 103 */ 104 private List<PropertyDefinition> addedProperties = 105 new ArrayList<PropertyDefinition>(); 106 107 /** 108 * The requested property set. 109 */ 110 private PropertySet requestedPropertySet; 111 112 /** 113 * Initializes a new instance of PropertyBag. 114 * 115 * @param owner The owner of the bag. 116 */ 117 public PropertyBag(ServiceObject owner) { 118 EwsUtilities.ewsAssert(owner != null, "PropertyBag.ctor", "owner is null"); 119 120 this.owner = owner; 121 } 122 123 /** 124 * Gets a Map holding the bag's property. 125 * 126 * @return A Map holding the bag's property. 127 */ 128 public Map<PropertyDefinition, Object> getProperties() { 129 return this.properties; 130 } 131 132 /** 133 * Gets the owner of this bag. 134 * 135 * @return The owner of this bag. 136 */ 137 public ServiceObject getOwner() { 138 return this.owner; 139 } 140 141 /** 142 * Indicates if a bag has pending changes. 143 * 144 * @return True if the bag has pending changes, false otherwise. 145 */ 146 public boolean getIsDirty() { 147 int changes = this.modifiedProperties.size() + 148 this.deletedProperties.size() + this.addedProperties.size(); 149 return changes > 0 || this.isDirty; 150 } 151 152 /** 153 * Adds the specified property to the specified change list if it is not 154 * already present. 155 * 156 * @param propertyDefinition The property to add to the change list. 157 * @param changeList The change list to add the property to. 158 */ 159 protected static void addToChangeList( 160 PropertyDefinition propertyDefinition, 161 List<PropertyDefinition> changeList) { 162 if (!changeList.contains(propertyDefinition)) { 163 changeList.add(propertyDefinition); 164 } 165 } 166 167 /** 168 * Checks if is property loaded. 169 * 170 * @param propertyDefinition the property definition 171 * @return true, if is property loaded 172 */ 173 public boolean isPropertyLoaded(PropertyDefinition propertyDefinition) { 174 // Is the property loaded? 175 if (this.loadedProperties.contains(propertyDefinition)) { 176 return true; 177 } else { 178 // Was the property requested? 179 return this.isRequestedProperty(propertyDefinition); 180 } 181 } 182 183 /** 184 * Checks if is requested property. 185 * 186 * @param propertyDefinition the property definition 187 * @return true, if is requested property 188 */ 189 private boolean isRequestedProperty(PropertyDefinition propertyDefinition) { 190 // If no requested property set, then property wasn't requested. 191 if (this.requestedPropertySet == null) { 192 return false; 193 } 194 195 // If base property set is all first-class property, use the 196 // appropriate list of 197 // property definitions to see if this property was requested. 198 // Otherwise, property had 199 // to be explicitly requested and needs to be listed in 200 // AdditionalProperties. 201 if (this.requestedPropertySet.getBasePropertySet() == BasePropertySet.FirstClassProperties) { 202 List<PropertyDefinition> firstClassProps = 203 this.onlySummaryPropertiesRequested ? this 204 .getOwner().getSchema().getFirstClassSummaryProperties() : 205 this.getOwner().getSchema().getFirstClassProperties(); 206 207 return firstClassProps.contains(propertyDefinition) || 208 this.requestedPropertySet.contains(propertyDefinition); 209 } else { 210 return this.requestedPropertySet.contains(propertyDefinition); 211 } 212 } 213 214 /** 215 * Determines whether the specified property has been updated. 216 * 217 * @param propertyDefinition The property definition. 218 * @return true if the specified property has been updated; otherwise, 219 * false. 220 */ 221 public boolean isPropertyUpdated(PropertyDefinition propertyDefinition) { 222 return this.modifiedProperties.contains(propertyDefinition) || 223 this.addedProperties.contains(propertyDefinition); 224 } 225 226 /** 227 * Tries to get a property value based on a property definition. 228 * 229 * @param propertyDefinition The property definition. 230 * @param propertyValueOutParam The property value. 231 * @return True if property was retrieved. 232 */ 233 protected boolean tryGetProperty(PropertyDefinition propertyDefinition, 234 OutParam<Object> propertyValueOutParam) { 235 OutParam<ServiceLocalException> serviceExceptionOutParam = 236 new OutParam<ServiceLocalException>(); 237 propertyValueOutParam.setParam(this.getPropertyValueOrException( 238 propertyDefinition, serviceExceptionOutParam)); 239 return serviceExceptionOutParam.getParam() == null; 240 } 241 242 /** 243 * Tries to get a property value based on a property definition. 244 * 245 * @param <T> the types of the property 246 * @param propertyDefinition the property definition 247 * @param propertyValue the property value 248 * @return true if property was retrieved 249 * @throws ArgumentException on validation error 250 */ 251 public <T> boolean tryGetPropertyType(Class<T> cls, PropertyDefinition propertyDefinition, 252 OutParam<T> propertyValue) throws ArgumentException { 253 // Verify that the type parameter and 254 //property definition's type are compatible. 255 if (!cls.isAssignableFrom(propertyDefinition.getType())) { 256 String errorMessage = String.format( 257 "Property definition type '%s' and type parameter '%s' aren't compatible.", 258 propertyDefinition.getType().getSimpleName(), 259 cls.getSimpleName()); 260 throw new ArgumentException(errorMessage, "propertyDefinition"); 261 } 262 263 OutParam<Object> value = new OutParam<Object>(); 264 boolean result = this.tryGetProperty(propertyDefinition, value); 265 if (result) { 266 propertyValue.setParam((T) value.getParam()); 267 } else { 268 propertyValue.setParam(null); 269 } 270 271 return result; 272 } 273 274 275 /** 276 * Gets the property value. 277 * 278 * @param propertyDefinition The property definition. 279 * @param serviceExceptionOutParam Exception that would be raised if there's an error retrieving 280 * the property. 281 * @return Property value. May be null. 282 */ 283 private <T> T getPropertyValueOrException( 284 PropertyDefinition propertyDefinition, 285 OutParam<ServiceLocalException> serviceExceptionOutParam) { 286 OutParam<T> propertyValueOutParam = new OutParam<T>(); 287 propertyValueOutParam.setParam(null); 288 serviceExceptionOutParam.setParam(null); 289 290 if (propertyDefinition.getVersion().ordinal() > this.getOwner() 291 .getService().getRequestedServerVersion().ordinal()) { 292 serviceExceptionOutParam.setParam(new ServiceVersionException( 293 String.format("The property %s is valid only for Exchange %s or later versions.", 294 propertyDefinition.getName(), propertyDefinition 295 .getVersion()))); 296 return null; 297 } 298 299 if (this.tryGetValue(propertyDefinition, propertyValueOutParam)) { 300 // If the requested property is in the bag, return it. 301 return propertyValueOutParam.getParam(); 302 } else { 303 if (propertyDefinition 304 .hasFlag(PropertyDefinitionFlags.AutoInstantiateOnRead)) { 305 EwsUtilities 306 .ewsAssert(propertyDefinition instanceof ComplexPropertyDefinitionBase, 307 "PropertyBag.get_this[]", 308 "propertyDefinition is " + 309 "marked with AutoInstantiateOnRead " + 310 "but is not a descendant " + 311 "of ComplexPropertyDefinitionBase"); 312 313 // The requested property is an auto-instantiate-on-read 314 // property 315 ComplexPropertyDefinitionBase complexPropertyDefinition = 316 (ComplexPropertyDefinitionBase) propertyDefinition; 317 ComplexProperty propertyValue = complexPropertyDefinition 318 .createPropertyInstance(getOwner()); 319 320 // XXX: It could be dangerous to return complex value instead of <T> 321 propertyValueOutParam.setParam((T) propertyValue); 322 if (propertyValue != null) { 323 this.initComplexProperty(propertyValue); 324 this.properties.put(propertyDefinition, propertyValue); 325 } 326 } else { 327 // If the property is not the Id (we need to let developers read 328 // the Id when it's null) and if has 329 // not been loaded, we throw. 330 if (propertyDefinition != this.getOwner() 331 .getIdPropertyDefinition()) { 332 if (!this.isPropertyLoaded(propertyDefinition)) { 333 serviceExceptionOutParam 334 .setParam(new ServiceObjectPropertyException( 335 "You must load or assign this property before you can read its value.", 336 propertyDefinition)); 337 return null; 338 } 339 340 // Non-nullable property (int, bool, etc.) must be 341 // assigned or loaded; cannot return null value. 342 if (!propertyDefinition.isNullable()) { 343 String errorMessage = this 344 .isRequestedProperty(propertyDefinition) ? "This property was requested, but it wasn't returned by the server." 345 : "You must assign this property before you can read its value."; 346 serviceExceptionOutParam 347 .setParam(new ServiceObjectPropertyException( 348 errorMessage, propertyDefinition)); 349 } 350 } 351 } 352 return propertyValueOutParam.getParam(); 353 } 354 } 355 356 /** 357 * Sets the isDirty flag to true and triggers dispatch of the change event 358 * to the owner of the property bag. Changed must be called whenever an 359 * operation that changes the state of this property bag is performed (e.g. 360 * adding or removing a property). 361 */ 362 public void changed() { 363 this.isDirty = true; 364 this.getOwner().changed(); 365 } 366 367 /** 368 * Determines whether the property bag contains a specific property. 369 * 370 * @param propertyDefinition The property to check against. 371 * @return True if the specified property is in the bag, false otherwise. 372 */ 373 public boolean contains(PropertyDefinition propertyDefinition) { 374 return this.properties.containsKey(propertyDefinition); 375 } 376 377 378 379 /** 380 * Tries to retrieve the value of the specified property. 381 * 382 * @param propertyDefinition the property for which to retrieve a value 383 * @param propertyValueOutParam if the method succeeds, contains the value of the property 384 * @return true if the value could be retrieved, false otherwise 385 */ 386 public <T> boolean tryGetValue(PropertyDefinition propertyDefinition, OutParam<T> propertyValueOutParam) { 387 if (this.properties.containsKey(propertyDefinition)) { 388 T param = (T) properties.get(propertyDefinition); 389 propertyValueOutParam.setParam(param); 390 return true; 391 } else { 392 propertyValueOutParam.setParam(null); 393 return false; 394 } 395 } 396 397 /** 398 * Handles a change event for the specified property. 399 * 400 * @param complexProperty The property that changes. 401 */ 402 protected void propertyChanged(ComplexProperty complexProperty) { 403 Iterator<Entry<PropertyDefinition, Object>> it = this.properties 404 .entrySet().iterator(); 405 while (it.hasNext()) { 406 Entry<PropertyDefinition, Object> keyValuePair = it.next(); 407 if (keyValuePair.getValue().equals(complexProperty)) { 408 if (!this.deletedProperties.containsKey(keyValuePair.getKey())) { 409 addToChangeList(keyValuePair.getKey(), 410 this.modifiedProperties); 411 this.changed(); 412 } 413 } 414 } 415 } 416 417 /** 418 * Deletes the property from the bag. 419 * 420 * @param propertyDefinition The property to delete. 421 */ 422 protected void deleteProperty(PropertyDefinition propertyDefinition) { 423 if (!this.deletedProperties.containsKey(propertyDefinition)) { 424 Object propertyValue = null; 425 426 if (this.properties.containsKey(propertyDefinition)) { 427 propertyValue = this.properties.get(propertyDefinition); 428 } 429 430 this.properties.remove(propertyDefinition); 431 this.modifiedProperties.remove(propertyDefinition); 432 this.deletedProperties.put(propertyDefinition, propertyValue); 433 434 if (propertyValue instanceof ComplexProperty) { 435 ComplexProperty complexProperty = 436 (ComplexProperty) propertyValue; 437 complexProperty.addOnChangeEvent(this); 438 } 439 } 440 } 441 442 /** 443 * Clears the bag. 444 */ 445 protected void clear() { 446 this.clearChangeLog(); 447 this.properties.clear(); 448 this.loadedProperties.clear(); 449 this.requestedPropertySet = null; 450 } 451 452 /** 453 * Clears the bag's change log. 454 */ 455 public void clearChangeLog() { 456 this.deletedProperties.clear(); 457 this.modifiedProperties.clear(); 458 this.addedProperties.clear(); 459 460 Iterator<Entry<PropertyDefinition, Object>> it = this.properties 461 .entrySet().iterator(); 462 while (it.hasNext()) { 463 Entry<PropertyDefinition, Object> keyValuePair = it.next(); 464 if (keyValuePair.getValue() instanceof ComplexProperty) { 465 ComplexProperty complexProperty = (ComplexProperty) keyValuePair 466 .getValue(); 467 complexProperty.clearChangeLog(); 468 } 469 } 470 471 this.isDirty = false; 472 } 473 474 /** 475 * Loads property from XML and inserts them in the bag. 476 * 477 * @param reader The reader from which to read the property. 478 * @param clear Indicates whether the bag should be cleared before property 479 * are loaded. 480 * @param requestedPropertySet The requested property set. 481 * @param onlySummaryPropertiesRequested Indicates whether summary or full property were requested. 482 * @throws Exception the exception 483 */ 484 public void loadFromXml(EwsServiceXmlReader reader, boolean clear, PropertySet requestedPropertySet, 485 boolean onlySummaryPropertiesRequested) throws Exception { 486 if (clear) { 487 this.clear(); 488 } 489 490 // Put the property bag in "loading" mode. When in loading mode, no 491 // checking is done 492 // when setting property values. 493 this.loading = true; 494 495 this.requestedPropertySet = requestedPropertySet; 496 this.onlySummaryPropertiesRequested = onlySummaryPropertiesRequested; 497 498 try { 499 do { 500 reader.read(); 501 502 if (reader.getNodeType().getNodeType() == XmlNodeType.START_ELEMENT) { 503 OutParam<PropertyDefinition> propertyDefinitionOut = 504 new OutParam<PropertyDefinition>(); 505 PropertyDefinition propertyDefinition; 506 507 if (this.getOwner().schema().tryGetPropertyDefinition( 508 reader.getLocalName(), propertyDefinitionOut)) { 509 propertyDefinition = propertyDefinitionOut.getParam(); 510 propertyDefinition.loadPropertyValueFromXml(reader, 511 this); 512 513 this.loadedProperties.add(propertyDefinition); 514 } else { 515 reader.skipCurrentElement(); 516 } 517 } 518 } while (!reader.isEndElement(XmlNamespace.Types, this.getOwner() 519 .getXmlElementName())); 520 521 this.clearChangeLog(); 522 } finally { 523 this.loading = false; 524 } 525 } 526 527 /** 528 * Writes the bag's property to XML. 529 * 530 * @param writer The writer to write the property to. 531 * @throws Exception the exception 532 */ 533 public void writeToXml(EwsServiceXmlWriter writer) throws Exception { 534 writer.writeStartElement(XmlNamespace.Types, this.getOwner() 535 .getXmlElementName()); 536 537 Iterator<PropertyDefinition> it = this.getOwner().getSchema() 538 .iterator(); 539 while (it.hasNext()) { 540 PropertyDefinition propertyDefinition = it.next(); 541 // The following test should not be necessary since the property bag 542 // prevents 543 // property to be set if they don't have the CanSet flag, but it 544 // doesn't hurt... 545 if (propertyDefinition 546 .hasFlag(PropertyDefinitionFlags.CanSet, writer.getService().getRequestedServerVersion())) { 547 if (this.contains(propertyDefinition)) { 548 propertyDefinition.writePropertyValueToXml(writer, this, 549 false /* isUpdateOperation */); 550 } 551 } 552 } 553 554 writer.writeEndElement(); 555 } 556 557 /** 558 * Writes the EWS update operations corresponding to the changes that 559 * occurred in the bag to XML. 560 * 561 * @param writer The writer to write the updates to. 562 * @throws Exception the exception 563 */ 564 public void writeToXmlForUpdate(EwsServiceXmlWriter writer) 565 throws Exception { 566 writer.writeStartElement(XmlNamespace.Types, this.getOwner() 567 .getChangeXmlElementName()); 568 569 this.getOwner().getId().writeToXml(writer); 570 571 writer.writeStartElement(XmlNamespace.Types, XmlElementNames.Updates); 572 573 for (PropertyDefinition propertyDefinition : this.addedProperties) { 574 this.writeSetUpdateToXml(writer, propertyDefinition); 575 } 576 577 for (PropertyDefinition propertyDefinition : this.modifiedProperties) { 578 this.writeSetUpdateToXml(writer, propertyDefinition); 579 } 580 581 Iterator<Entry<PropertyDefinition, Object>> it = this.deletedProperties 582 .entrySet().iterator(); 583 while (it.hasNext()) { 584 Entry<PropertyDefinition, Object> property = it.next(); 585 this.writeDeleteUpdateToXml(writer, property.getKey(), property 586 .getValue()); 587 } 588 589 writer.writeEndElement(); 590 writer.writeEndElement(); 591 } 592 593 /** 594 * Determines whether an EWS UpdateItem/UpdateFolder call is necessary to 595 * save the changes that occurred in the bag. 596 * 597 * @return True if an UpdateItem/UpdateFolder call is necessary, false 598 * otherwise. 599 */ 600 public boolean getIsUpdateCallNecessary() { 601 List<PropertyDefinition> propertyDefinitions = 602 new ArrayList<PropertyDefinition>(); 603 propertyDefinitions.addAll(this.addedProperties); 604 propertyDefinitions.addAll(this.modifiedProperties); 605 propertyDefinitions.addAll(this.deletedProperties.keySet()); 606 for (PropertyDefinition propertyDefinition : propertyDefinitions) { 607 if (propertyDefinition.hasFlag(PropertyDefinitionFlags.CanUpdate)) { 608 return true; 609 } 610 } 611 return false; 612 } 613 614 /** 615 * Initializes a ComplexProperty instance. When a property is inserted into 616 * the bag, it needs to be initialized in order for changes that occur on 617 * that property to be properly detected and dispatched. 618 * 619 * @param complexProperty The ComplexProperty instance to initialize. 620 */ 621 private void initComplexProperty(ComplexProperty complexProperty) { 622 if (complexProperty != null) { 623 complexProperty.addOnChangeEvent(this); 624 if (complexProperty instanceof IOwnedProperty) { 625 IOwnedProperty ownedProperty = (IOwnedProperty) complexProperty; 626 ownedProperty.setOwner(this.getOwner()); 627 } 628 } 629 } 630 631 /** 632 * Writes an EWS SetUpdate opeartion for the specified property. 633 * 634 * @param writer The writer to write the update to. 635 * @param propertyDefinition The property fro which to write the update. 636 * @throws Exception the exception 637 */ 638 private void writeSetUpdateToXml(EwsServiceXmlWriter writer, 639 PropertyDefinition propertyDefinition) throws Exception { 640 // The following test should not be necessary since the property bag 641 // prevents 642 // property to be updated if they don't have the CanUpdate flag, but 643 // it 644 // doesn't hurt... 645 if (propertyDefinition.hasFlag(PropertyDefinitionFlags.CanUpdate)) { 646 Object propertyValue = this 647 .getObjectFromPropertyDefinition(propertyDefinition); 648 649 boolean handled = false; 650 651 if (propertyValue instanceof ICustomXmlUpdateSerializer) { 652 ICustomXmlUpdateSerializer updateSerializer = 653 (ICustomXmlUpdateSerializer) propertyValue; 654 handled = updateSerializer.writeSetUpdateToXml(writer, this 655 .getOwner(), propertyDefinition); 656 } 657 658 if (!handled) { 659 writer.writeStartElement(XmlNamespace.Types, this.getOwner() 660 .getSetFieldXmlElementName()); 661 662 propertyDefinition.writeToXml(writer); 663 664 writer.writeStartElement(XmlNamespace.Types, this.getOwner() 665 .getXmlElementName()); 666 propertyDefinition 667 .writePropertyValueToXml(writer, this, 668 true /* isUpdateOperation */); 669 writer.writeEndElement(); 670 671 writer.writeEndElement(); 672 } 673 } 674 } 675 676 /** 677 * Writes an EWS DeleteUpdate opeartion for the specified property. 678 * 679 * @param writer The writer to write the update to. 680 * @param propertyDefinition The property fro which to write the update. 681 * @param propertyValue The current value of the property. 682 * @throws Exception the exception 683 */ 684 private void writeDeleteUpdateToXml(EwsServiceXmlWriter writer, 685 PropertyDefinition propertyDefinition, Object propertyValue) 686 throws Exception { 687 // The following test should not be necessary since the property bag 688 // prevents 689 // property to be deleted (set to null) if they don't have the 690 // CanDelete flag, 691 // but it doesn't hurt... 692 if (propertyDefinition.hasFlag(PropertyDefinitionFlags.CanDelete)) { 693 boolean handled = false; 694 695 if (propertyValue instanceof ICustomXmlUpdateSerializer) { 696 ICustomXmlUpdateSerializer updateSerializer = 697 (ICustomXmlUpdateSerializer) propertyValue; 698 handled = updateSerializer.writeDeleteUpdateToXml(writer, this 699 .getOwner()); 700 } 701 702 if (!handled) { 703 writer.writeStartElement(XmlNamespace.Types, this.getOwner() 704 .getDeleteFieldXmlElementName()); 705 propertyDefinition.writeToXml(writer); 706 writer.writeEndElement(); 707 } 708 } 709 } 710 711 /** 712 * Validate property bag instance. 713 * 714 * @throws Exception the exception 715 */ 716 public void validate() throws Exception { 717 for (PropertyDefinition propertyDefinition : this.addedProperties) { 718 this.validatePropertyValue(propertyDefinition); 719 } 720 721 for (PropertyDefinition propertyDefinition : this.modifiedProperties) { 722 this.validatePropertyValue(propertyDefinition); 723 } 724 } 725 726 /** 727 * Validates the property value. 728 * 729 * @param propertyDefinition The property definition. 730 * @throws Exception the exception 731 */ 732 private void validatePropertyValue(PropertyDefinition propertyDefinition) 733 throws Exception { 734 OutParam<Object> propertyValueOut = new OutParam<Object>(); 735 if (this.tryGetProperty(propertyDefinition, propertyValueOut)) { 736 Object propertyValue = propertyValueOut.getParam(); 737 738 if (propertyValue instanceof ISelfValidate) { 739 ISelfValidate validatingValue = (ISelfValidate) propertyValue; 740 validatingValue.validate(); 741 } 742 } 743 } 744 745 /** 746 * Gets the value of a property. 747 * 748 * @param propertyDefinition The property to get or set. 749 * @return An object representing the value of the property. 750 * @throws ServiceLocalException ServiceVersionException will be raised if this property 751 * requires a later version of Exchange. 752 * ServiceObjectPropertyException will be raised for get if 753 * property hasn't been assigned or loaded, raised for set if 754 * property cannot be updated or deleted. 755 */ 756 public <T> T getObjectFromPropertyDefinition(PropertyDefinition propertyDefinition) 757 throws ServiceLocalException { 758 OutParam<ServiceLocalException> serviceExceptionOut = 759 new OutParam<ServiceLocalException>(); 760 T propertyValue = getPropertyValueOrException(propertyDefinition, serviceExceptionOut); 761 762 ServiceLocalException serviceException = serviceExceptionOut.getParam(); 763 if (serviceException != null) { 764 throw serviceException; 765 } 766 return propertyValue; 767 } 768 769 /** 770 * Gets the value of a property. 771 * 772 * @param propertyDefinition The property to get or set. 773 * @param object An object representing the value of the property. 774 * @throws Exception the exception 775 */ 776 public void setObjectFromPropertyDefinition(PropertyDefinition propertyDefinition, Object object) 777 throws Exception { 778 if (propertyDefinition.getVersion().ordinal() > this.getOwner() 779 .getService().getRequestedServerVersion().ordinal()) { 780 throw new ServiceVersionException(String.format( 781 "The property %s is valid only for Exchange %s or later versions.", 782 propertyDefinition.getName(), propertyDefinition 783 .getVersion())); 784 } 785 786 // If the property bag is not in the loading state, we need to verify 787 // whether 788 // the property can actually be set or updated. 789 if (!this.loading) { 790 // If the owner is new and if the property cannot be set, throw. 791 if (this.getOwner().isNew() 792 && !propertyDefinition 793 .hasFlag(PropertyDefinitionFlags.CanSet, this.getOwner() 794 .getService().getRequestedServerVersion())) { 795 throw new ServiceObjectPropertyException("This property is read-only and can't be set.", propertyDefinition); 796 } 797 798 if (!this.getOwner().isNew()) { 799 // If owner is an item attachment, property cannot be updated 800 // (EWS doesn't support updating item attachments) 801 802 if ((this.getOwner() instanceof Item)) { 803 Item ownerItem = (Item) this.getOwner(); 804 if (ownerItem.isAttachment()) { 805 throw new ServiceObjectPropertyException("Item attachments can't be updated.", 806 propertyDefinition); 807 } 808 } 809 810 // If the property cannot be deleted, throw. 811 if (object == null 812 && !propertyDefinition 813 .hasFlag(PropertyDefinitionFlags.CanDelete)) { 814 throw new ServiceObjectPropertyException("This property can't be deleted.", 815 propertyDefinition); 816 } 817 818 // If the property cannot be updated, throw. 819 if (!propertyDefinition 820 .hasFlag(PropertyDefinitionFlags.CanUpdate)) { 821 throw new ServiceObjectPropertyException("This property can't be updated.", 822 propertyDefinition); 823 } 824 } 825 } 826 827 // If the value is set to null, delete the property. 828 if (object == null) { 829 this.deleteProperty(propertyDefinition); 830 } else { 831 ComplexProperty complexProperty = null; 832 Object currentValue = null; 833 834 if (this.properties.containsKey(propertyDefinition)) { 835 currentValue = this.properties.get(propertyDefinition); 836 837 if (currentValue instanceof ComplexProperty) { 838 complexProperty = (ComplexProperty) currentValue; 839 complexProperty.removeChangeEvent(this); 840 } 841 } 842 843 // If the property was to be deleted, the deletion becomes an 844 // update. 845 if (this.deletedProperties.containsKey(propertyDefinition)) { 846 this.deletedProperties.remove(propertyDefinition); 847 addToChangeList(propertyDefinition, this.modifiedProperties); 848 } else { 849 // If the property value was not set, we have a newly set 850 // property. 851 if (!this.properties.containsKey(propertyDefinition)) { 852 addToChangeList(propertyDefinition, this.addedProperties); 853 } else { 854 // The last case is that we have a modified property. 855 if (!this.modifiedProperties.contains(propertyDefinition)) { 856 addToChangeList(propertyDefinition, 857 this.modifiedProperties); 858 } 859 } 860 } 861 862 if (object instanceof ComplexProperty) { 863 this.initComplexProperty((ComplexProperty) object); 864 } 865 this.properties.put(propertyDefinition, object); 866 this.changed(); 867 } 868 869 } 870 871 /* 872 * (non-Javadoc) 873 * 874 * @seemicrosoft.exchange.webservices.ComplexPropertyChangedInterface# 875 * complexPropertyChanged(microsoft.exchange.webservices.ComplexProperty) 876 */ 877 @Override 878 public void complexPropertyChanged(ComplexProperty complexProperty) { 879 this.propertyChanged(complexProperty); 880 } 881 882 public void setLoading(boolean loading) { 883 this.loading = loading; 884 } 885}