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;
025
026 import microsoft.exchange.webservices.data.ISelfValidate;
027 import microsoft.exchange.webservices.data.core.request.ServiceRequestBase;
028 import microsoft.exchange.webservices.data.core.enumeration.property.BasePropertySet;
029 import microsoft.exchange.webservices.data.core.enumeration.property.BodyType;
030 import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
031 import microsoft.exchange.webservices.data.core.enumeration.property.PropertyDefinitionFlags;
032 import microsoft.exchange.webservices.data.core.enumeration.service.ServiceObjectType;
033 import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
034 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceValidationException;
035 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
036 import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
037 import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
038 import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
039
040 import javax.xml.stream.XMLStreamException;
041
042 import java.util.ArrayList;
043 import java.util.Arrays;
044 import java.util.HashMap;
045 import java.util.Iterator;
046 import java.util.List;
047 import java.util.Map;
048
049 /**
050 * Represents a set of item or folder property. Property sets are used to
051 * indicate what property of an item or folder should be loaded when binding
052 * to an existing item or folder or when loading an item or folder's property.
053 */
054 public final class PropertySet implements ISelfValidate,
055 Iterable<PropertyDefinitionBase> {
056
057 /**
058 * The Constant IdOnly.
059 */
060 public static final PropertySet IdOnly = PropertySet.
061 createReadonlyPropertySet(BasePropertySet.IdOnly);
062
063 /**
064 * Returns a predefined property set that only includes the Id property.
065 *
066 * @return Returns a predefined property set that only includes the Id
067 * property.
068 */
069 public static PropertySet getIdOnly() {
070 return IdOnly;
071 }
072
073 /**
074 * The Constant FirstClassProperties.
075 */
076 public static final PropertySet FirstClassProperties = PropertySet.
077 createReadonlyPropertySet(BasePropertySet.FirstClassProperties);
078
079 /**
080 * Returns a predefined property set that includes the first class
081 * property of an item or folder.
082 *
083 * @return A predefined property set that includes the first class
084 * property of an item or folder.
085 */
086 public static PropertySet getFirstClassProperties() {
087 return FirstClassProperties;
088 }
089
090 /**
091 * Maps BasePropertySet values to EWS's BaseShape values.
092 */
093 private static LazyMember<Map<BasePropertySet, String>> defaultPropertySetMap =
094 new LazyMember<Map<BasePropertySet, String>>(new
095 ILazyMember<Map<BasePropertySet, String>>() {
096 @Override
097 public Map<BasePropertySet, String> createInstance() {
098 Map<BasePropertySet, String> result = new
099 HashMap<BasePropertySet, String>();
100 result.put(BasePropertySet.IdOnly,
101 BasePropertySet.IdOnly
102 .getBaseShapeValue());
103 result.put(BasePropertySet.FirstClassProperties,
104 BasePropertySet.FirstClassProperties
105 .getBaseShapeValue());
106 return result;
107 }
108 });
109 /**
110 * The base property set this property set is based upon.
111 */
112 private BasePropertySet basePropertySet;
113
114 /**
115 * The list of additional property included in this property set.
116 */
117 private List<PropertyDefinitionBase> additionalProperties = new
118 ArrayList<PropertyDefinitionBase>();
119
120 /**
121 * The requested body type for get and find operations. If null, the
122 * "best body" is returned.
123 */
124 private BodyType requestedBodyType;
125
126 /**
127 * Value indicating whether or not the server should filter HTML content.
128 */
129 private Boolean filterHtml;
130
131 /**
132 * Value indicating whether or not the server
133 * should convert HTML code page to UTF8.
134 */
135 private Boolean convertHtmlCodePageToUTF8;
136
137 /**
138 * Value indicating whether or not this PropertySet can be modified.
139 */
140 private boolean isReadOnly;
141
142 /**
143 * Initializes a new instance of PropertySet.
144 *
145 * @param basePropertySet The base property set to base the property set upon.
146 * @param additionalProperties Additional property to include in the property set. Property
147 * definitions are available as static members from schema
148 * classes (for example, EmailMessageSchema.Subject,
149 * AppointmentSchema.Start, ContactSchema.GivenName, etc.)
150 */
151 public PropertySet(BasePropertySet basePropertySet,
152 PropertyDefinitionBase... additionalProperties) {
153 this.basePropertySet = basePropertySet;
154 if (null != additionalProperties) {
155 this.additionalProperties.addAll(Arrays.asList(additionalProperties));
156 }
157 }
158
159 /**
160 * Initializes a new instance of PropertySet.
161 *
162 * @param basePropertySet The base property set to base the property set upon.
163 * @param additionalProperties Additional property to include in the property set. Property
164 * definitions are available as static members from schema
165 * classes (for example, EmailMessageSchema.Subject,
166 * AppointmentSchema.Start, ContactSchema.GivenName, etc.)
167 */
168 public PropertySet(BasePropertySet basePropertySet,
169 Iterator<PropertyDefinitionBase> additionalProperties) {
170 this.basePropertySet = basePropertySet;
171 if (null != additionalProperties) {
172 while (additionalProperties.hasNext()) {
173 this.additionalProperties.add(additionalProperties.next());
174 }
175 }
176 }
177
178 /**
179 * Initializes a new instance of PropertySet based upon
180 * BasePropertySet.IdOnly.
181 */
182 public PropertySet() {
183 this.basePropertySet = BasePropertySet.IdOnly;
184 }
185
186 /**
187 * Initializes a new instance of PropertySet.
188 *
189 * @param basePropertySet The base property set to base the property set upon.
190 */
191 public PropertySet(BasePropertySet basePropertySet) {
192 this.basePropertySet = basePropertySet;
193 }
194
195 /**
196 * Initializes a new instance of PropertySet based upon
197 * BasePropertySet.IdOnly.
198 *
199 * @param additionalProperties Additional property to include in the property set. Property
200 * definitions are available as static members from schema
201 * classes (for example, EmailMessageSchema.Subject,
202 * AppointmentSchema.Start, ContactSchema.GivenName, etc.)
203 */
204 public PropertySet(PropertyDefinitionBase... additionalProperties) {
205 this(BasePropertySet.IdOnly, additionalProperties);
206 }
207
208 /**
209 * Initializes a new instance of PropertySet based upon
210 * BasePropertySet.IdOnly.
211 *
212 * @param additionalProperties Additional property to include in the property set. Property
213 * definitions are available as static members from schema
214 * classes (for example, EmailMessageSchema.Subject,
215 * AppointmentSchema.Start, ContactSchema.GivenName, etc.)
216 */
217 public PropertySet(Iterator<PropertyDefinitionBase> additionalProperties) {
218 this(BasePropertySet.IdOnly, additionalProperties);
219 }
220
221 /**
222 * Implements an implicit conversion between
223 * PropertySet and BasePropertySet.
224 *
225 * @param basePropertySet The BasePropertySet value to convert from.
226 * @return A PropertySet instance based on the specified base property set.
227 */
228 public static PropertySet getPropertySetFromBasePropertySet(BasePropertySet
229 basePropertySet) {
230 return new PropertySet(basePropertySet);
231 }
232
233
234 /**
235 * Adds the specified property to the property set.
236 *
237 * @param property The property to add.
238 * @throws Exception the exception
239 */
240 public void add(PropertyDefinitionBase property) throws Exception {
241 this.throwIfReadonly();
242 EwsUtilities.validateParam(property, "property");
243
244 if (!this.additionalProperties.contains(property)) {
245 this.additionalProperties.add(property);
246 }
247 }
248
249 /**
250 * Adds the specified property to the property set.
251 *
252 * @param properties The property to add.
253 * @throws Exception the exception
254 */
255 public void addRange(Iterable<PropertyDefinitionBase> properties)
256 throws Exception {
257 this.throwIfReadonly();
258 Iterator<PropertyDefinitionBase> property = properties.iterator();
259 EwsUtilities.validateParamCollection(property, "property");
260
261 for (Iterator<PropertyDefinitionBase> it = properties.iterator(); it
262 .hasNext(); ) {
263 this.add(it.next());
264 }
265 }
266
267 /**
268 * Remove all explicitly added property from the property set.
269 */
270 public void clear() {
271 this.throwIfReadonly();
272 this.additionalProperties.clear();
273 }
274
275 /**
276 * Creates a read-only PropertySet.
277 *
278 * @param basePropertySet The base property set.
279 * @return PropertySet
280 */
281 private static PropertySet createReadonlyPropertySet(
282 BasePropertySet basePropertySet) {
283 PropertySet propertySet = new PropertySet(basePropertySet);
284 propertySet.isReadOnly = true;
285 return propertySet;
286 }
287
288 /**
289 * Throws if readonly property set.
290 */
291 private void throwIfReadonly() {
292 if (this.isReadOnly) {
293 throw new UnsupportedOperationException("This PropertySet is read-only and can't be modified.");
294 }
295 }
296
297 /**
298 * Determines whether the specified property has been explicitly added to
299 * this property set using the Add or AddRange methods.
300 *
301 * @param property The property.
302 * @return true if this property set contains the specified property
303 * otherwise, false
304 */
305 public boolean contains(PropertyDefinitionBase property) {
306 return this.additionalProperties.contains(property);
307 }
308
309 /**
310 * Removes the specified property from the set.
311 *
312 * @param property The property to remove.
313 * @return true if the property was successfully removed, false otherwise.
314 */
315 public boolean remove(PropertyDefinitionBase property) {
316 this.throwIfReadonly();
317 return this.additionalProperties.remove(property);
318 }
319
320 /**
321 * Gets the base property set, the property set is based upon.
322 *
323 * @return the base property set
324 */
325 public BasePropertySet getBasePropertySet() {
326 return this.basePropertySet;
327 }
328
329 /**
330 * Maps BasePropertySet values to EWS's BaseShape values.
331 *
332 * @return the base property set
333 */
334 public static LazyMember<Map<BasePropertySet, String>> getDefaultPropertySetMap() {
335 return PropertySet.defaultPropertySetMap;
336
337 }
338
339 /**
340 * Sets the base property set, the property set is based upon.
341 *
342 * @param basePropertySet Base property set.
343 */
344 public void setBasePropertySet(BasePropertySet basePropertySet) {
345 this.throwIfReadonly();
346 this.basePropertySet = basePropertySet;
347 }
348
349 /**
350 * Gets type of body that should be loaded on item. If RequestedBodyType
351 * is null, body is returned as HTML if available, plain text otherwise.
352 *
353 * @return the requested body type
354 */
355 public BodyType getRequestedBodyType() {
356 return this.requestedBodyType;
357 }
358
359 /**
360 * Sets type of body that should be loaded on item. If RequestedBodyType is
361 * null, body is returned as HTML if available, plain text otherwise.
362 *
363 * @param requestedBodyType Type of body that should be loaded on item.
364 */
365 public void setRequestedBodyType(BodyType requestedBodyType) {
366 this.throwIfReadonly();
367 this.requestedBodyType = requestedBodyType;
368 }
369
370 /**
371 * Gets the number of explicitly added property in this set.
372 *
373 * @return the count
374 */
375 public int getCount() {
376 return this.additionalProperties.size();
377 }
378
379 /**
380 * Gets value indicating whether or not to filter potentially unsafe HTML
381 * content from message bodies.
382 *
383 * @return the filter html content
384 */
385 public Boolean getFilterHtmlContent() {
386 return this.filterHtml;
387 }
388
389 /**
390 * Sets value indicating whether or not to filter potentially unsafe HTML
391 * content from message bodies.
392 *
393 * @param filterHtml true to filter otherwise false.
394 */
395 public void setFilterHtmlContent(Boolean filterHtml) {
396 this.throwIfReadonly();
397 this.filterHtml = filterHtml;
398 }
399
400
401
402 /**
403 * Gets value indicating whether or not to convert
404 * HTML code page to UTF8 encoding.
405 */
406 public Boolean getConvertHtmlCodePageToUTF8() {
407 return this.convertHtmlCodePageToUTF8;
408
409 }
410
411 /**
412 * Sets value indicating whether or not to
413 * convert HTML code page to UTF8 encoding.
414 */
415 public void setConvertHtmlCodePageToUTF8(Boolean value) {
416 this.throwIfReadonly();
417 this.convertHtmlCodePageToUTF8 = value;
418
419 }
420
421
422 /**
423 * Gets the PropertyDefinitionBase at the specified index.
424 *
425 * @param index Index.
426 * @return the property definition base at
427 */
428 public PropertyDefinitionBase getPropertyDefinitionBaseAt(int index) {
429 return this.additionalProperties.get(index);
430 }
431
432
433 /**
434 * Validate.
435 *
436 * @throws ServiceValidationException the service validation exception
437 */
438 @Override
439 public void validate() throws ServiceValidationException {
440 this.internalValidate();
441 }
442
443 /**
444 * Writes additional property to XML.
445 *
446 * @param writer The writer to write to
447 * @param propertyDefinitions The property definitions to write
448 * @throws XMLStreamException the XML stream exception
449 * @throws ServiceXmlSerializationException the service xml serialization exception
450 */
451 public static void writeAdditionalPropertiesToXml(EwsServiceXmlWriter writer,
452 Iterator<PropertyDefinitionBase> propertyDefinitions)
453 throws XMLStreamException, ServiceXmlSerializationException {
454 writer.writeStartElement(XmlNamespace.Types,
455 XmlElementNames.AdditionalProperties);
456
457 while (propertyDefinitions.hasNext()) {
458 PropertyDefinitionBase propertyDefinition = propertyDefinitions
459 .next();
460 propertyDefinition.writeToXml(writer);
461 }
462
463 writer.writeEndElement();
464 }
465
466 /**
467 * Validates this property set.
468 *
469 * @throws ServiceValidationException the service validation exception
470 */
471 public void internalValidate() throws ServiceValidationException {
472 for (int i = 0; i < this.additionalProperties.size(); i++) {
473 if (this.additionalProperties.get(i) == null) {
474 throw new ServiceValidationException(String.format("The additional property at index %d is null.", i));
475 }
476 }
477 }
478
479 /**
480 * Validates this property set instance for request to ensure that: 1.
481 * Properties are valid for the request server version 2. If only summary
482 * property are legal for this request (e.g. FindItem) then only summary
483 * property were specified.
484 *
485 * @param request The request.
486 * @param summaryPropertiesOnly if set to true then only summary property are allowed.
487 * @throws ServiceVersionException the service version exception
488 * @throws ServiceValidationException the service validation exception
489 */
490 public void validateForRequest(ServiceRequestBase request, boolean summaryPropertiesOnly) throws ServiceVersionException,
491 ServiceValidationException {
492 for (PropertyDefinitionBase propDefBase : this.additionalProperties) {
493 if (propDefBase instanceof PropertyDefinition) {
494 PropertyDefinition propertyDefinition =
495 (PropertyDefinition) propDefBase;
496 if (propertyDefinition.getVersion().ordinal() > request
497 .getService().getRequestedServerVersion().ordinal()) {
498 throw new ServiceVersionException(String.format(
499 "The property %s is valid only for Exchange %s or later versions.",
500 propertyDefinition.getName(), propertyDefinition
501 .getVersion()));
502 }
503
504 if (summaryPropertiesOnly &&
505 !propertyDefinition.hasFlag(
506 PropertyDefinitionFlags.CanFind, request.
507 getService().getRequestedServerVersion())) {
508 throw new ServiceValidationException(String.format("The property %s can't be used in %s request.",
509 propertyDefinition.getName(), request
510 .getXmlElementName()));
511 }
512 }
513 }
514 if (this.getFilterHtmlContent() != null) {
515 if (request.getService().getRequestedServerVersion().compareTo(ExchangeVersion.Exchange2010) < 0) {
516 throw new ServiceVersionException(
517 String.format("The property %s is valid only for Exchange %s or later versions.",
518 "FilterHtmlContent",
519 ExchangeVersion.Exchange2010));
520 }
521 }
522
523 if (this.getConvertHtmlCodePageToUTF8() != null) {
524 if (request.getService().getRequestedServerVersion().compareTo(ExchangeVersion.Exchange2010_SP1) < 0) {
525 throw new ServiceVersionException(
526 String.format("The property %s is valid only for Exchange %s or later versions.",
527 "ConvertHtmlCodePageToUTF8",
528 ExchangeVersion.Exchange2010_SP1));
529 }
530 }
531 }
532
533 /**
534 * Writes the property set to XML.
535 *
536 * @param writer The writer to write to
537 * @param serviceObjectType The type of service object the property set is emitted for
538 * @throws XMLStreamException the XML stream exception
539 * @throws ServiceXmlSerializationException the service xml serialization exception
540 */
541 public void writeToXml(EwsServiceXmlWriter writer, ServiceObjectType serviceObjectType) throws XMLStreamException, ServiceXmlSerializationException {
542 writer
543 .writeStartElement(
544 XmlNamespace.Messages,
545 serviceObjectType == ServiceObjectType.Item ?
546 XmlElementNames.ItemShape
547 : XmlElementNames.FolderShape);
548
549 writer.writeElementValue(XmlNamespace.Types, XmlElementNames.BaseShape,
550 this.getBasePropertySet().getBaseShapeValue());
551
552 if (serviceObjectType == ServiceObjectType.Item) {
553 if (this.getRequestedBodyType() != null) {
554 writer.writeElementValue(XmlNamespace.Types,
555 XmlElementNames.BodyType, this.getRequestedBodyType());
556 }
557
558 if (this.getFilterHtmlContent() != null) {
559 writer.writeElementValue(XmlNamespace.Types,
560 XmlElementNames.FilterHtmlContent, this
561 .getFilterHtmlContent());
562 }
563 if ((this.getConvertHtmlCodePageToUTF8() != null) &&
564 writer.getService().getRequestedServerVersion().
565 compareTo(ExchangeVersion.Exchange2010_SP1) >= 0) {
566 writer.writeElementValue(
567 XmlNamespace.Types,
568 XmlElementNames.ConvertHtmlCodePageToUTF8,
569 this.getConvertHtmlCodePageToUTF8());
570 }
571 }
572
573 if (this.additionalProperties.size() > 0) {
574 writeAdditionalPropertiesToXml(writer, this.additionalProperties
575 .iterator());
576 }
577
578 writer.writeEndElement(); // Item/FolderShape
579 }
580
581 /*
582 * (non-Javadoc)
583 *
584 * @see java.lang.Iterable#iterator()
585 */
586 @Override
587 public Iterator<PropertyDefinitionBase> iterator() {
588 return this.additionalProperties.iterator();
589 }
590
591 }