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.core.service.schema;
025
026import microsoft.exchange.webservices.data.attribute.EditorBrowsable;
027import microsoft.exchange.webservices.data.core.EwsUtilities;
028import microsoft.exchange.webservices.data.core.ILazyMember;
029import microsoft.exchange.webservices.data.core.LazyMember;
030import microsoft.exchange.webservices.data.core.XmlElementNames;
031import microsoft.exchange.webservices.data.core.enumeration.attribute.EditorBrowsableState;
032import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
033import microsoft.exchange.webservices.data.core.enumeration.property.PropertyDefinitionFlags;
034import microsoft.exchange.webservices.data.misc.OutParam;
035import microsoft.exchange.webservices.data.property.complex.ExtendedPropertyCollection;
036import microsoft.exchange.webservices.data.property.complex.ICreateComplexPropertyDelegate;
037import microsoft.exchange.webservices.data.property.definition.ComplexPropertyDefinition;
038import microsoft.exchange.webservices.data.property.definition.IndexedPropertyDefinition;
039import microsoft.exchange.webservices.data.property.definition.PropertyDefinition;
040import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
041import org.apache.commons.logging.Log;
042import org.apache.commons.logging.LogFactory;
043
044import java.lang.reflect.Field;
045import java.lang.reflect.Modifier;
046import java.util.ArrayList;
047import java.util.EnumSet;
048import java.util.HashMap;
049import java.util.Iterator;
050import java.util.List;
051import java.util.Map;
052
053/**
054 * Represents the base class for all item and folder schema.
055 */
056@EditorBrowsable(state = EditorBrowsableState.Never)
057public 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}