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.service.schema;
025    
026    import microsoft.exchange.webservices.data.attribute.EditorBrowsable;
027    import microsoft.exchange.webservices.data.core.EwsUtilities;
028    import microsoft.exchange.webservices.data.core.ILazyMember;
029    import microsoft.exchange.webservices.data.core.LazyMember;
030    import microsoft.exchange.webservices.data.core.XmlElementNames;
031    import microsoft.exchange.webservices.data.core.enumeration.attribute.EditorBrowsableState;
032    import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
033    import microsoft.exchange.webservices.data.core.enumeration.property.PropertyDefinitionFlags;
034    import microsoft.exchange.webservices.data.misc.OutParam;
035    import microsoft.exchange.webservices.data.property.complex.ExtendedPropertyCollection;
036    import microsoft.exchange.webservices.data.property.complex.ICreateComplexPropertyDelegate;
037    import microsoft.exchange.webservices.data.property.definition.ComplexPropertyDefinition;
038    import microsoft.exchange.webservices.data.property.definition.IndexedPropertyDefinition;
039    import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
040    import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
041    import org.apache.commons.logging.Log;
042    import org.apache.commons.logging.LogFactory;
043    
044    import java.lang.reflect.Field;
045    import java.lang.reflect.Modifier;
046    import java.util.ArrayList;
047    import java.util.EnumSet;
048    import java.util.HashMap;
049    import java.util.Iterator;
050    import java.util.List;
051    import java.util.Map;
052    
053    /**
054     * Represents the base class for all item and folder schema.
055     */
056    @EditorBrowsable(state = EditorBrowsableState.Never)
057    public abstract class ServiceObjectSchema implements
058        Iterable<PropertyDefinition> {
059    
060      private static final Log LOG = LogFactory.getLog(ServiceObjectSchema.class);
061    
062      /**
063       * The lock object.
064       */
065      private static final Object lockObject = new Object();
066    
067      /**
068       * List of all schema types. If you add a new ServiceObject subclass that
069       * has an associated schema, add the schema type to the list below.
070       */
071      private static LazyMember<List<Class<?>>> allSchemaTypes = new
072          LazyMember<List<Class<?>>>(new
073                                         ILazyMember<List<Class<?>>>() {
074                                           public List<Class<?>> createInstance() {
075                                             List<Class<?>> typeList = new ArrayList<Class<?>>();
076                                             // typeList.add()
077                                            /*
078                                             * typeList.add(AppointmentSchema.class);
079                                             * typeList.add(CalendarResponseObjectSchema.class);
080                                             * typeList.add(CancelMeetingMessageSchema.class);
081                                             * typeList.add(ContactGroupSchema.class);
082                                             * typeList.add(ContactSchema.class);
083                                             * typeList.add(EmailMessageSchema.class);
084                                             * typeList.add(FolderSchema.class);
085                                             * typeList.add(ItemSchema.class);
086                                             * typeList.add(MeetingMessageSchema.class);
087                                             * typeList.add(MeetingRequestSchema.class);
088                                             * typeList.add(PostItemSchema.class);
089                                             * typeList.add(PostReplySchema.class);
090                                             * typeList.add(ResponseMessageSchema.class);
091                                             * typeList.add(ResponseObjectSchema.class);
092                                             * typeList.add(ServiceObjectSchema.class);
093                                             * typeList.add(SearchFolderSchema.class);
094                                             * typeList.add(TaskSchema.class);
095                                             */
096                                             // Verify that all Schema types in the Managed API assembly
097                                             // have been included.
098                                            /*
099                                             * var missingTypes = from type in
100                                             * Assembly.GetExecutingAssembly().GetTypes() where
101                                             * type.IsSubclassOf(typeof(ServiceObjectSchema)) &&
102                                             * !typeList.Contains(type) select type; if
103                                             * (missingTypes.Count() > 0) { throw new
104                                             * ServiceLocalException
105                                             * ("SchemaTypeList does not include all 
106                                             * defined schema types."
107                                             * ); }
108                                             */
109                                             return typeList;
110                                           }
111                                         });
112    
113      /**
114       * Dictionary of all property definitions.
115       */
116      private static LazyMember<Map<String, PropertyDefinitionBase>>
117          allSchemaProperties = new
118          LazyMember<Map<String, PropertyDefinitionBase>>(
119          new ILazyMember<Map<String, PropertyDefinitionBase>>() {
120            public Map<String, PropertyDefinitionBase> createInstance() {
121              Map<String, PropertyDefinitionBase> propDefDictionary =
122                  new HashMap<String, PropertyDefinitionBase>();
123              for (Class<?> c : ServiceObjectSchema.allSchemaTypes
124                  .getMember()) {
125                ServiceObjectSchema.addSchemaPropertiesToDictionary(c,
126                    propDefDictionary);
127              }
128              return propDefDictionary;
129            }
130          });
131    
132      /**
133       * Adds schema property to dictionary.
134       *
135       * @param type              Schema type.
136       * @param propDefDictionary The property definition dictionary.
137       */
138      protected static void addSchemaPropertiesToDictionary(Class<?> type,
139          Map<String, PropertyDefinitionBase> propDefDictionary) {
140        Field[] fields = type.getDeclaredFields();
141        for (Field field : fields) {
142          int modifier = field.getModifiers();
143          if (Modifier.isPublic(modifier) && Modifier.isStatic(modifier)) {
144            Object o;
145            try {
146              o = field.get(null);
147              if (o instanceof PropertyDefinition) {
148                PropertyDefinition propertyDefinition =
149                    (PropertyDefinition) o;
150                // Some property definitions descend from
151                // ServiceObjectPropertyDefinition but don't have
152                // a Uri, like ExtendedProperties. Ignore them.
153                if (null != propertyDefinition.getUri() &&
154                    !propertyDefinition.getUri().isEmpty()) {
155                  PropertyDefinitionBase existingPropertyDefinition;
156                  if (propDefDictionary
157                      .containsKey(propertyDefinition.getUri())) {
158                    existingPropertyDefinition = propDefDictionary
159                        .get(propertyDefinition.getUri());
160                    EwsUtilities
161                        .ewsAssert(existingPropertyDefinition == propertyDefinition,
162                                   "Schema.allSchemaProperties." + "delegate",
163                                   String.format("There are at least " +
164                                                 "two distinct property " +
165                                                 "definitions with the" +
166                                                 " following URI: %s", propertyDefinition.getUri()));
167                  } else {
168                    propDefDictionary.put(propertyDefinition
169                        .getUri(), propertyDefinition);
170                    // The following is a "generic hack" to register
171                    // property that are not public and
172                    // thus not returned by the above GetFields
173                    // call. It is currently solely used to register
174                    // the MeetingTimeZone property.
175                    List<PropertyDefinition> associatedInternalProperties =
176                        propertyDefinition.getAssociatedInternalProperties();
177                    for (PropertyDefinition associatedInternalProperty : associatedInternalProperties) {
178                      propDefDictionary
179                          .put(associatedInternalProperty
180                                  .getUri(),
181                              associatedInternalProperty);
182                    }
183    
184                  }
185                }
186              }
187            } catch (IllegalArgumentException e) {
188              LOG.error(e);
189    
190              // Skip the field
191            } catch (IllegalAccessException e) {
192              LOG.error(e);
193    
194              // Skip the field
195            }
196    
197          }
198        }
199      }
200    
201      /**
202       * Adds the schema property names to dictionary.
203       *
204       * @param type                   The type.
205       * @param propertyNameDictionary The property name dictionary.
206       */
207      protected static void addSchemaPropertyNamesToDictionary(Class<?> type,
208          Map<PropertyDefinition, String> propertyNameDictionary) {
209    
210        Field[] fields = type.getDeclaredFields();
211        for (Field field : fields) {
212          int modifier = field.getModifiers();
213          if (Modifier.isPublic(modifier) && Modifier.isStatic(modifier)) {
214            Object o;
215            try {
216              o = field.get(null);
217              if (o instanceof PropertyDefinition) {
218                PropertyDefinition propertyDefinition =
219                    (PropertyDefinition) o;
220                propertyNameDictionary.put(propertyDefinition, field
221                    .getName());
222              }
223            } catch (IllegalArgumentException e) {
224              LOG.error(e);
225    
226              // Skip the field
227            } catch (IllegalAccessException e) {
228              LOG.error(e);
229    
230              // Skip the field
231            }
232          }
233        }
234      }
235    
236      /**
237       * Initializes a new instance.
238       */
239      protected ServiceObjectSchema() {
240        this.registerProperties();
241      }
242    
243      /**
244       * Finds the property definition.
245       *
246       * @param uri The URI.
247       * @return Property definition.
248       */
249      public static PropertyDefinitionBase findPropertyDefinition(String uri) {
250        return ServiceObjectSchema.allSchemaProperties.getMember().get(uri);
251      }
252    
253      /**
254       * Initialize schema property names.
255       */
256      public static void initializeSchemaPropertyNames() {
257        synchronized (lockObject) {
258          for (Class<?> type : ServiceObjectSchema.allSchemaTypes.getMember()) {
259            Field[] fields = type.getDeclaredFields();
260            for (Field field : fields) {
261              int modifier = field.getModifiers();
262              if (Modifier.isPublic(modifier) &&
263                  Modifier.isStatic(modifier)) {
264                Object o;
265                try {
266                  o = field.get(null);
267                  if (o instanceof PropertyDefinition) {
268                    PropertyDefinition propertyDefinition =
269                        (PropertyDefinition) o;
270                    propertyDefinition.setName(field.getName());
271                  }
272                } catch (IllegalArgumentException e) {
273                  LOG.error(e);
274    
275                  // Skip the field
276                } catch (IllegalAccessException e) {
277                  LOG.error(e);
278    
279                  // Skip the field
280                }
281              }
282            }
283          }
284        }
285      }
286    
287      /**
288       * Defines the ExtendedProperties property.
289       */
290      public static final PropertyDefinition extendedProperties =
291          new ComplexPropertyDefinition<ExtendedPropertyCollection>(
292              ExtendedPropertyCollection.class,
293              XmlElementNames.ExtendedProperty,
294              EnumSet.of(PropertyDefinitionFlags.AutoInstantiateOnRead,
295                  PropertyDefinitionFlags.ReuseInstance,
296                  PropertyDefinitionFlags.CanSet,
297                  PropertyDefinitionFlags.CanUpdate),
298              ExchangeVersion.Exchange2007_SP1,
299              new ICreateComplexPropertyDelegate<ExtendedPropertyCollection>() {
300                public ExtendedPropertyCollection createComplexProperty() {
301                  return new ExtendedPropertyCollection();
302                }
303              });
304    
305      /**
306       * The property.
307       */
308      private Map<String, PropertyDefinition> properties =
309          new HashMap<String, PropertyDefinition>();
310    
311      /**
312       * The visible property.
313       */
314      private List<PropertyDefinition> visibleProperties =
315          new ArrayList<PropertyDefinition>();
316    
317      /**
318       * The first class property.
319       */
320      private List<PropertyDefinition> firstClassProperties =
321          new ArrayList<PropertyDefinition>();
322    
323      /**
324       * The first class summary property.
325       */
326      private List<PropertyDefinition> firstClassSummaryProperties =
327          new ArrayList<PropertyDefinition>();
328    
329      private List<IndexedPropertyDefinition> indexedProperties =
330          new ArrayList<IndexedPropertyDefinition>();
331    
332      /**
333       * Registers a schema property.
334       *
335       * @param property   The property to register.
336       * @param isInternal Indicates whether the property is internal or should be
337       *                   visible to developers.
338       */
339      private void registerProperty(PropertyDefinition property,
340          boolean isInternal) {
341        this.properties.put(property.getXmlElement(), property);
342    
343        if (!isInternal) {
344          this.visibleProperties.add(property);
345        }
346    
347        // If this property does not have to be requested explicitly, add
348        // it to the list of firstClassProperties.
349        if (!property.hasFlag(PropertyDefinitionFlags.MustBeExplicitlyLoaded)) {
350          this.firstClassProperties.add(property);
351        }
352    
353        // If this property can be found, add it to the list of
354        // firstClassSummaryProperties
355        if (property.hasFlag(PropertyDefinitionFlags.CanFind)) {
356          this.firstClassSummaryProperties.add(property);
357        }
358      }
359    
360      /**
361       * Registers a schema property that will be visible to developers.
362       *
363       * @param property The property to register.
364       */
365      protected void registerProperty(PropertyDefinition property) {
366        this.registerProperty(property, false);
367      }
368    
369      /**
370       * Registers an internal schema property.
371       *
372       * @param property The property to register.
373       */
374      protected void registerInternalProperty(PropertyDefinition property) {
375        this.registerProperty(property, true);
376      }
377    
378      /**
379       * Registers an indexed property.
380       *
381       * @param indexedProperty The indexed property to register.
382       */
383      protected void registerIndexedProperty(IndexedPropertyDefinition
384          indexedProperty) {
385        this.indexedProperties.add(indexedProperty);
386      }
387    
388    
389      /**
390       * Registers property.
391       */
392      protected void registerProperties() {
393      }
394    
395      /**
396       * Gets the list of first class property for this service object type.
397       *
398       * @return the first class property
399       */
400      public List<PropertyDefinition> getFirstClassProperties() {
401        return this.firstClassProperties;
402      }
403    
404      /**
405       * Gets the list of first class summary property for this service object
406       * type.
407       *
408       * @return the first class summary property
409       */
410      public List<PropertyDefinition> getFirstClassSummaryProperties() {
411        return this.firstClassSummaryProperties;
412      }
413    
414      /**
415       * Tries to get property definition.
416       *
417       * @param xmlElementName             Name of the XML element.
418       * @param propertyDefinitionOutParam The property definition.
419       * @return True if property definition exists.
420       */
421      public boolean tryGetPropertyDefinition(String xmlElementName,
422          OutParam<PropertyDefinition> propertyDefinitionOutParam) {
423        if (this.properties.containsKey(xmlElementName)) {
424          propertyDefinitionOutParam.setParam(this.properties
425              .get(xmlElementName));
426          return true;
427        } else {
428          return false;
429        }
430      }
431    
432      /**
433       * Returns an iterator over a set of elements of type T.
434       *
435       * @return an Iterator.
436       */
437      @Override
438      public Iterator<PropertyDefinition> iterator() {
439        return this.visibleProperties.iterator();
440      }
441    }