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.property.complex.time;
025    
026    import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
027    import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
028    import microsoft.exchange.webservices.data.core.XmlAttributeNames;
029    import microsoft.exchange.webservices.data.core.XmlElementNames;
030    import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
031    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
032    import microsoft.exchange.webservices.data.core.enumeration.property.time.DayOfTheWeek;
033    import microsoft.exchange.webservices.data.core.exception.service.local.InvalidOrUnsupportedTimeZoneDefinitionException;
034    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
035    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
036    import microsoft.exchange.webservices.data.property.complex.ComplexProperty;
037    
038    import java.util.ArrayList;
039    import java.util.Collections;
040    import java.util.Comparator;
041    import java.util.Date;
042    import java.util.HashMap;
043    import java.util.Iterator;
044    import java.util.List;
045    import java.util.Map;
046    
047    /**
048     * Represents a time zone as defined by the EWS schema.
049     */
050    public class TimeZoneDefinition extends ComplexProperty implements Comparator<TimeZoneTransition> {
051    
052      /**
053       * Prefix for generated ids.
054       */
055      private static String NoIdPrefix = "NoId_";
056    
057      /**
058       * The Standard period id.
059       */
060      protected final String StandardPeriodId = "Std";
061    
062      /**
063       * The Standard period name.
064       */
065      protected final String StandardPeriodName = "Standard";
066    
067      /**
068       * The Daylight period id.
069       */
070      protected final String DaylightPeriodId = "Dlt";
071    
072      /**
073       * The Daylight period name.
074       */
075      protected final String DaylightPeriodName = "Daylight";
076    
077      /**
078       * The name.
079       */
080      public String name;
081    
082      /**
083       * The id.
084       */
085      public String id;
086    
087      /**
088       * The periods.
089       */
090      private Map<String, TimeZonePeriod> periods =
091          new HashMap<String, TimeZonePeriod>();
092    
093      /**
094       * The transition groups.
095       */
096      private Map<String, TimeZoneTransitionGroup> transitionGroups =
097          new HashMap<String, TimeZoneTransitionGroup>();
098    
099      /**
100       * The transitions.
101       */
102      private List<TimeZoneTransition> transitions =
103          new ArrayList<TimeZoneTransition>();
104    
105      /**
106       * Compares the transitions.
107       *
108       * @param x The first transition.
109       * @param y The second transition.
110       * @return A negative number if x is less than y, 0 if x and y are equal, a
111       * positive number if x is greater than y.
112       */
113      @Override
114      public int compare(final TimeZoneTransition x, final TimeZoneTransition y) {
115        if (x == y) {
116          return 0;
117        } else if (x != null && y != null) {
118          if (x instanceof AbsoluteDateTransition && y instanceof AbsoluteDateTransition) {
119            final AbsoluteDateTransition firstTransition = (AbsoluteDateTransition) x;
120            final AbsoluteDateTransition secondTransition = (AbsoluteDateTransition) y;
121    
122            final Date firstDateTime = firstTransition.getDateTime();
123            final Date secondDateTime = secondTransition.getDateTime();
124    
125            return firstDateTime.compareTo(secondDateTime);
126    
127          } else if (y instanceof TimeZoneTransition) {
128            return 1;
129          }
130        } else if (y == null) {
131          return 1;
132        }
133        return -1;
134      }
135    
136      /**
137       * Initializes a new instance of the TimeZoneDefinition class.
138       */
139      public TimeZoneDefinition() {
140        super();
141      }
142    
143    
144      /**
145       * Adds a transition group with a single transition to the specified period.
146       *
147       * @param timeZonePeriod the time zone period
148       * @return A TimeZoneTransitionGroup.
149       */
150      private TimeZoneTransitionGroup createTransitionGroupToPeriod(
151          TimeZonePeriod timeZonePeriod) {
152        TimeZoneTransition transitionToPeriod = new TimeZoneTransition(this,
153            timeZonePeriod);
154    
155        TimeZoneTransitionGroup transitionGroup = new TimeZoneTransitionGroup(
156            this, String.valueOf(this.transitionGroups.size()));
157        transitionGroup.getTransitions().add(transitionToPeriod);
158        this.transitionGroups.put(transitionGroup.getId(), transitionGroup);
159        return transitionGroup;
160      }
161    
162      /**
163       * Reads the attribute from XML.
164       *
165       * @param reader the reader
166       * @throws Exception the exception
167       */
168      @Override
169      public void readAttributesFromXml(EwsServiceXmlReader reader)
170          throws Exception {
171        this.name = reader.readAttributeValue(XmlAttributeNames.Name);
172        this.id = reader.readAttributeValue(XmlAttributeNames.Id);
173    
174        // E14:319057 -- EWS can return a TimeZone definition with no Id. Generate a new Id in this case.
175        if (this.id == null || this.id.isEmpty()) {
176          String nameValue = (this.getName() == null || this.
177              getName().isEmpty()) ? "" : this.getName();
178          this.setId(NoIdPrefix + Math.abs(nameValue.hashCode()));
179        }
180      }
181    
182      /**
183       * Writes the attribute to XML.
184       *
185       * @param writer the writer
186       * @throws ServiceXmlSerializationException the service xml serialization exception
187       */
188      @Override
189      public void writeAttributesToXml(EwsServiceXmlWriter writer)
190          throws ServiceXmlSerializationException {
191        // The Name attribute is only supported in Exchange 2010 and above.
192        if (writer.getService().getRequestedServerVersion() != ExchangeVersion.Exchange2007_SP1) {
193          writer.writeAttributeValue(XmlAttributeNames.Name, this.name);
194        }
195    
196        writer.writeAttributeValue(XmlAttributeNames.Id, this.id);
197      }
198    
199      /**
200       * Tries to read element from XML.
201       *
202       * @param reader the reader
203       * @return True if element was read.
204       * @throws Exception the exception
205       */
206      @Override
207      public boolean tryReadElementFromXml(EwsServiceXmlReader reader)
208          throws Exception {
209        if (reader.getLocalName().equals(XmlElementNames.Periods)) {
210          do {
211            reader.read();
212            if (reader.isStartElement(XmlNamespace.Types,
213                XmlElementNames.Period)) {
214              TimeZonePeriod period = new TimeZonePeriod();
215              period.loadFromXml(reader);
216    
217              this.periods.put(period.getId(), period);
218            }
219          } while (!reader.isEndElement(XmlNamespace.Types,
220              XmlElementNames.Periods));
221    
222          return true;
223        } else if (reader.getLocalName().equals(
224            XmlElementNames.TransitionsGroups)) {
225          do {
226            reader.read();
227            if (reader.isStartElement(XmlNamespace.Types,
228                XmlElementNames.TransitionsGroup)) {
229              TimeZoneTransitionGroup transitionGroup =
230                  new TimeZoneTransitionGroup(
231                      this);
232    
233              transitionGroup.loadFromXml(reader);
234    
235              this.transitionGroups.put(transitionGroup.getId(),
236                  transitionGroup);
237            }
238          } while (!reader.isEndElement(XmlNamespace.Types,
239              XmlElementNames.TransitionsGroups));
240    
241          return true;
242        } else if (reader.getLocalName().equals(XmlElementNames.Transitions)) {
243          do {
244            reader.read();
245            if (reader.isStartElement()) {
246              TimeZoneTransition transition = TimeZoneTransition.create(
247                  this, reader.getLocalName());
248    
249              transition.loadFromXml(reader);
250    
251              this.transitions.add(transition);
252            }
253          } while (!reader.isEndElement(XmlNamespace.Types,
254              XmlElementNames.Transitions));
255    
256          return true;
257        } else {
258          return false;
259        }
260      }
261    
262      /**
263       * Loads from XML.
264       *
265       * @param reader the reader
266       * @throws Exception the exception
267       */
268      public void loadFromXml(EwsServiceXmlReader reader) throws Exception {
269        this.loadFromXml(reader, XmlElementNames.TimeZoneDefinition);
270        Collections.sort(this.transitions, new TimeZoneDefinition());
271      }
272    
273      /**
274       * Writes elements to XML.
275       *
276       * @param writer the writer
277       * @throws Exception the exception
278       */
279      @Override
280      public void writeElementsToXml(EwsServiceXmlWriter writer)
281          throws Exception {
282        // We only emit the full time zone definition against Exchange 2010
283        // servers and above.
284        if (writer.getService().getRequestedServerVersion() != ExchangeVersion.Exchange2007_SP1) {
285          if (this.periods.size() > 0) {
286            writer.writeStartElement(XmlNamespace.Types,
287                XmlElementNames.Periods);
288    
289            Iterator<TimeZonePeriod> it = this.periods.values().iterator();
290            while (it.hasNext()) {
291              it.next().writeToXml(writer);
292            }
293    
294            writer.writeEndElement(); // Periods
295          }
296    
297          if (this.transitionGroups.size() > 0) {
298            writer.writeStartElement(XmlNamespace.Types,
299                XmlElementNames.TransitionsGroups);
300            for (int i = 0; i < this.transitionGroups.size(); i++) {
301              Object key[] = this.transitionGroups.keySet().toArray();
302              this.transitionGroups.get(key[i]).writeToXml(writer);
303            }
304            writer.writeEndElement(); // TransitionGroups
305          }
306    
307          if (this.transitions.size() > 0) {
308            writer.writeStartElement(XmlNamespace.Types,
309                XmlElementNames.Transitions);
310    
311            for (TimeZoneTransition transition : this.transitions) {
312              transition.writeToXml(writer);
313            }
314    
315            writer.writeEndElement(); // Transitions
316          }
317        }
318      }
319    
320      /**
321       * Writes to XML.
322       *
323       * @param writer The writer.
324       * @throws Exception the exception
325       */
326      protected void writeToXml(EwsServiceXmlWriter writer) throws Exception {
327        this.writeToXml(writer, XmlElementNames.TimeZoneDefinition);
328      }
329    
330      /**
331       * Validates this time zone definition.
332       *
333       * @throws InvalidOrUnsupportedTimeZoneDefinitionException thrown when time zone definition is not valid.
334       */
335      public void validate() throws ServiceLocalException {
336        // The definition must have at least one period, one transition group
337        // and one transition,
338        // and there must be as many transitions as there are transition groups.
339        if (this.periods.size() < 1 || this.transitions.size() < 1
340            || this.transitionGroups.size() < 1
341            || this.transitionGroups.size() != this.transitions.size()) {
342          throw new InvalidOrUnsupportedTimeZoneDefinitionException();
343        }
344    
345        // The first transition must be of type TimeZoneTransition.
346        if (this.transitions.get(0).getClass() != TimeZoneTransition.class) {
347          throw new InvalidOrUnsupportedTimeZoneDefinitionException();
348        }
349    
350        // All transitions must be to transition groups and be either
351        // TimeZoneTransition or
352        // AbsoluteDateTransition instances.
353        for (TimeZoneTransition transition : this.transitions) {
354          Class<?> transitionType = transition.getClass();
355    
356          if (transitionType != TimeZoneTransition.class
357              && transitionType != AbsoluteDateTransition.class) {
358            throw new InvalidOrUnsupportedTimeZoneDefinitionException();
359          }
360    
361          if (transition.getTargetGroup() == null) {
362            throw new InvalidOrUnsupportedTimeZoneDefinitionException();
363          }
364        }
365    
366        // All transition groups must be valid.
367        for (TimeZoneTransitionGroup transitionGroup : this.transitionGroups
368            .values()) {
369          transitionGroup.validate();
370        }
371      }
372    
373      /**
374       * Gets the name of this time zone definition.
375       *
376       * @return the name
377       */
378      public String getName() {
379        return this.name;
380      }
381    
382      /**
383       * Sets the name.
384       *
385       * @param name the new name
386       */
387      protected void setName(String name) {
388        this.name = name;
389      }
390    
391      /**
392       * Gets the Id of this time zone definition.
393       *
394       * @return the id
395       */
396      public String getId() {
397        return this.id;
398      }
399    
400      /**
401       * Sets the id.
402       *
403       * @param id the new id
404       */
405      public void setId(String id) {
406        this.id = id;
407      }
408    
409      /**
410       * Adds a transition group with a single transition to the specified period.
411       *
412       * @return A TimeZoneTransitionGroup.
413       */
414      public Map<String, TimeZonePeriod> getPeriods() {
415        return this.periods;
416      }
417    
418      /**
419       * Gets the transition groups associated with this time zone definition,
420       * indexed by Id.
421       *
422       * @return the transition groups
423       */
424      public Map<String, TimeZoneTransitionGroup> getTransitionGroups() {
425        return this.transitionGroups;
426      }
427    
428      /**
429       * Writes to XML.
430       *
431       * @param writer         accepts EwsServiceXmlWriter
432       * @param xmlElementName accepts String
433       * @throws Exception throws Exception
434       */
435      public void writeToXml(EwsServiceXmlWriter writer, String xmlElementName)
436          throws Exception {
437        this.writeToXml(writer, this.getNamespace(), xmlElementName);
438      }
439    
440    }