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 }