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    
024    package microsoft.exchange.webservices.data.core;
025    
026    import microsoft.exchange.webservices.data.ISelfValidate;
027    import microsoft.exchange.webservices.data.core.service.ServiceObject;
028    import microsoft.exchange.webservices.data.core.service.item.Item;
029    import microsoft.exchange.webservices.data.core.enumeration.property.BasePropertySet;
030    import microsoft.exchange.webservices.data.core.enumeration.property.PropertyDefinitionFlags;
031    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
032    import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException;
033    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
034    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceObjectPropertyException;
035    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
036    import microsoft.exchange.webservices.data.misc.OutParam;
037    import microsoft.exchange.webservices.data.property.complex.ComplexProperty;
038    import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChanged;
039    import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChangedDelegate;
040    import microsoft.exchange.webservices.data.property.complex.IOwnedProperty;
041    import microsoft.exchange.webservices.data.property.definition.ComplexPropertyDefinitionBase;
042    import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
043    import microsoft.exchange.webservices.data.security.XmlNodeType;
044    
045    import java.util.ArrayList;
046    import java.util.HashMap;
047    import java.util.Iterator;
048    import java.util.List;
049    import java.util.Map;
050    import java.util.Map.Entry;
051    
052    /**
053     * Represents a property bag keyed on PropertyDefinition objects.
054     */
055    public 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    }