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.service;
025    
026    import microsoft.exchange.webservices.data.attribute.ServiceObjectDefinition;
027    import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
028    import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
029    import microsoft.exchange.webservices.data.core.EwsUtilities;
030    import microsoft.exchange.webservices.data.core.ExchangeService;
031    import microsoft.exchange.webservices.data.core.PropertyBag;
032    import microsoft.exchange.webservices.data.core.PropertySet;
033    import microsoft.exchange.webservices.data.core.XmlElementNames;
034    import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
035    import microsoft.exchange.webservices.data.core.enumeration.service.DeleteMode;
036    import microsoft.exchange.webservices.data.core.enumeration.service.SendCancellationsMode;
037    import microsoft.exchange.webservices.data.core.enumeration.service.calendar.AffectedTaskOccurrence;
038    import microsoft.exchange.webservices.data.core.exception.misc.InvalidOperationException;
039    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
040    import microsoft.exchange.webservices.data.core.service.schema.ServiceObjectSchema;
041    import microsoft.exchange.webservices.data.misc.OutParam;
042    import microsoft.exchange.webservices.data.property.complex.ExtendedProperty;
043    import microsoft.exchange.webservices.data.property.complex.ExtendedPropertyCollection;
044    import microsoft.exchange.webservices.data.property.complex.IServiceObjectChangedDelegate;
045    import microsoft.exchange.webservices.data.property.complex.ServiceId;
046    import microsoft.exchange.webservices.data.property.definition.ExtendedPropertyDefinition;
047    import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
048    import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
049    
050    import java.util.ArrayList;
051    import java.util.Collection;
052    import java.util.List;
053    
054    /**
055     * Represents the base abstract class for all item and folder types.
056     */
057    public abstract class ServiceObject {
058    
059      /**
060       * The lock object.
061       */
062      private Object lockObject = new Object();
063    
064      /**
065       * The service.
066       */
067      private ExchangeService service;
068    
069      /**
070       * The property bag.
071       */
072      private PropertyBag propertyBag;
073    
074      /**
075       * The xml element name.
076       */
077      private String xmlElementName;
078    
079      /**
080       * Triggers dispatch of the change event.
081       */
082      public void changed() {
083    
084        for (IServiceObjectChangedDelegate change : this.onChange) {
085          change.serviceObjectChanged(this);
086        }
087      }
088    
089      /**
090       * Throws exception if this is a new service object.
091       *
092       * @throws InvalidOperationException the invalid operation exception
093       * @throws ServiceLocalException     the service local exception
094       */
095      public void throwIfThisIsNew() throws InvalidOperationException,
096          ServiceLocalException {
097        if (this.isNew()) {
098          throw new InvalidOperationException(
099              "This operation can't be performed because this service object doesn't have an Id.");
100        }
101      }
102    
103      /**
104       * Throws exception if this is not a new service object.
105       *
106       * @throws InvalidOperationException the invalid operation exception
107       * @throws ServiceLocalException     the service local exception
108       */
109      protected void throwIfThisIsNotNew() throws InvalidOperationException,
110          ServiceLocalException {
111        if (!this.isNew()) {
112          throw new InvalidOperationException(
113              "This operation can't be performed because this service object already has an ID. To update this service object, use the Update() method instead.");
114        }
115      }
116    
117      // / This methods lets subclasses of ServiceObject override the default
118      // mechanism
119      // / by which the XML element name associated with their type is retrieved.
120    
121      /**
122       * This methods lets subclasses of ServiceObject override the default
123       * mechanism by which the XML element name associated with their type is
124       * retrieved.
125       *
126       * @return String
127       */
128      protected String getXmlElementNameOverride() {
129        return null;
130      }
131    
132      /**
133       * GetXmlElementName retrieves the XmlElementName of this type based on the
134       * EwsObjectDefinition attribute that decorates it, if present.
135       *
136       * @return The XML element name associated with this type.
137       */
138      public String getXmlElementName() {
139        if (this.isNullOrEmpty(this.xmlElementName)) {
140          this.xmlElementName = this.getXmlElementNameOverride();
141          if (this.isNullOrEmpty(this.xmlElementName)) {
142            synchronized (this.lockObject) {
143    
144              ServiceObjectDefinition annotation = this.getClass()
145                  .getAnnotation(ServiceObjectDefinition.class);
146              if (null != annotation) {
147                this.xmlElementName = annotation.xmlElementName();
148              }
149            }
150          }
151        }
152        EwsUtilities
153            .ewsAssert(!isNullOrEmpty(this.xmlElementName), "EwsObject.GetXmlElementName", String
154                .format("The class %s does not have an " + "associated XML element name.",
155                        this.getClass().getName()));
156    
157        return this.xmlElementName;
158      }
159    
160      /**
161       * Gets the name of the change XML element.
162       *
163       * @return the change xml element name
164       */
165      public String getChangeXmlElementName() {
166        return XmlElementNames.ItemChange;
167      }
168    
169      /**
170       * Gets the name of the set field XML element.
171       *
172       * @return String
173       */
174      public String getSetFieldXmlElementName() {
175        return XmlElementNames.SetItemField;
176      }
177    
178      /**
179       * Gets the name of the delete field XML element.
180       *
181       * @return String
182       */
183      public String getDeleteFieldXmlElementName() {
184        return XmlElementNames.DeleteItemField;
185      }
186    
187      /**
188       * Gets a value indicating whether a time zone SOAP header should be emitted
189       * in a CreateItem or UpdateItem request so this item can be property saved
190       * or updated.
191       *
192       * @param isUpdateOperation the is update operation
193       * @return boolean
194       * @throws ServiceLocalException
195       * @throws Exception
196       */
197      protected boolean getIsTimeZoneHeaderRequired(boolean isUpdateOperation)
198          throws ServiceLocalException, Exception {
199        return false;
200      }
201    
202      /**
203       * Determines whether property defined with
204       * ScopedDateTimePropertyDefinition require custom time zone scoping.
205       *
206       * @return boolean
207       */
208      protected boolean getIsCustomDateTimeScopingRequired() {
209        return false;
210      }
211    
212      /**
213       * The property bag holding property values for this object.
214       *
215       * @return the property bag
216       */
217      public PropertyBag getPropertyBag() {
218        return this.propertyBag;
219      }
220    
221      /**
222       * Internal constructor.
223       *
224       * @param service the service
225       * @throws Exception the exception
226       */
227      protected ServiceObject(ExchangeService service) throws Exception {
228        EwsUtilities.validateParam(service, "service");
229        EwsUtilities.validateServiceObjectVersion(this, service
230            .getRequestedServerVersion());
231        this.service = service;
232        this.propertyBag = new PropertyBag(this);
233      }
234    
235      /**
236       * Gets the schema associated with this type of object.
237       *
238       * @return ServiceObjectSchema
239       */
240      public ServiceObjectSchema schema() {
241        return this.getSchema();
242      }
243    
244      /**
245       * Internal method to return the schema associated with this type of object.
246       *
247       * @return the schema
248       */
249      public abstract ServiceObjectSchema getSchema();
250    
251      /**
252       * Gets the minimum required server version.
253       *
254       * @return the minimum required server version
255       */
256      public abstract ExchangeVersion getMinimumRequiredServerVersion();
257    
258      /**
259       * Loads service object from XML.
260       *
261       * @param reader           the reader
262       * @param clearPropertyBag the clear property bag
263       * @throws Exception the exception
264       */
265      public void loadFromXml(EwsServiceXmlReader reader, boolean clearPropertyBag) throws Exception {
266    
267        this.getPropertyBag().loadFromXml(reader, clearPropertyBag,
268            null, // propertySet
269            false); // summaryPropertiesOnly
270    
271      }
272    
273      // / Validates this instance.
274    
275      /**
276       * Validate.
277       *
278       * @throws Exception the exception
279       */
280      protected void validate() throws Exception {
281        this.getPropertyBag().validate();
282      }
283    
284      // / Loads service object from XML.
285    
286      /**
287       * Load from xml.
288       *
289       * @param reader                the reader
290       * @param clearPropertyBag      the clear property bag
291       * @param requestedPropertySet  the requested property set
292       * @param summaryPropertiesOnly the summary property only
293       * @throws Exception the exception
294       */
295      public void loadFromXml(EwsServiceXmlReader reader, boolean clearPropertyBag,
296          PropertySet requestedPropertySet, boolean summaryPropertiesOnly) throws Exception {
297    
298        this.getPropertyBag().loadFromXml(reader, clearPropertyBag,
299            requestedPropertySet, summaryPropertiesOnly);
300    
301      }
302    
303      // Clears the object's change log.
304    
305      /**
306       * Clear change log.
307       */
308      public void clearChangeLog() {
309        this.getPropertyBag().clearChangeLog();
310      }
311    
312      // / Writes service object as XML.
313    
314      /**
315       * Write to xml.
316       *
317       * @param writer the writer
318       * @throws Exception the exception
319       */
320      public void writeToXml(EwsServiceXmlWriter writer) throws Exception {
321        this.getPropertyBag().writeToXml(writer);
322      }
323    
324      // Writes service object for update as XML.
325    
326      /**
327       * Write to xml for update.
328       *
329       * @param writer the writer
330       * @throws Exception the exception
331       */
332      public void writeToXmlForUpdate(EwsServiceXmlWriter writer)
333          throws Exception {
334        this.getPropertyBag().writeToXmlForUpdate(writer);
335      }
336    
337      // / Loads the specified set of property on the object.
338    
339      /**
340       * Internal load.
341       *
342       * @param propertySet the property set
343       * @throws Exception the exception
344       */
345      protected abstract void internalLoad(PropertySet propertySet)
346          throws Exception;
347    
348      // / Deletes the object.
349    
350      /**
351       * Internal delete.
352       *
353       * @param deleteMode              the delete mode
354       * @param sendCancellationsMode   the send cancellations mode
355       * @param affectedTaskOccurrences the affected task occurrences
356       * @throws Exception the exception
357       */
358      protected abstract void internalDelete(DeleteMode deleteMode,
359          SendCancellationsMode sendCancellationsMode,
360          AffectedTaskOccurrence affectedTaskOccurrences) throws Exception;
361    
362      // / Loads the specified set of property. Calling this method results in a
363      // call to EWS.
364    
365      /**
366       * Load.
367       *
368       * @param propertySet the property set
369       * @throws Exception the exception
370       */
371      public void load(PropertySet propertySet) throws Exception {
372        this.internalLoad(propertySet);
373      }
374    
375      // Loads the first class property. Calling this method results in a call
376      // to EWS.
377    
378      /**
379       * Load.
380       *
381       * @throws Exception the exception
382       */
383      public void load() throws Exception {
384        this.internalLoad(PropertySet.getFirstClassProperties());
385      }
386    
387      /**
388       * Gets the value of specified property in this instance.
389       *
390       * @param propertyDefinition Definition of the property to get.
391       * @return The value of specified property in this instance.
392       * @throws Exception the exception
393       */
394      public Object getObjectFromPropertyDefinition(
395          PropertyDefinitionBase propertyDefinition) throws Exception {
396        PropertyDefinition propDef = (PropertyDefinition) propertyDefinition;
397    
398        if (propDef != null) {
399          return this.getPropertyBag().getObjectFromPropertyDefinition(propDef);
400        } else {
401          // E14:226103 -- Other subclasses of PropertyDefinitionBase are not supported.
402          throw new UnsupportedOperationException(String.format(
403              "This operation isn't supported for property definition type %s.",
404              propertyDefinition.getType().getName()));
405        }
406      }
407    
408      /**
409       * Try to get the value of a specified extended property in this instance.
410       *
411       * @param propertyDefinition the property definition
412       * @param propertyValue      the property value
413       * @return true, if successful
414       * @throws Exception the exception
415       */
416      protected <T> boolean tryGetExtendedProperty(Class<T> cls,
417          ExtendedPropertyDefinition propertyDefinition,
418          OutParam<T> propertyValue) throws Exception {
419        ExtendedPropertyCollection propertyCollection = this
420            .getExtendedProperties();
421    
422        if ((propertyCollection != null) &&
423            propertyCollection.tryGetValue(cls, propertyDefinition, propertyValue)) {
424          return true;
425        } else {
426          propertyValue.setParam(null);
427          return false;
428        }
429      }
430    
431      /**
432       * Try to get the value of a specified property in this instance.
433       *
434       * @param propertyDefinition The property definition.
435       * @param propertyValue      The property value
436       * @return True if property retrieved, false otherwise.
437       * @throws Exception
438       */
439      public boolean tryGetProperty(PropertyDefinitionBase propertyDefinition, OutParam<Object> propertyValue)
440          throws Exception {
441        return this.tryGetProperty(Object.class, propertyDefinition, propertyValue);
442      }
443    
444      /**
445       * Try to get the value of a specified property in this instance.
446       *
447       * @param propertyDefinition the property definition
448       * @param propertyValue      the property value
449       * @return true, if successful
450       * @throws Exception the exception
451       */
452      public <T> boolean tryGetProperty(Class<T> cls, PropertyDefinitionBase propertyDefinition,
453          OutParam<T> propertyValue) throws Exception {
454    
455        PropertyDefinition propDef = (PropertyDefinition) propertyDefinition;
456        if (propDef != null) {
457          return this.getPropertyBag().tryGetPropertyType(cls, propDef, propertyValue);
458        } else {
459          // E14:226103 -- Other subclasses of PropertyDefinitionBase are not supported.
460          throw new UnsupportedOperationException(String.format(
461              "This operation isn't supported for property definition type %s.",
462              propertyDefinition.getType().getName()));
463        }
464      }
465    
466      /**
467       * Gets the collection of loaded property definitions.
468       *
469       * @return the loaded property definitions
470       * @throws Exception the exception
471       */
472      public Collection<PropertyDefinitionBase> getLoadedPropertyDefinitions()
473          throws Exception {
474    
475        Collection<PropertyDefinitionBase> propDefs =
476            new ArrayList<PropertyDefinitionBase>();
477        for (PropertyDefinition propDef : this.getPropertyBag().getProperties()
478            .keySet()) {
479          propDefs.add(propDef);
480        }
481    
482        if (this.getExtendedProperties() != null) {
483          for (ExtendedProperty extProp : getExtendedProperties()) {
484            propDefs.add(extProp.getPropertyDefinition());
485          }
486        }
487    
488        return propDefs;
489      }
490    
491      /**
492       * Gets the service.
493       *
494       * @return the service
495       */
496      public ExchangeService getService() {
497        return service;
498      }
499    
500      /**
501       * Sets the service.
502       *
503       * @param service the new service
504       */
505      protected void setService(ExchangeService service) {
506        this.service = service;
507      }
508    
509      // / The property definition for the Id of this object.
510    
511      /**
512       * Gets the id property definition.
513       *
514       * @return the id property definition
515       */
516      public PropertyDefinition getIdPropertyDefinition() {
517        return null;
518      }
519    
520      // / The unique Id of this object.
521    
522      /**
523       * Gets the id.
524       *
525       * @return the id
526       * @throws ServiceLocalException the service local exception
527       */
528      public ServiceId getId() throws ServiceLocalException {
529        PropertyDefinition idPropertyDefinition = this
530            .getIdPropertyDefinition();
531    
532        OutParam<Object> serviceId = new OutParam<Object>();
533    
534        if (idPropertyDefinition != null) {
535          this.getPropertyBag().tryGetValue(idPropertyDefinition, serviceId);
536        }
537    
538        return (ServiceId) serviceId.getParam();
539      }
540    
541      // / Indicates whether this object is a real store item, or if it's a local
542      // object
543      // / that has yet to be saved.
544    
545      /**
546       * Checks if is new.
547       *
548       * @return true, if is new
549       * @throws ServiceLocalException the service local exception
550       */
551      public boolean isNew() throws ServiceLocalException {
552    
553        ServiceId id = this.getId();
554    
555        return id == null ? true : !id.isValid();
556    
557      }
558    
559      // / Gets a value indicating whether the object has been modified and should
560      // be saved.
561    
562      /**
563       * Checks if is dirty.
564       *
565       * @return true, if is dirty
566       */
567      public boolean isDirty() {
568        return this.getPropertyBag().getIsDirty();
569    
570      }
571    
572      // Gets the extended property collection.
573    
574      /**
575       * Gets the extended property.
576       *
577       * @return the extended property
578       * @throws Exception the exception
579       */
580      protected ExtendedPropertyCollection getExtendedProperties()
581          throws Exception {
582        return null;
583      }
584    
585      /**
586       * Checks is the string is null or empty.
587       *
588       * @param namespacePrefix the namespace prefix
589       * @return true, if is null or empty
590       */
591      private boolean isNullOrEmpty(String namespacePrefix) {
592        return (namespacePrefix == null || namespacePrefix.isEmpty());
593    
594      }
595    
596      /**
597       * The on change.
598       */
599      private List<IServiceObjectChangedDelegate> onChange =
600          new ArrayList<IServiceObjectChangedDelegate>();
601    
602      /**
603       * Adds the service object changed event.
604       *
605       * @param change the change
606       */
607      public void addServiceObjectChangedEvent(
608          IServiceObjectChangedDelegate change) {
609        this.onChange.add(change);
610      }
611    
612      /**
613       * Removes the service object changed event.
614       *
615       * @param change the change
616       */
617      public void removeServiceObjectChangedEvent(
618          IServiceObjectChangedDelegate change) {
619        this.onChange.remove(change);
620      }
621    
622      /**
623       * Clear service object changed event.
624       */
625      public void clearServiceObjectChangedEvent() {
626        this.onChange.clear();
627      }
628    
629    }