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}