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.core.response;
025    
026    import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
027    import microsoft.exchange.webservices.data.core.XmlAttributeNames;
028    import microsoft.exchange.webservices.data.core.XmlElementNames;
029    import microsoft.exchange.webservices.data.core.service.schema.ServiceObjectSchema;
030    import microsoft.exchange.webservices.data.core.enumeration.misc.error.ServiceError;
031    import microsoft.exchange.webservices.data.core.enumeration.service.ServiceResult;
032    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
033    import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceResponseException;
034    import microsoft.exchange.webservices.data.misc.SoapFaultDetails;
035    import microsoft.exchange.webservices.data.property.definition.ExtendedPropertyDefinition;
036    import microsoft.exchange.webservices.data.property.definition.IndexedPropertyDefinition;
037    import microsoft.exchange.webservices.data.property.definition.PropertyDefinitionBase;
038    
039    import java.util.ArrayList;
040    import java.util.Collection;
041    import java.util.HashMap;
042    import java.util.Map;
043    
044    /**
045     * Represents the standard response to an Exchange Web Services operation.
046     */
047    public 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    }