/* * The MIT License * Copyright (c) 2012 Microsoft Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package microsoft.exchange.webservices.data.core; import microsoft.exchange.webservices.data.ISelfValidate; import microsoft.exchange.webservices.data.core.service.ServiceObject; import microsoft.exchange.webservices.data.core.service.item.Item; import microsoft.exchange.webservices.data.core.enumeration.property.BasePropertySet; import microsoft.exchange.webservices.data.core.enumeration.property.PropertyDefinitionFlags; import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; import microsoft.exchange.webservices.data.core.exception.misc.ArgumentException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceObjectPropertyException; import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException; import microsoft.exchange.webservices.data.misc.OutParam; import microsoft.exchange.webservices.data.property.complex.ComplexProperty; import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChanged; import microsoft.exchange.webservices.data.property.complex.IComplexPropertyChangedDelegate; import microsoft.exchange.webservices.data.property.complex.IOwnedProperty; import microsoft.exchange.webservices.data.property.definition.ComplexPropertyDefinitionBase; import microsoft.exchange.webservices.data.property.definition.PropertyDefinition; import microsoft.exchange.webservices.data.security.XmlNodeType; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * Represents a property bag keyed on PropertyDefinition objects. */ public class PropertyBag implements IComplexPropertyChanged, IComplexPropertyChangedDelegate { /** * The owner. */ private ServiceObject owner; /** * The is dirty. */ private boolean isDirty; /** * The loading. */ private boolean loading; /** * The only summary property requested. */ private boolean onlySummaryPropertiesRequested; /** * The loaded property. */ private List<PropertyDefinition> loadedProperties = new ArrayList<PropertyDefinition>(); /** * The property. */ private Map<PropertyDefinition, Object> properties = new HashMap<PropertyDefinition, Object>(); /** * The deleted property. */ private Map<PropertyDefinition, Object> deletedProperties = new HashMap<PropertyDefinition, Object>(); /** * The modified property. */ private List<PropertyDefinition> modifiedProperties = new ArrayList<PropertyDefinition>(); /** * The added property. */ private List<PropertyDefinition> addedProperties = new ArrayList<PropertyDefinition>(); /** * The requested property set. */ private PropertySet requestedPropertySet; /** * Initializes a new instance of PropertyBag. * * @param owner The owner of the bag. */ public PropertyBag(ServiceObject owner) { EwsUtilities.ewsAssert(owner != null, "PropertyBag.ctor", "owner is null"); this.owner = owner; } /** * Gets a Map holding the bag's property. * * @return A Map holding the bag's property. */ public Map<PropertyDefinition, Object> getProperties() { return this.properties; } /** * Gets the owner of this bag. * * @return The owner of this bag. */ public ServiceObject getOwner() { return this.owner; } /** * Indicates if a bag has pending changes. * * @return True if the bag has pending changes, false otherwise. */ public boolean getIsDirty() { int changes = this.modifiedProperties.size() + this.deletedProperties.size() + this.addedProperties.size(); return changes > 0 || this.isDirty; } /** * Adds the specified property to the specified change list if it is not * already present. * * @param propertyDefinition The property to add to the change list. * @param changeList The change list to add the property to. */ protected static void addToChangeList( PropertyDefinition propertyDefinition, List<PropertyDefinition> changeList) { if (!changeList.contains(propertyDefinition)) { changeList.add(propertyDefinition); } } /** * Checks if is property loaded. * * @param propertyDefinition the property definition * @return true, if is property loaded */ public boolean isPropertyLoaded(PropertyDefinition propertyDefinition) { // Is the property loaded? if (this.loadedProperties.contains(propertyDefinition)) { return true; } else { // Was the property requested? return this.isRequestedProperty(propertyDefinition); } } /** * Checks if is requested property. * * @param propertyDefinition the property definition * @return true, if is requested property */ private boolean isRequestedProperty(PropertyDefinition propertyDefinition) { // If no requested property set, then property wasn't requested. if (this.requestedPropertySet == null) { return false; } // If base property set is all first-class property, use the // appropriate list of // property definitions to see if this property was requested. // Otherwise, property had // to be explicitly requested and needs to be listed in // AdditionalProperties. if (this.requestedPropertySet.getBasePropertySet() == BasePropertySet.FirstClassProperties) { List<PropertyDefinition> firstClassProps = this.onlySummaryPropertiesRequested ? this .getOwner().getSchema().getFirstClassSummaryProperties() : this.getOwner().getSchema().getFirstClassProperties(); return firstClassProps.contains(propertyDefinition) || this.requestedPropertySet.contains(propertyDefinition); } else { return this.requestedPropertySet.contains(propertyDefinition); } } /** * Determines whether the specified property has been updated. * * @param propertyDefinition The property definition. * @return true if the specified property has been updated; otherwise, * false. */ public boolean isPropertyUpdated(PropertyDefinition propertyDefinition) { return this.modifiedProperties.contains(propertyDefinition) || this.addedProperties.contains(propertyDefinition); } /** * Tries to get a property value based on a property definition. * * @param propertyDefinition The property definition. * @param propertyValueOutParam The property value. * @return True if property was retrieved. */ protected boolean tryGetProperty(PropertyDefinition propertyDefinition, OutParam<Object> propertyValueOutParam) { OutParam<ServiceLocalException> serviceExceptionOutParam = new OutParam<ServiceLocalException>(); propertyValueOutParam.setParam(this.getPropertyValueOrException( propertyDefinition, serviceExceptionOutParam)); return serviceExceptionOutParam.getParam() == null; } /** * Tries to get a property value based on a property definition. * * @param <T> the types of the property * @param propertyDefinition the property definition * @param propertyValue the property value * @return true if property was retrieved * @throws ArgumentException on validation error */ public <T> boolean tryGetPropertyType(Class<T> cls, PropertyDefinition propertyDefinition, OutParam<T> propertyValue) throws ArgumentException { // Verify that the type parameter and //property definition's type are compatible. if (!cls.isAssignableFrom(propertyDefinition.getType())) { String errorMessage = String.format( "Property definition type '%s' and type parameter '%s' aren't compatible.", propertyDefinition.getType().getSimpleName(), cls.getSimpleName()); throw new ArgumentException(errorMessage, "propertyDefinition"); } OutParam<Object> value = new OutParam<Object>(); boolean result = this.tryGetProperty(propertyDefinition, value); if (result) { propertyValue.setParam((T) value.getParam()); } else { propertyValue.setParam(null); } return result; } /** * Gets the property value. * * @param propertyDefinition The property definition. * @param serviceExceptionOutParam Exception that would be raised if there's an error retrieving * the property. * @return Property value. May be null. */ private <T> T getPropertyValueOrException( PropertyDefinition propertyDefinition, OutParam<ServiceLocalException> serviceExceptionOutParam) { OutParam<T> propertyValueOutParam = new OutParam<T>(); propertyValueOutParam.setParam(null); serviceExceptionOutParam.setParam(null); if (propertyDefinition.getVersion().ordinal() > this.getOwner() .getService().getRequestedServerVersion().ordinal()) { serviceExceptionOutParam.setParam(new ServiceVersionException( String.format("The property %s is valid only for Exchange %s or later versions.", propertyDefinition.getName(), propertyDefinition .getVersion()))); return null; } if (this.tryGetValue(propertyDefinition, propertyValueOutParam)) { // If the requested property is in the bag, return it. return propertyValueOutParam.getParam(); } else { if (propertyDefinition .hasFlag(PropertyDefinitionFlags.AutoInstantiateOnRead)) { EwsUtilities .ewsAssert(propertyDefinition instanceof ComplexPropertyDefinitionBase, "PropertyBag.get_this[]", "propertyDefinition is " + "marked with AutoInstantiateOnRead " + "but is not a descendant " + "of ComplexPropertyDefinitionBase"); // The requested property is an auto-instantiate-on-read // property ComplexPropertyDefinitionBase complexPropertyDefinition = (ComplexPropertyDefinitionBase) propertyDefinition; ComplexProperty propertyValue = complexPropertyDefinition .createPropertyInstance(getOwner()); // XXX: It could be dangerous to return complex value instead of <T> propertyValueOutParam.setParam((T) propertyValue); if (propertyValue != null) { this.initComplexProperty(propertyValue); this.properties.put(propertyDefinition, propertyValue); } } else { // If the property is not the Id (we need to let developers read // the Id when it's null) and if has // not been loaded, we throw. if (propertyDefinition != this.getOwner() .getIdPropertyDefinition()) { if (!this.isPropertyLoaded(propertyDefinition)) { serviceExceptionOutParam .setParam(new ServiceObjectPropertyException( "You must load or assign this property before you can read its value.", propertyDefinition)); return null; } // Non-nullable property (int, bool, etc.) must be // assigned or loaded; cannot return null value. if (!propertyDefinition.isNullable()) { String errorMessage = this .isRequestedProperty(propertyDefinition) ? "This property was requested, but it wasn't returned by the server." : "You must assign this property before you can read its value."; serviceExceptionOutParam .setParam(new ServiceObjectPropertyException( errorMessage, propertyDefinition)); } } } return propertyValueOutParam.getParam(); } } /** * Sets the isDirty flag to true and triggers dispatch of the change event * to the owner of the property bag. Changed must be called whenever an * operation that changes the state of this property bag is performed (e.g. * adding or removing a property). */ public void changed() { this.isDirty = true; this.getOwner().changed(); } /** * Determines whether the property bag contains a specific property. * * @param propertyDefinition The property to check against. * @return True if the specified property is in the bag, false otherwise. */ public boolean contains(PropertyDefinition propertyDefinition) { return this.properties.containsKey(propertyDefinition); } /** * Tries to retrieve the value of the specified property. * * @param propertyDefinition the property for which to retrieve a value * @param propertyValueOutParam if the method succeeds, contains the value of the property * @return true if the value could be retrieved, false otherwise */ public <T> boolean tryGetValue(PropertyDefinition propertyDefinition, OutParam<T> propertyValueOutParam) { if (this.properties.containsKey(propertyDefinition)) { T param = (T) properties.get(propertyDefinition); propertyValueOutParam.setParam(param); return true; } else { propertyValueOutParam.setParam(null); return false; } } /** * Handles a change event for the specified property. * * @param complexProperty The property that changes. */ protected void propertyChanged(ComplexProperty complexProperty) { Iterator<Entry<PropertyDefinition, Object>> it = this.properties .entrySet().iterator(); while (it.hasNext()) { Entry<PropertyDefinition, Object> keyValuePair = it.next(); if (keyValuePair.getValue().equals(complexProperty)) { if (!this.deletedProperties.containsKey(keyValuePair.getKey())) { addToChangeList(keyValuePair.getKey(), this.modifiedProperties); this.changed(); } } } } /** * Deletes the property from the bag. * * @param propertyDefinition The property to delete. */ protected void deleteProperty(PropertyDefinition propertyDefinition) { if (!this.deletedProperties.containsKey(propertyDefinition)) { Object propertyValue = null; if (this.properties.containsKey(propertyDefinition)) { propertyValue = this.properties.get(propertyDefinition); } this.properties.remove(propertyDefinition); this.modifiedProperties.remove(propertyDefinition); this.deletedProperties.put(propertyDefinition, propertyValue); if (propertyValue instanceof ComplexProperty) { ComplexProperty complexProperty = (ComplexProperty) propertyValue; complexProperty.addOnChangeEvent(this); } } } /** * Clears the bag. */ protected void clear() { this.clearChangeLog(); this.properties.clear(); this.loadedProperties.clear(); this.requestedPropertySet = null; } /** * Clears the bag's change log. */ public void clearChangeLog() { this.deletedProperties.clear(); this.modifiedProperties.clear(); this.addedProperties.clear(); Iterator<Entry<PropertyDefinition, Object>> it = this.properties .entrySet().iterator(); while (it.hasNext()) { Entry<PropertyDefinition, Object> keyValuePair = it.next(); if (keyValuePair.getValue() instanceof ComplexProperty) { ComplexProperty complexProperty = (ComplexProperty) keyValuePair .getValue(); complexProperty.clearChangeLog(); } } this.isDirty = false; } /** * Loads property from XML and inserts them in the bag. * * @param reader The reader from which to read the property. * @param clear Indicates whether the bag should be cleared before property * are loaded. * @param requestedPropertySet The requested property set. * @param onlySummaryPropertiesRequested Indicates whether summary or full property were requested. * @throws Exception the exception */ public void loadFromXml(EwsServiceXmlReader reader, boolean clear, PropertySet requestedPropertySet, boolean onlySummaryPropertiesRequested) throws Exception { if (clear) { this.clear(); } // Put the property bag in "loading" mode. When in loading mode, no // checking is done // when setting property values. this.loading = true; this.requestedPropertySet = requestedPropertySet; this.onlySummaryPropertiesRequested = onlySummaryPropertiesRequested; try { do { reader.read(); if (reader.getNodeType().getNodeType() == XmlNodeType.START_ELEMENT) { OutParam<PropertyDefinition> propertyDefinitionOut = new OutParam<PropertyDefinition>(); PropertyDefinition propertyDefinition; if (this.getOwner().schema().tryGetPropertyDefinition( reader.getLocalName(), propertyDefinitionOut)) { propertyDefinition = propertyDefinitionOut.getParam(); propertyDefinition.loadPropertyValueFromXml(reader, this); this.loadedProperties.add(propertyDefinition); } else { reader.skipCurrentElement(); } } } while (!reader.isEndElement(XmlNamespace.Types, this.getOwner() .getXmlElementName())); this.clearChangeLog(); } finally { this.loading = false; } } /** * Writes the bag's property to XML. * * @param writer The writer to write the property to. * @throws Exception the exception */ public void writeToXml(EwsServiceXmlWriter writer) throws Exception { writer.writeStartElement(XmlNamespace.Types, this.getOwner() .getXmlElementName()); Iterator<PropertyDefinition> it = this.getOwner().getSchema() .iterator(); while (it.hasNext()) { PropertyDefinition propertyDefinition = it.next(); // The following test should not be necessary since the property bag // prevents // property to be set if they don't have the CanSet flag, but it // doesn't hurt... if (propertyDefinition .hasFlag(PropertyDefinitionFlags.CanSet, writer.getService().getRequestedServerVersion())) { if (this.contains(propertyDefinition)) { propertyDefinition.writePropertyValueToXml(writer, this, false /* isUpdateOperation */); } } } writer.writeEndElement(); } /** * Writes the EWS update operations corresponding to the changes that * occurred in the bag to XML. * * @param writer The writer to write the updates to. * @throws Exception the exception */ public void writeToXmlForUpdate(EwsServiceXmlWriter writer) throws Exception { writer.writeStartElement(XmlNamespace.Types, this.getOwner() .getChangeXmlElementName()); this.getOwner().getId().writeToXml(writer); writer.writeStartElement(XmlNamespace.Types, XmlElementNames.Updates); for (PropertyDefinition propertyDefinition : this.addedProperties) { this.writeSetUpdateToXml(writer, propertyDefinition); } for (PropertyDefinition propertyDefinition : this.modifiedProperties) { this.writeSetUpdateToXml(writer, propertyDefinition); } Iterator<Entry<PropertyDefinition, Object>> it = this.deletedProperties .entrySet().iterator(); while (it.hasNext()) { Entry<PropertyDefinition, Object> property = it.next(); this.writeDeleteUpdateToXml(writer, property.getKey(), property .getValue()); } writer.writeEndElement(); writer.writeEndElement(); } /** * Determines whether an EWS UpdateItem/UpdateFolder call is necessary to * save the changes that occurred in the bag. * * @return True if an UpdateItem/UpdateFolder call is necessary, false * otherwise. */ public boolean getIsUpdateCallNecessary() { List<PropertyDefinition> propertyDefinitions = new ArrayList<PropertyDefinition>(); propertyDefinitions.addAll(this.addedProperties); propertyDefinitions.addAll(this.modifiedProperties); propertyDefinitions.addAll(this.deletedProperties.keySet()); for (PropertyDefinition propertyDefinition : propertyDefinitions) { if (propertyDefinition.hasFlag(PropertyDefinitionFlags.CanUpdate)) { return true; } } return false; } /** * Initializes a ComplexProperty instance. When a property is inserted into * the bag, it needs to be initialized in order for changes that occur on * that property to be properly detected and dispatched. * * @param complexProperty The ComplexProperty instance to initialize. */ private void initComplexProperty(ComplexProperty complexProperty) { if (complexProperty != null) { complexProperty.addOnChangeEvent(this); if (complexProperty instanceof IOwnedProperty) { IOwnedProperty ownedProperty = (IOwnedProperty) complexProperty; ownedProperty.setOwner(this.getOwner()); } } } /** * Writes an EWS SetUpdate opeartion for the specified property. * * @param writer The writer to write the update to. * @param propertyDefinition The property fro which to write the update. * @throws Exception the exception */ private void writeSetUpdateToXml(EwsServiceXmlWriter writer, PropertyDefinition propertyDefinition) throws Exception { // The following test should not be necessary since the property bag // prevents // property to be updated if they don't have the CanUpdate flag, but // it // doesn't hurt... if (propertyDefinition.hasFlag(PropertyDefinitionFlags.CanUpdate)) { Object propertyValue = this .getObjectFromPropertyDefinition(propertyDefinition); boolean handled = false; if (propertyValue instanceof ICustomXmlUpdateSerializer) { ICustomXmlUpdateSerializer updateSerializer = (ICustomXmlUpdateSerializer) propertyValue; handled = updateSerializer.writeSetUpdateToXml(writer, this .getOwner(), propertyDefinition); } if (!handled) { writer.writeStartElement(XmlNamespace.Types, this.getOwner() .getSetFieldXmlElementName()); propertyDefinition.writeToXml(writer); writer.writeStartElement(XmlNamespace.Types, this.getOwner() .getXmlElementName()); propertyDefinition .writePropertyValueToXml(writer, this, true /* isUpdateOperation */); writer.writeEndElement(); writer.writeEndElement(); } } } /** * Writes an EWS DeleteUpdate opeartion for the specified property. * * @param writer The writer to write the update to. * @param propertyDefinition The property fro which to write the update. * @param propertyValue The current value of the property. * @throws Exception the exception */ private void writeDeleteUpdateToXml(EwsServiceXmlWriter writer, PropertyDefinition propertyDefinition, Object propertyValue) throws Exception { // The following test should not be necessary since the property bag // prevents // property to be deleted (set to null) if they don't have the // CanDelete flag, // but it doesn't hurt... if (propertyDefinition.hasFlag(PropertyDefinitionFlags.CanDelete)) { boolean handled = false; if (propertyValue instanceof ICustomXmlUpdateSerializer) { ICustomXmlUpdateSerializer updateSerializer = (ICustomXmlUpdateSerializer) propertyValue; handled = updateSerializer.writeDeleteUpdateToXml(writer, this .getOwner()); } if (!handled) { writer.writeStartElement(XmlNamespace.Types, this.getOwner() .getDeleteFieldXmlElementName()); propertyDefinition.writeToXml(writer); writer.writeEndElement(); } } } /** * Validate property bag instance. * * @throws Exception the exception */ public void validate() throws Exception { for (PropertyDefinition propertyDefinition : this.addedProperties) { this.validatePropertyValue(propertyDefinition); } for (PropertyDefinition propertyDefinition : this.modifiedProperties) { this.validatePropertyValue(propertyDefinition); } } /** * Validates the property value. * * @param propertyDefinition The property definition. * @throws Exception the exception */ private void validatePropertyValue(PropertyDefinition propertyDefinition) throws Exception { OutParam<Object> propertyValueOut = new OutParam<Object>(); if (this.tryGetProperty(propertyDefinition, propertyValueOut)) { Object propertyValue = propertyValueOut.getParam(); if (propertyValue instanceof ISelfValidate) { ISelfValidate validatingValue = (ISelfValidate) propertyValue; validatingValue.validate(); } } } /** * Gets the value of a property. * * @param propertyDefinition The property to get or set. * @return An object representing the value of the property. * @throws ServiceLocalException ServiceVersionException will be raised if this property * requires a later version of Exchange. * ServiceObjectPropertyException will be raised for get if * property hasn't been assigned or loaded, raised for set if * property cannot be updated or deleted. */ public <T> T getObjectFromPropertyDefinition(PropertyDefinition propertyDefinition) throws ServiceLocalException { OutParam<ServiceLocalException> serviceExceptionOut = new OutParam<ServiceLocalException>(); T propertyValue = getPropertyValueOrException(propertyDefinition, serviceExceptionOut); ServiceLocalException serviceException = serviceExceptionOut.getParam(); if (serviceException != null) { throw serviceException; } return propertyValue; } /** * Gets the value of a property. * * @param propertyDefinition The property to get or set. * @param object An object representing the value of the property. * @throws Exception the exception */ public void setObjectFromPropertyDefinition(PropertyDefinition propertyDefinition, Object object) throws Exception { if (propertyDefinition.getVersion().ordinal() > this.getOwner() .getService().getRequestedServerVersion().ordinal()) { throw new ServiceVersionException(String.format( "The property %s is valid only for Exchange %s or later versions.", propertyDefinition.getName(), propertyDefinition .getVersion())); } // If the property bag is not in the loading state, we need to verify // whether // the property can actually be set or updated. if (!this.loading) { // If the owner is new and if the property cannot be set, throw. if (this.getOwner().isNew() && !propertyDefinition .hasFlag(PropertyDefinitionFlags.CanSet, this.getOwner() .getService().getRequestedServerVersion())) { throw new ServiceObjectPropertyException("This property is read-only and can't be set.", propertyDefinition); } if (!this.getOwner().isNew()) { // If owner is an item attachment, property cannot be updated // (EWS doesn't support updating item attachments) if ((this.getOwner() instanceof Item)) { Item ownerItem = (Item) this.getOwner(); if (ownerItem.isAttachment()) { throw new ServiceObjectPropertyException("Item attachments can't be updated.", propertyDefinition); } } // If the property cannot be deleted, throw. if (object == null && !propertyDefinition .hasFlag(PropertyDefinitionFlags.CanDelete)) { throw new ServiceObjectPropertyException("This property can't be deleted.", propertyDefinition); } // If the property cannot be updated, throw. if (!propertyDefinition .hasFlag(PropertyDefinitionFlags.CanUpdate)) { throw new ServiceObjectPropertyException("This property can't be updated.", propertyDefinition); } } } // If the value is set to null, delete the property. if (object == null) { this.deleteProperty(propertyDefinition); } else { ComplexProperty complexProperty = null; Object currentValue = null; if (this.properties.containsKey(propertyDefinition)) { currentValue = this.properties.get(propertyDefinition); if (currentValue instanceof ComplexProperty) { complexProperty = (ComplexProperty) currentValue; complexProperty.removeChangeEvent(this); } } // If the property was to be deleted, the deletion becomes an // update. if (this.deletedProperties.containsKey(propertyDefinition)) { this.deletedProperties.remove(propertyDefinition); addToChangeList(propertyDefinition, this.modifiedProperties); } else { // If the property value was not set, we have a newly set // property. if (!this.properties.containsKey(propertyDefinition)) { addToChangeList(propertyDefinition, this.addedProperties); } else { // The last case is that we have a modified property. if (!this.modifiedProperties.contains(propertyDefinition)) { addToChangeList(propertyDefinition, this.modifiedProperties); } } } if (object instanceof ComplexProperty) { this.initComplexProperty((ComplexProperty) object); } this.properties.put(propertyDefinition, object); this.changed(); } } /* * (non-Javadoc) * * @seemicrosoft.exchange.webservices.ComplexPropertyChangedInterface# * complexPropertyChanged(microsoft.exchange.webservices.ComplexProperty) */ @Override public void complexPropertyChanged(ComplexProperty complexProperty) { this.propertyChanged(complexProperty); } }