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.property.complex.time; 025 026import microsoft.exchange.webservices.data.core.EwsServiceXmlReader; 027import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter; 028import microsoft.exchange.webservices.data.core.XmlAttributeNames; 029import microsoft.exchange.webservices.data.core.XmlElementNames; 030import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion; 031import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace; 032import microsoft.exchange.webservices.data.core.enumeration.property.time.DayOfTheWeek; 033import microsoft.exchange.webservices.data.core.exception.service.local.InvalidOrUnsupportedTimeZoneDefinitionException; 034import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException; 035import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException; 036import microsoft.exchange.webservices.data.property.complex.ComplexProperty; 037 038import java.util.ArrayList; 039import java.util.Collections; 040import java.util.Comparator; 041import java.util.Date; 042import java.util.HashMap; 043import java.util.Iterator; 044import java.util.List; 045import java.util.Map; 046 047/** 048 * Represents a time zone as defined by the EWS schema. 049 */ 050public 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}