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.misc;
025
026 import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
027 import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
028 import microsoft.exchange.webservices.data.core.EwsUtilities;
029 import microsoft.exchange.webservices.data.core.ExchangeService;
030 import microsoft.exchange.webservices.data.core.XmlAttributeNames;
031 import microsoft.exchange.webservices.data.core.XmlElementNames;
032 import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
033 import microsoft.exchange.webservices.data.core.enumeration.misc.UserConfigurationProperties;
034 import microsoft.exchange.webservices.data.core.enumeration.property.WellKnownFolderName;
035 import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
036 import microsoft.exchange.webservices.data.core.exception.misc.InvalidOperationException;
037 import microsoft.exchange.webservices.data.core.exception.service.local.PropertyException;
038 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
039 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
040 import microsoft.exchange.webservices.data.property.complex.FolderId;
041 import microsoft.exchange.webservices.data.property.complex.ItemId;
042 import microsoft.exchange.webservices.data.property.complex.UserConfigurationDictionary;
043 import microsoft.exchange.webservices.data.security.XmlNodeType;
044 import org.apache.commons.codec.binary.Base64;
045 import org.apache.commons.logging.Log;
046 import org.apache.commons.logging.LogFactory;
047
048 import javax.xml.stream.XMLStreamException;
049
050 import java.util.EnumSet;
051
052 /**
053 * Represents an object that can be used to store user-defined configuration
054 * settings.
055 */
056 public 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 }