001/*
002 * The MIT License
003 * Copyright (c) 2012 Microsoft Corporation
004 *
005 * Permission is hereby granted, free of charge, to any person obtaining a copy
006 * of this software and associated documentation files (the "Software"), to deal
007 * in the Software without restriction, including without limitation the rights
008 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009 * copies of the Software, and to permit persons to whom the Software is
010 * furnished to do so, subject to the following conditions:
011 *
012 * The above copyright notice and this permission notice shall be included in
013 * all copies or substantial portions of the Software.
014 *
015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021 * THE SOFTWARE.
022 */
023
024package microsoft.exchange.webservices.data.core.service;
025
026import microsoft.exchange.webservices.data.attribute.ServiceObjectDefinition;
027import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
028import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
029import microsoft.exchange.webservices.data.core.EwsUtilities;
030import microsoft.exchange.webservices.data.core.ExchangeService;
031import microsoft.exchange.webservices.data.core.PropertyBag;
032import microsoft.exchange.webservices.data.core.PropertySet;
033import microsoft.exchange.webservices.data.core.XmlElementNames;
034import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
035import microsoft.exchange.webservices.data.core.enumeration.service.DeleteMode;
036import microsoft.exchange.webservices.data.core.enumeration.service.SendCancellationsMode;
037import microsoft.exchange.webservices.data.core.enumeration.service.calendar.AffectedTaskOccurrence;
038import microsoft.exchange.webservices.data.core.exception.misc.InvalidOperationException;
039import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
040import microsoft.exchange.webservices.data.core.service.schema.ServiceObjectSchema;
041import microsoft.exchange.webservices.data.misc.OutParam;
042import microsoft.exchange.webservices.data.property.complex.ExtendedProperty;
043import microsoft.exchange.webservices.data.property.complex.ExtendedPropertyCollection;
044import microsoft.exchange.webservices.data.property.complex.IServiceObjectChangedDelegate;
045import microsoft.exchange.webservices.data.property.complex.ServiceId;
046import microsoft.exchange.webservices.data.property.definition.ExtendedPropertyDefinition;
047import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
048import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
049
050import java.util.ArrayList;
051import java.util.Collection;
052import java.util.List;
053
054/**
055 * Represents the base abstract class for all item and folder types.
056 */
057public 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  protected ServiceObject(ExchangeService service, ServiceId serviceId) throws Exception {
236    this(service);
237    propertyBag.setLoading(true);
238    propertyBag.setObjectFromPropertyDefinition(getIdPropertyDefinition(), serviceId);
239    propertyBag.setLoading(false);
240  }
241
242  /**
243   * Gets the schema associated with this type of object.
244   *
245   * @return ServiceObjectSchema
246   */
247  public ServiceObjectSchema schema() {
248    return this.getSchema();
249  }
250
251  /**
252   * Internal method to return the schema associated with this type of object.
253   *
254   * @return the schema
255   */
256  public abstract ServiceObjectSchema getSchema();
257
258  /**
259   * Gets the minimum required server version.
260   *
261   * @return the minimum required server version
262   */
263  public abstract ExchangeVersion getMinimumRequiredServerVersion();
264
265  /**
266   * Loads service object from XML.
267   *
268   * @param reader           the reader
269   * @param clearPropertyBag the clear property bag
270   * @throws Exception the exception
271   */
272  public void loadFromXml(EwsServiceXmlReader reader, boolean clearPropertyBag) throws Exception {
273
274    this.getPropertyBag().loadFromXml(reader, clearPropertyBag,
275        null, // propertySet
276        false); // summaryPropertiesOnly
277
278  }
279
280  // / Validates this instance.
281
282  /**
283   * Validate.
284   *
285   * @throws Exception the exception
286   */
287  protected void validate() throws Exception {
288    this.getPropertyBag().validate();
289  }
290
291  // / Loads service object from XML.
292
293  /**
294   * Load from xml.
295   *
296   * @param reader                the reader
297   * @param clearPropertyBag      the clear property bag
298   * @param requestedPropertySet  the requested property set
299   * @param summaryPropertiesOnly the summary property only
300   * @throws Exception the exception
301   */
302  public void loadFromXml(EwsServiceXmlReader reader, boolean clearPropertyBag,
303      PropertySet requestedPropertySet, boolean summaryPropertiesOnly) throws Exception {
304
305    this.getPropertyBag().loadFromXml(reader, clearPropertyBag,
306        requestedPropertySet, summaryPropertiesOnly);
307
308  }
309
310  // Clears the object's change log.
311
312  /**
313   * Clear change log.
314   */
315  public void clearChangeLog() {
316    this.getPropertyBag().clearChangeLog();
317  }
318
319  // / Writes service object as XML.
320
321  /**
322   * Write to xml.
323   *
324   * @param writer the writer
325   * @throws Exception the exception
326   */
327  public void writeToXml(EwsServiceXmlWriter writer) throws Exception {
328    this.getPropertyBag().writeToXml(writer);
329  }
330
331  // Writes service object for update as XML.
332
333  /**
334   * Write to xml for update.
335   *
336   * @param writer the writer
337   * @throws Exception the exception
338   */
339  public void writeToXmlForUpdate(EwsServiceXmlWriter writer)
340      throws Exception {
341    this.getPropertyBag().writeToXmlForUpdate(writer);
342  }
343
344  // / Loads the specified set of property on the object.
345
346  /**
347   * Internal load.
348   *
349   * @param propertySet the property set
350   * @throws Exception the exception
351   */
352  protected abstract void internalLoad(PropertySet propertySet)
353      throws Exception;
354
355  // / Deletes the object.
356
357  /**
358   * Internal delete.
359   *
360   * @param deleteMode              the delete mode
361   * @param sendCancellationsMode   the send cancellations mode
362   * @param affectedTaskOccurrences the affected task occurrences
363   * @throws Exception the exception
364   */
365  protected abstract void internalDelete(DeleteMode deleteMode,
366      SendCancellationsMode sendCancellationsMode,
367      AffectedTaskOccurrence affectedTaskOccurrences) throws Exception;
368
369  // / Loads the specified set of property. Calling this method results in a
370  // call to EWS.
371
372  /**
373   * Load.
374   *
375   * @param propertySet the property set
376   * @throws Exception the exception
377   */
378  public void load(PropertySet propertySet) throws Exception {
379    this.internalLoad(propertySet);
380  }
381
382  // Loads the first class property. Calling this method results in a call
383  // to EWS.
384
385  /**
386   * Load.
387   *
388   * @throws Exception the exception
389   */
390  public void load() throws Exception {
391    this.internalLoad(PropertySet.getFirstClassProperties());
392  }
393
394  /**
395   * Gets the value of specified property in this instance.
396   *
397   * @param propertyDefinition Definition of the property to get.
398   * @return The value of specified property in this instance.
399   * @throws Exception the exception
400   */
401  public Object getObjectFromPropertyDefinition(
402      PropertyDefinitionBase propertyDefinition) throws Exception {
403    PropertyDefinition propDef = (PropertyDefinition) propertyDefinition;
404
405    if (propDef != null) {
406      return this.getPropertyBag().getObjectFromPropertyDefinition(propDef);
407    } else {
408      // E14:226103 -- Other subclasses of PropertyDefinitionBase are not supported.
409      throw new UnsupportedOperationException(String.format(
410          "This operation isn't supported for property definition type %s.",
411          propertyDefinition.getType().getName()));
412    }
413  }
414
415  /**
416   * Try to get the value of a specified extended property in this instance.
417   *
418   * @param propertyDefinition the property definition
419   * @param propertyValue      the property value
420   * @return true, if successful
421   * @throws Exception the exception
422   */
423  protected <T> boolean tryGetExtendedProperty(Class<T> cls,
424      ExtendedPropertyDefinition propertyDefinition,
425      OutParam<T> propertyValue) throws Exception {
426    ExtendedPropertyCollection propertyCollection = this
427        .getExtendedProperties();
428
429    if ((propertyCollection != null) &&
430        propertyCollection.tryGetValue(cls, propertyDefinition, propertyValue)) {
431      return true;
432    } else {
433      propertyValue.setParam(null);
434      return false;
435    }
436  }
437
438  /**
439   * Try to get the value of a specified property in this instance.
440   *
441   * @param propertyDefinition The property definition.
442   * @param propertyValue      The property value
443   * @return True if property retrieved, false otherwise.
444   * @throws Exception
445   */
446  public boolean tryGetProperty(PropertyDefinitionBase propertyDefinition, OutParam<Object> propertyValue)
447      throws Exception {
448    return this.tryGetProperty(Object.class, propertyDefinition, propertyValue);
449  }
450
451  /**
452   * Try to get the value of a specified property in this instance.
453   *
454   * @param propertyDefinition the property definition
455   * @param propertyValue      the property value
456   * @return true, if successful
457   * @throws Exception the exception
458   */
459  public <T> boolean tryGetProperty(Class<T> cls, PropertyDefinitionBase propertyDefinition,
460      OutParam<T> propertyValue) throws Exception {
461
462    PropertyDefinition propDef = (PropertyDefinition) propertyDefinition;
463    if (propDef != null) {
464      return this.getPropertyBag().tryGetPropertyType(cls, propDef, propertyValue);
465    } else {
466      // E14:226103 -- Other subclasses of PropertyDefinitionBase are not supported.
467      throw new UnsupportedOperationException(String.format(
468          "This operation isn't supported for property definition type %s.",
469          propertyDefinition.getType().getName()));
470    }
471  }
472
473  /**
474   * Gets the collection of loaded property definitions.
475   *
476   * @return the loaded property definitions
477   * @throws Exception the exception
478   */
479  public Collection<PropertyDefinitionBase> getLoadedPropertyDefinitions()
480      throws Exception {
481
482    Collection<PropertyDefinitionBase> propDefs =
483        new ArrayList<PropertyDefinitionBase>();
484    for (PropertyDefinition propDef : this.getPropertyBag().getProperties()
485        .keySet()) {
486      propDefs.add(propDef);
487    }
488
489    if (this.getExtendedProperties() != null) {
490      for (ExtendedProperty extProp : getExtendedProperties()) {
491        propDefs.add(extProp.getPropertyDefinition());
492      }
493    }
494
495    return propDefs;
496  }
497
498  /**
499   * Gets the service.
500   *
501   * @return the service
502   */
503  public ExchangeService getService() {
504    return service;
505  }
506
507  /**
508   * Sets the service.
509   *
510   * @param service the new service
511   */
512  protected void setService(ExchangeService service) {
513    this.service = service;
514  }
515
516  // / The property definition for the Id of this object.
517
518  /**
519   * Gets the id property definition.
520   *
521   * @return the id property definition
522   */
523  public PropertyDefinition getIdPropertyDefinition() {
524    return null;
525  }
526
527  // / The unique Id of this object.
528
529  /**
530   * Gets the id.
531   *
532   * @return the id
533   * @throws ServiceLocalException the service local exception
534   */
535  public ServiceId getId() throws ServiceLocalException {
536    PropertyDefinition idPropertyDefinition = this
537        .getIdPropertyDefinition();
538
539    OutParam<Object> serviceId = new OutParam<Object>();
540
541    if (idPropertyDefinition != null) {
542      this.getPropertyBag().tryGetValue(idPropertyDefinition, serviceId);
543    }
544
545    return (ServiceId) serviceId.getParam();
546  }
547
548  // / Indicates whether this object is a real store item, or if it's a local
549  // object
550  // / that has yet to be saved.
551
552  /**
553   * Checks if is new.
554   *
555   * @return true, if is new
556   * @throws ServiceLocalException the service local exception
557   */
558  public boolean isNew() throws ServiceLocalException {
559
560    ServiceId id = this.getId();
561
562    return id == null ? true : !id.isValid();
563
564  }
565
566  // / Gets a value indicating whether the object has been modified and should
567  // be saved.
568
569  /**
570   * Checks if is dirty.
571   *
572   * @return true, if is dirty
573   */
574  public boolean isDirty() {
575    return this.getPropertyBag().getIsDirty();
576
577  }
578
579  // Gets the extended property collection.
580
581  /**
582   * Gets the extended property.
583   *
584   * @return the extended property
585   * @throws Exception the exception
586   */
587  protected ExtendedPropertyCollection getExtendedProperties()
588      throws Exception {
589    return null;
590  }
591
592  /**
593   * Checks is the string is null or empty.
594   *
595   * @param namespacePrefix the namespace prefix
596   * @return true, if is null or empty
597   */
598  private boolean isNullOrEmpty(String namespacePrefix) {
599    return (namespacePrefix == null || namespacePrefix.isEmpty());
600
601  }
602
603  /**
604   * The on change.
605   */
606  private List<IServiceObjectChangedDelegate> onChange =
607      new ArrayList<IServiceObjectChangedDelegate>();
608
609  /**
610   * Adds the service object changed event.
611   *
612   * @param change the change
613   */
614  public void addServiceObjectChangedEvent(
615      IServiceObjectChangedDelegate change) {
616    this.onChange.add(change);
617  }
618
619  /**
620   * Removes the service object changed event.
621   *
622   * @param change the change
623   */
624  public void removeServiceObjectChangedEvent(
625      IServiceObjectChangedDelegate change) {
626    this.onChange.remove(change);
627  }
628
629  /**
630   * Clear service object changed event.
631   */
632  public void clearServiceObjectChangedEvent() {
633    this.onChange.clear();
634  }
635
636}