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 }