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}