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    }