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.misc; 025 026import microsoft.exchange.webservices.data.core.EwsServiceXmlReader; 027import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter; 028import microsoft.exchange.webservices.data.core.EwsUtilities; 029import microsoft.exchange.webservices.data.core.ExchangeService; 030import microsoft.exchange.webservices.data.core.XmlAttributeNames; 031import microsoft.exchange.webservices.data.core.XmlElementNames; 032import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion; 033import microsoft.exchange.webservices.data.core.enumeration.misc.UserConfigurationProperties; 034import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName; 035import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; 036import microsoft.exchange.webservices.data.core.exception.misc.InvalidOperationException; 037import microsoft.exchange.webservices.data.core.exception.service.local.PropertyException; 038import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException; 039import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException; 040import microsoft.exchange.webservices.data.property.complex.FolderId; 041import microsoft.exchange.webservices.data.property.complex.ItemId; 042import microsoft.exchange.webservices.data.property.complex.UserConfigurationDictionary; 043import microsoft.exchange.webservices.data.security.XmlNodeType; 044import org.apache.commons.codec.binary.Base64; 045import org.apache.commons.logging.Log; 046import org.apache.commons.logging.LogFactory; 047 048import javax.xml.stream.XMLStreamException; 049 050import java.util.EnumSet; 051 052/** 053 * Represents an object that can be used to store user-defined configuration 054 * settings. 055 */ 056public class UserConfiguration { 057 058 private static final Log LOG = LogFactory.getLog(UserConfiguration.class); 059 060 /** 061 * The object version. 062 */ 063 private static ExchangeVersion ObjectVersion = ExchangeVersion.Exchange2010; 064 065 /** 066 * For consistency with ServiceObject behavior, access to ItemId is 067 * permitted for a new object. 068 */ 069 /** 070 * The Constant PropertiesAvailableForNewObject. 071 */ 072 private final static EnumSet<UserConfigurationProperties> 073 PropertiesAvailableForNewObject = 074 EnumSet.of(UserConfigurationProperties.BinaryData, 075 UserConfigurationProperties.Dictionary, 076 UserConfigurationProperties.XmlData); 077 078 /** 079 * The No property. 080 */ 081 private final UserConfigurationProperties NoProperties = 082 UserConfigurationProperties.values()[0]; 083 084 /** 085 * The service. 086 */ 087 private ExchangeService service; 088 089 /** 090 * The name. 091 */ 092 private String name; 093 094 /** 095 * The parent folder id. 096 */ 097 private FolderId parentFolderId = null; 098 099 /** 100 * The item id. 101 */ 102 private ItemId itemId = null; 103 104 /** 105 * The dictionary. 106 */ 107 private UserConfigurationDictionary dictionary = null; 108 109 /** 110 * The xml data. 111 */ 112 private byte[] xmlData = null; 113 114 /** 115 * The binary data. 116 */ 117 private byte[] binaryData = null; 118 119 /** 120 * The property available for access. 121 */ 122 private EnumSet<UserConfigurationProperties> propertiesAvailableForAccess; 123 124 /** 125 * The updated property. 126 */ 127 private EnumSet<UserConfigurationProperties> updatedProperties; 128 129 /** 130 * Indicates whether changes trigger an update or create operation. 131 */ 132 private boolean isNew = false; 133 134 /** 135 * Initializes a new instance of <see cref="UserConfiguration"/> class. 136 * 137 * @param service The service to which the user configuration is bound. 138 * @throws Exception the exception 139 */ 140 public UserConfiguration(ExchangeService service) throws Exception { 141 this(service, PropertiesAvailableForNewObject); 142 } 143 144 /** 145 * Writes a byte array to Xml. 146 * 147 * @param writer the writer 148 * @param byteArray byte array to write 149 * @param xmlElementName name of the Xml element 150 * @throws XMLStreamException the XML stream exception 151 * @throws ServiceXmlSerializationException the service xml serialization exception 152 */ 153 private static void writeByteArrayToXml(EwsServiceXmlWriter writer, 154 byte[] byteArray, String xmlElementName) throws XMLStreamException, ServiceXmlSerializationException { 155 EwsUtilities.ewsAssert(writer != null, "UserConfiguration.WriteByteArrayToXml", "writer is null"); 156 EwsUtilities.ewsAssert(xmlElementName != null, "UserConfiguration.WriteByteArrayToXml", 157 "xmlElementName is null"); 158 159 writer.writeStartElement(XmlNamespace.Types, xmlElementName); 160 161 if (byteArray != null && byteArray.length > 0) { 162 writer.writeValue(Base64.encodeBase64String(byteArray), xmlElementName); 163 } 164 165 writer.writeEndElement(); 166 } 167 168 169 /** 170 * Writes to Xml. 171 * 172 * @param writer The writer. 173 * @param xmlNamespace The XML namespace. 174 * @param name The user configuration name. 175 * @param parentFolderId The Id of the folder containing the user configuration. 176 * @throws Exception the exception 177 */ 178 public static void writeUserConfigurationNameToXml(EwsServiceXmlWriter writer, XmlNamespace xmlNamespace, 179 String name, FolderId parentFolderId) throws Exception { 180 EwsUtilities.ewsAssert(writer != null, "UserConfiguration.WriteUserConfigurationNameToXml", 181 "writer is null"); 182 EwsUtilities.ewsAssert(name != null, "UserConfiguration.WriteUserConfigurationNameToXml", "name is null"); 183 EwsUtilities.ewsAssert(parentFolderId != null, "UserConfiguration.WriteUserConfigurationNameToXml", 184 "parentFolderId is null"); 185 186 writer.writeStartElement(xmlNamespace, 187 XmlElementNames.UserConfigurationName); 188 189 writer.writeAttributeValue(XmlAttributeNames.Name, name); 190 191 parentFolderId.writeToXml(writer); 192 193 writer.writeEndElement(); 194 } 195 196 /** 197 * Initializes a new instance of <see cref="UserConfiguration"/> class. 198 * 199 * @param service The service to which the user configuration is bound. 200 * @param requestedProperties The property requested for this user configuration. 201 * @throws Exception the exception 202 */ 203 public UserConfiguration(ExchangeService service, EnumSet<UserConfigurationProperties> requestedProperties) 204 throws Exception { 205 EwsUtilities.validateParam(service, "service"); 206 207 if (service.getRequestedServerVersion().ordinal() < UserConfiguration.ObjectVersion.ordinal()) { 208 throw new ServiceVersionException(String.format( 209 "The object type %s is only valid for Exchange Server version %s or later versions.", this 210 .getClass().getName(), UserConfiguration.ObjectVersion)); 211 } 212 213 this.service = service; 214 this.isNew = true; 215 216 this.initializeProperties(requestedProperties); 217 } 218 219 /** 220 * Gets the name of the user configuration. 221 * 222 * @return the name 223 */ 224 public String getName() { 225 return this.name; 226 } 227 228 /** 229 * Sets the name. 230 * 231 * @param value the new name 232 */ 233 public void setName(String value) { 234 this.name = value; 235 } 236 237 /** 238 * Gets the Id of the folder containing the user configuration. 239 * 240 * @return the parent folder id 241 */ 242 public FolderId getParentFolderId() { 243 return this.parentFolderId; 244 } 245 246 /** 247 * Sets the parent folder id. 248 * 249 * @param value the new parent folder id 250 */ 251 public void setParentFolderId(FolderId value) { 252 this.parentFolderId = value; 253 } 254 255 /** 256 * Gets the Id of the user configuration. 257 * 258 * @return the item id 259 */ 260 public ItemId getItemId() { 261 return this.itemId; 262 } 263 264 /** 265 * Gets the dictionary of the user configuration. 266 * 267 * @return the dictionary 268 */ 269 public UserConfigurationDictionary getDictionary() { 270 return this.dictionary; 271 } 272 273 /** 274 * Gets the xml data of the user configuration. 275 * 276 * @return the xml data 277 * @throws PropertyException the property exception 278 */ 279 public byte[] getXmlData() throws PropertyException { 280 281 this.validatePropertyAccess(UserConfigurationProperties.XmlData); 282 283 return this.xmlData; 284 } 285 286 /** 287 * Sets the xml data. 288 * 289 * @param value the new xml data 290 */ 291 public void setXmlData(byte[] value) { 292 this.xmlData = value; 293 294 this.markPropertyForUpdate(UserConfigurationProperties.XmlData); 295 } 296 297 /** 298 * Gets the binary data of the user configuration. 299 * 300 * @return the binary data 301 * @throws PropertyException the property exception 302 */ 303 public byte[] getBinaryData() throws PropertyException { 304 this.validatePropertyAccess(UserConfigurationProperties.BinaryData); 305 306 return this.binaryData; 307 308 } 309 310 /** 311 * Sets the binary data. 312 * 313 * @param value the new binary data 314 */ 315 public void setBinaryData(byte[] value) { 316 this.binaryData = value; 317 this.markPropertyForUpdate(UserConfigurationProperties.BinaryData); 318 } 319 320 /** 321 * Gets a value indicating whether this user configuration has been 322 * modified. 323 * 324 * @return the checks if is dirty 325 */ 326 public boolean getIsDirty() { 327 return (!this.updatedProperties.contains(NoProperties)) 328 || this.dictionary.getIsDirty(); 329 } 330 331 /** 332 * Binds to an existing user configuration and loads the specified 333 * property. Calling this method results in a call to EWS. 334 * 335 * @param service The service to which the user configuration is bound. 336 * @param name The name of the user configuration. 337 * @param parentFolderId The Id of the folder containing the user configuration. 338 * @param properties The property to load. 339 * @return A user configuration instance. 340 * @throws IndexOutOfBoundsException the index out of bounds exception 341 * @throws Exception the exception 342 */ 343 public static UserConfiguration bind(ExchangeService service, String name, 344 FolderId parentFolderId, UserConfigurationProperties properties) 345 throws IndexOutOfBoundsException, Exception { 346 347 UserConfiguration result = service.getUserConfiguration(name, 348 parentFolderId, properties); 349 result.isNew = false; 350 return result; 351 } 352 353 /** 354 * Binds to an existing user configuration and loads the specified 355 * property. 356 * 357 * @param service The service to which the user configuration is bound. 358 * @param name The name of the user configuration. 359 * @param parentFolderName The name of the folder containing the user configuration. 360 * @param properties The property to load. 361 * @return A user configuration instance. 362 * @throws IndexOutOfBoundsException the index out of bounds exception 363 * @throws Exception the exception 364 */ 365 public static UserConfiguration bind(ExchangeService service, String name, 366 WellKnownFolderName parentFolderName, 367 UserConfigurationProperties properties) 368 throws IndexOutOfBoundsException, Exception { 369 return UserConfiguration.bind(service, name, new FolderId( 370 parentFolderName), properties); 371 } 372 373 /** 374 * Saves the user configuration. Calling this method results in a call to 375 * EWS. 376 * 377 * @param name The name of the user configuration. 378 * @param parentFolderId The Id of the folder in which to save the user configuration. 379 * @throws Exception the exception 380 */ 381 public void save(String name, FolderId parentFolderId) throws Exception { 382 EwsUtilities.validateParam(name, "name"); 383 EwsUtilities.validateParam(parentFolderId, "parentFolderId"); 384 385 parentFolderId.validate(this.service.getRequestedServerVersion()); 386 387 if (!this.isNew) { 388 throw new InvalidOperationException( 389 "Calling Save isn't allowed because this user configuration isn't new. To apply local changes to this user configuration, call Update instead."); 390 } 391 392 this.parentFolderId = parentFolderId; 393 this.name = name; 394 395 this.service.createUserConfiguration(this); 396 397 this.isNew = false; 398 399 this.resetIsDirty(); 400 } 401 402 /** 403 * Saves the user configuration. Calling this method results in a call to 404 * EWS. 405 * 406 * @param name The name of the user configuration. 407 * @param parentFolderName The name of the folder in which to save the user 408 * configuration. 409 * @throws Exception the exception 410 */ 411 public void save(String name, WellKnownFolderName parentFolderName) 412 throws Exception { 413 this.save(name, new FolderId(parentFolderName)); 414 } 415 416 /** 417 * Updates the user configuration by applying local changes to the Exchange 418 * server. Calling this method results in a call to EWS 419 * 420 * @throws Exception the exception 421 */ 422 423 public void update() throws Exception { 424 if (this.isNew) { 425 throw new InvalidOperationException( 426 "This user configuration can't be updated because it's never been saved."); 427 } 428 429 if (this.isPropertyUpdated(UserConfigurationProperties.BinaryData) 430 || this 431 .isPropertyUpdated(UserConfigurationProperties. 432 Dictionary) 433 || this.isPropertyUpdated(UserConfigurationProperties. 434 XmlData)) { 435 436 this.service.updateUserConfiguration(this); 437 } 438 439 this.resetIsDirty(); 440 } 441 442 /** 443 * Deletes the user configuration. Calling this method results in a call to 444 * EWS. 445 * 446 * @throws Exception the exception 447 */ 448 public void delete() throws Exception { 449 if (this.isNew) { 450 throw new InvalidOperationException( 451 "This user configuration object can't be deleted because it's never been saved."); 452 } else { 453 this.service 454 .deleteUserConfiguration(this.name, this.parentFolderId); 455 } 456 } 457 458 /** 459 * Loads the specified property on the user configuration. Calling this 460 * method results in a call to EWS. 461 * 462 * @param properties The property to load. 463 * @throws Exception the exception 464 */ 465 public void load(UserConfigurationProperties properties) throws Exception { 466 this.initializeProperties(EnumSet.of(properties)); 467 this.service.loadPropertiesForUserConfiguration(this, properties); 468 } 469 470 /** 471 * Writes to XML. 472 * 473 * @param writer The writer. 474 * @param xmlNamespace The XML namespace. 475 * @param xmlElementName Name of the XML element. 476 * @throws Exception the exception 477 */ 478 public void writeToXml(EwsServiceXmlWriter writer, XmlNamespace xmlNamespace, String xmlElementName) throws Exception { 479 EwsUtilities.ewsAssert(writer != null, "UserConfiguration.WriteToXml", "writer is null"); 480 EwsUtilities.ewsAssert(xmlElementName != null, "UserConfiguration.WriteToXml", "xmlElementName is null"); 481 482 writer.writeStartElement(xmlNamespace, xmlElementName); 483 484 // Write the UserConfigurationName element 485 writeUserConfigurationNameToXml(writer, XmlNamespace.Types, this.name, 486 this.parentFolderId); 487 488 // Write the Dictionary element 489 if (this.isPropertyUpdated(UserConfigurationProperties.Dictionary)) { 490 this.dictionary.writeToXml(writer, XmlElementNames.Dictionary); 491 } 492 493 // Write the XmlData element 494 if (this.isPropertyUpdated(UserConfigurationProperties.XmlData)) { 495 this.writeXmlDataToXml(writer); 496 } 497 498 // Write the BinaryData element 499 if (this.isPropertyUpdated(UserConfigurationProperties.BinaryData)) { 500 this.writeBinaryDataToXml(writer); 501 } 502 503 writer.writeEndElement(); 504 } 505 506 /** 507 * Determines whether the specified property was updated. 508 * 509 * @param property property to evaluate. 510 * @return Boolean indicating whether to send the property Xml. 511 */ 512 private boolean isPropertyUpdated(UserConfigurationProperties property) { 513 boolean isPropertyDirty = false; 514 boolean isPropertyEmpty = false; 515 516 switch (property) { 517 case Dictionary: 518 isPropertyDirty = this.getDictionary().getIsDirty(); 519 isPropertyEmpty = this.getDictionary().getCount() == 0; 520 break; 521 case XmlData: 522 isPropertyDirty = this.updatedProperties.contains(property); 523 isPropertyEmpty = (this.xmlData == null) || 524 (this.xmlData.length == 0); 525 break; 526 case BinaryData: 527 isPropertyDirty = this.updatedProperties.contains(property); 528 isPropertyEmpty = (this.binaryData == null) || 529 (this.binaryData.length == 0); 530 break; 531 default: 532 EwsUtilities.ewsAssert(false, "UserConfiguration.IsPropertyUpdated", 533 "property not supported: " + property.toString()); 534 break; 535 } 536 537 // Consider the property updated, if it's been modified, and either 538 // . there's a value or 539 // . there's no value but the operation is update. 540 return isPropertyDirty && ((!isPropertyEmpty) || (!this.isNew)); 541 } 542 543 /** 544 * Writes the XmlData property to Xml. 545 * 546 * @param writer the writer 547 * @throws XMLStreamException the XML stream exception 548 * @throws ServiceXmlSerializationException the service xml serialization exception 549 */ 550 private void writeXmlDataToXml(EwsServiceXmlWriter writer) 551 throws XMLStreamException, ServiceXmlSerializationException { 552 EwsUtilities.ewsAssert(writer != null, "UserConfiguration.WriteXmlDataToXml", "writer is null"); 553 554 writeByteArrayToXml(writer, this.xmlData, XmlElementNames.XmlData); 555 } 556 557 /** 558 * Writes the BinaryData property to Xml. 559 * 560 * @param writer the writer 561 * @throws XMLStreamException the XML stream exception 562 * @throws ServiceXmlSerializationException the service xml serialization exception 563 */ 564 private void writeBinaryDataToXml(EwsServiceXmlWriter writer) 565 throws XMLStreamException, ServiceXmlSerializationException { 566 EwsUtilities.ewsAssert(writer != null, "UserConfiguration.WriteBinaryDataToXml", "writer is null"); 567 568 writeByteArrayToXml(writer, this.binaryData, 569 XmlElementNames.BinaryData); 570 } 571 572 573 574 /** 575 * Loads from XML. 576 * 577 * @param reader The reader. 578 * @throws Exception the exception 579 */ 580 public void loadFromXml(EwsServiceXmlReader reader) throws Exception { 581 EwsUtilities.ewsAssert(reader != null, "UserConfiguration.loadFromXml", "reader is null"); 582 583 reader.readStartElement(XmlNamespace.Messages, 584 XmlElementNames.UserConfiguration); 585 reader.read(); // Position at first property element 586 587 do { 588 if (reader.getNodeType().getNodeType() == XmlNodeType.START_ELEMENT) { 589 if (reader.getLocalName().equals( 590 XmlElementNames.UserConfigurationName)) { 591 String responseName = reader 592 .readAttributeValue(XmlAttributeNames.Name); 593 594 EwsUtilities.ewsAssert(this.name.equals(responseName), "UserConfiguration.loadFromXml", 595 "UserConfigurationName does not match: Expected: " + this.name 596 + " Name in response: " + responseName); 597 598 reader.skipCurrentElement(); 599 } else if (reader.getLocalName().equals(XmlElementNames.ItemId)) { 600 this.itemId = new ItemId(); 601 this.itemId.loadFromXml(reader, XmlElementNames.ItemId); 602 } else if (reader.getLocalName().equals( 603 XmlElementNames.Dictionary)) { 604 this.dictionary.loadFromXml(reader, 605 XmlElementNames.Dictionary); 606 } else if (reader.getLocalName() 607 .equals(XmlElementNames.XmlData)) { 608 this.xmlData = Base64.decodeBase64(reader.readElementValue()); 609 } else if (reader.getLocalName().equals( 610 XmlElementNames.BinaryData)) { 611 this.binaryData = Base64.decodeBase64(reader.readElementValue()); 612 } else { 613 EwsUtilities.ewsAssert(false, "UserConfiguration.loadFromXml", 614 "Xml element not supported: " + reader.getLocalName()); 615 } 616 } 617 618 // If XmlData was loaded, read is skipped because GetXmlData 619 // positions the reader at the next property. 620 reader.read(); 621 } while (!reader.isEndElement(XmlNamespace.Messages, 622 XmlElementNames.UserConfiguration)); 623 } 624 625 /** 626 * Initializes property. 627 * 628 * @param requestedProperties The property requested for this UserConfiguration. 629 */ 630 // / InitializeProperties is called in 3 cases: 631 // / . Create new object: From the UserConfiguration constructor. 632 // / . Bind to existing object: Again from the constructor. The constructor 633 // is called eventually by the GetUserConfiguration request. 634 // / . Refresh property: From the Load method. 635 private void initializeProperties( 636 EnumSet<UserConfigurationProperties> requestedProperties) { 637 this.itemId = null; 638 this.dictionary = new UserConfigurationDictionary(); 639 this.xmlData = null; 640 this.binaryData = null; 641 this.propertiesAvailableForAccess = requestedProperties; 642 643 this.resetIsDirty(); 644 } 645 646 /** 647 * Resets flags to indicate that property haven't been modified. 648 */ 649 private void resetIsDirty() { 650 try { 651 this.updatedProperties = EnumSet.of(NoProperties); 652 } catch (Exception e) { 653 LOG.error(e); 654 } 655 this.dictionary.setIsDirty(false); 656 } 657 658 /** 659 * Determines whether the specified property may be accessed. 660 * 661 * @param property Property to access. 662 * @throws PropertyException the property exception 663 */ 664 private void validatePropertyAccess(UserConfigurationProperties property) 665 throws PropertyException { 666 if (!this.propertiesAvailableForAccess.contains(property)) { 667 throw new PropertyException("You must load or assign this property before you can read its value.", property 668 .toString()); 669 } 670 } 671 672 /** 673 * Adds the passed property to updatedProperties. 674 * 675 * @param property Property to update. 676 */ 677 private void markPropertyForUpdate(UserConfigurationProperties property) { 678 this.updatedProperties.add(property); 679 this.propertiesAvailableForAccess.add(property); 680 681 } 682 683}