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.response;
025
026import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
027import microsoft.exchange.webservices.data.core.XmlAttributeNames;
028import microsoft.exchange.webservices.data.core.XmlElementNames;
029import microsoft.exchange.webservices.data.core.service.schema.ServiceObjectSchema;
030import microsoft.exchange.webservices.data.core.enumeration.misc.error.ServiceError;
031import microsoft.exchange.webservices.data.core.enumeration.service.ServiceResult;
032import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
033import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceResponseException;
034import microsoft.exchange.webservices.data.misc.SoapFaultDetails;
035import microsoft.exchange.webservices.data.property.definition.ExtendedPropertyDefinition;
036import microsoft.exchange.webservices.data.property.definition.IndexedPropertyDefinition;
037import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
038
039import java.util.ArrayList;
040import java.util.Collection;
041import java.util.HashMap;
042import java.util.Map;
043
044/**
045 * Represents the standard response to an Exchange Web Services operation.
046 */
047public class ServiceResponse {
048
049  /**
050   * The result.
051   */
052  private ServiceResult result;
053
054  /**
055   * The error code.
056   */
057  private ServiceError errorCode;
058
059  /**
060   * The error message.
061   */
062  private String errorMessage;
063
064  /**
065   * The error details.
066   */
067  private Map<String, String> errorDetails = new HashMap<String, String>();
068
069  /**
070   * The error property.
071   */
072  private Collection<PropertyDefinitionBase> errorProperties =
073      new ArrayList<PropertyDefinitionBase>();
074
075  /**
076   * Initializes a new instance.
077   */
078  public ServiceResponse() {
079  }
080
081  /**
082   * Initializes a new instance.
083   *
084   * @param soapFaultDetails The SOAP fault details.
085   */
086  public ServiceResponse(SoapFaultDetails soapFaultDetails) {
087    this.result = ServiceResult.Error;
088    this.errorCode = soapFaultDetails.getResponseCode();
089    this.errorMessage = soapFaultDetails.getFaultString();
090    this.errorDetails = soapFaultDetails.getErrorDetails();
091  }
092
093  /**
094   * Loads response from XML.
095   *
096   * @param reader         the reader
097   * @param xmlElementName the xml element name
098   * @throws Exception the exception
099   */
100  public void loadFromXml(EwsServiceXmlReader reader, String xmlElementName)
101      throws Exception {
102    if (!reader.isStartElement(XmlNamespace.Messages, xmlElementName)) {
103      reader.readStartElement(XmlNamespace.Messages, xmlElementName);
104    }
105
106    this.result = reader.readAttributeValue(ServiceResult.class,
107        XmlAttributeNames.ResponseClass);
108
109    if (this.result == ServiceResult.Success ||
110        this.result == ServiceResult.Warning) {
111      if (this.result == ServiceResult.Warning) {
112        this.errorMessage = reader.readElementValue(
113            XmlNamespace.Messages, XmlElementNames.MessageText);
114      }
115
116      this.errorCode = reader.readElementValue(ServiceError.class,
117          XmlNamespace.Messages, XmlElementNames.ResponseCode);
118
119      if (this.result == ServiceResult.Warning) {
120        reader.readElementValue(int.class, XmlNamespace.Messages,
121            XmlElementNames.DescriptiveLinkKey);
122      }
123
124      // Bug E14:212308 -- If batch processing stopped, EWS returns an
125      // empty element. Skip over it.
126      if (this.getBatchProcessingStopped()) {
127        do {
128          reader.read();
129        } while (!reader.isEndElement(XmlNamespace.Messages,
130            xmlElementName));
131      } else {
132
133        this.readElementsFromXml(reader);
134        //read end tag if it is an empty element.
135        if (reader.isEmptyElement()) {
136          reader.read();
137        }
138        reader.readEndElementIfNecessary(XmlNamespace.
139            Messages, xmlElementName);
140      }
141    } else {
142      this.errorMessage = reader.readElementValue(XmlNamespace.Messages,
143          XmlElementNames.MessageText);
144      this.errorCode = reader.readElementValue(ServiceError.class,
145          XmlNamespace.Messages, XmlElementNames.ResponseCode);
146      reader.readElementValue(int.class, XmlNamespace.Messages,
147          XmlElementNames.DescriptiveLinkKey);
148
149      while (!reader.isEndElement(XmlNamespace.
150          Messages, xmlElementName)) {
151        reader.read();
152
153        if (reader.isStartElement()) {
154          if (!this.loadExtraErrorDetailsFromXml(reader, reader.getLocalName())) {
155            reader.skipCurrentElement();
156          }
157
158        }
159      }
160    }
161
162    this.mapErrorCodeToErrorMessage();
163
164    this.loaded();
165  }
166
167  /**
168   * Parses the message XML.
169   *
170   * @param reader The reader.
171   * @throws Exception the exception
172   */
173  protected void parseMessageXml(EwsServiceXmlReader reader)
174      throws Exception {
175    do {
176      reader.read();
177      if (reader.isStartElement()) {
178        if (reader.getLocalName().equals(XmlElementNames.Value)) {
179          this.errorDetails.put(reader
180              .readAttributeValue(XmlAttributeNames.Name), reader
181              .readElementValue());
182        } else if (reader.getLocalName().equals(
183            XmlElementNames.FieldURI)) {
184          this.errorProperties
185              .add(ServiceObjectSchema
186                  .findPropertyDefinition(reader.readAttributeValue(XmlAttributeNames.
187                                                                        FieldURI)));
188        } else if (reader.getLocalName().equals(
189            XmlElementNames.IndexedFieldURI)) {
190          this.errorProperties
191              .add(new IndexedPropertyDefinition(
192                  reader
193                      .readAttributeValue(XmlAttributeNames.
194                          FieldURI),
195                  reader
196                      .readAttributeValue(XmlAttributeNames.
197                          FieldIndex)));
198        } else if (reader.getLocalName().equals(
199            XmlElementNames.ExtendedFieldURI)) {
200          ExtendedPropertyDefinition extendedPropDef =
201              new ExtendedPropertyDefinition();
202          extendedPropDef.loadFromXml(reader);
203          this.errorProperties.add(extendedPropDef);
204        }
205      }
206    } while (!reader.isEndElement(XmlNamespace.Messages,
207        XmlElementNames.MessageXml));
208  }
209
210
211
212  /**
213   * Called when the response has been loaded from XML.
214   */
215  protected void loaded() {
216  }
217
218  /**
219   * Called after the response has been loaded from XML in order to map error
220   * codes to "better" error messages.
221   */
222  protected void mapErrorCodeToErrorMessage() {
223    // Bug E14:69560 -- Use a better error message when an item cannot be
224    // updated because its changeKey is old.
225    if (this.getErrorCode() == ServiceError.ErrorIrresolvableConflict) {
226      this.setErrorMessage(
227          "The operation can't be performed because the item is out of date. Reload the item and try again.");
228    }
229  }
230
231  /**
232   * Reads response elements from XML.
233   *
234   * @param reader the reader
235   * @throws Exception the exception
236   */
237  protected void readElementsFromXml(EwsServiceXmlReader reader) throws Exception {
238  }
239
240  /**
241   * Loads extra error details from XML
242   *
243   * @param reader         The reader.
244   * @param xmlElementName The current element name of the extra error details.
245   * @return True if the expected extra details is loaded;
246   * False if the element name does not match the expected element.
247   */
248  protected boolean loadExtraErrorDetailsFromXml(EwsServiceXmlReader reader,
249      String xmlElementName) throws Exception {
250    if (reader.isStartElement(XmlNamespace.Messages, XmlElementNames.MessageXml) &&
251        !reader.isEmptyElement()) {
252      this.parseMessageXml(reader);
253
254      return true;
255    } else {
256      return false;
257    }
258  }
259
260  /**
261   * Throws a ServiceResponseException if this response has its Result
262   * property set to Error.
263   *
264   * @throws ServiceResponseException the service response exception
265   */
266  public void throwIfNecessary() throws ServiceResponseException {
267    this.internalThrowIfNecessary();
268  }
269
270  /**
271   * Internal method that throws a ServiceResponseException if this response
272   * has its Result property set to Error.
273   *
274   * @throws ServiceResponseException the service response exception
275   */
276  protected void internalThrowIfNecessary() throws ServiceResponseException {
277    if (this.result == ServiceResult.Error) {
278      throw new ServiceResponseException(this);
279    }
280  }
281
282  /**
283   * Gets a value indicating whether a batch request stopped processing before
284   * the end.
285   *
286   * @return A value indicating whether a batch request stopped processing
287   * before the end.
288   */
289  protected boolean getBatchProcessingStopped() {
290    return (this.result == ServiceResult.Warning)
291        && (this.errorCode == ServiceError.ErrorBatchProcessingStopped);
292  }
293
294  /**
295   * Gets the result associated with this response.
296   *
297   * @return The result associated with this response.
298   */
299  public ServiceResult getResult() {
300    return result;
301  }
302
303  /**
304   * Gets the error code associated with this response.
305   *
306   * @return The error code associated with this response.
307   */
308  public ServiceError getErrorCode() {
309    return errorCode;
310  }
311
312  /**
313   * Gets a detailed error message associated with the response. If Result
314   * is set to Success, ErrorMessage returns null. ErrorMessage is localized
315   * according to the PreferredCulture property of the ExchangeService object
316   * that was used to call the method that generated the response.
317   *
318   * @return the error message
319   */
320  public String getErrorMessage() {
321    return errorMessage;
322  }
323
324  /**
325   * Sets a detailed error message associated with the response.
326   *
327   * @param errorMessage The error message associated with the response.
328   */
329  protected void setErrorMessage(String errorMessage) {
330    this.errorMessage = errorMessage;
331  }
332
333  /**
334   * Gets error details associated with the response. If Result is set to
335   * Success, ErrorDetailsDictionary returns null. Error details will only
336   * available for some error codes. For example, when error code is
337   * ErrorRecurrenceHasNoOccurrence, the ErrorDetailsDictionary will contain
338   * keys for EffectiveStartDate and EffectiveEndDate.
339   *
340   * @return The error details dictionary.
341   */
342  public Map<String, String> getErrorDetails() {
343    return errorDetails;
344  }
345
346  /**
347   * Gets information about property errors associated with the response. If
348   * Result is set to Success, ErrorProperties returns null. ErrorProperties
349   * is only available for some error codes. For example, when the error code
350   * is ErrorInvalidPropertyForOperation, ErrorProperties will contain the
351   * definition of the property that was invalid for the request.
352   *
353   * @return the error property
354   */
355  public Collection<PropertyDefinitionBase> getErrorProperties() {
356    return this.errorProperties;
357  }
358}