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.request;
025    
026    import microsoft.exchange.webservices.data.core.EwsServiceXmlReader;
027    import microsoft.exchange.webservices.data.core.EwsServiceXmlWriter;
028    import microsoft.exchange.webservices.data.core.EwsUtilities;
029    import microsoft.exchange.webservices.data.core.ExchangeServerInfo;
030    import microsoft.exchange.webservices.data.core.ExchangeService;
031    import microsoft.exchange.webservices.data.core.XmlAttributeNames;
032    import microsoft.exchange.webservices.data.core.XmlElementNames;
033    import microsoft.exchange.webservices.data.core.response.ServiceResponse;
034    import microsoft.exchange.webservices.data.core.enumeration.misc.DateTimePrecision;
035    import microsoft.exchange.webservices.data.core.enumeration.misc.ExchangeVersion;
036    import microsoft.exchange.webservices.data.core.enumeration.misc.TraceFlags;
037    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
038    import microsoft.exchange.webservices.data.core.exception.http.EWSHttpException;
039    import microsoft.exchange.webservices.data.core.exception.http.HttpErrorException;
040    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceLocalException;
041    import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceRequestException;
042    import microsoft.exchange.webservices.data.core.exception.service.remote.ServiceResponseException;
043    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceVersionException;
044    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
045    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlSerializationException;
046    import microsoft.exchange.webservices.data.core.exception.xml.XmlException;
047    import microsoft.exchange.webservices.data.misc.SoapFaultDetails;
048    import microsoft.exchange.webservices.data.security.XmlNodeType;
049    import org.apache.commons.logging.Log;
050    import org.apache.commons.logging.LogFactory;
051    
052    import javax.xml.stream.XMLStreamException;
053    import javax.xml.ws.http.HTTPException;
054    
055    import java.io.ByteArrayInputStream;
056    import java.io.ByteArrayOutputStream;
057    import java.io.IOException;
058    import java.io.InputStream;
059    import java.util.zip.GZIPInputStream;
060    import java.util.zip.InflaterInputStream;
061    
062    /**
063     * Represents an abstract service request.
064     */
065    public abstract class ServiceRequestBase<T> {
066    
067      private static final Log LOG = LogFactory.getLog(ServiceRequestBase.class);
068    
069      /**
070       * The service.
071       */
072      private ExchangeService service;
073    
074      // Methods for subclasses to override
075    
076      /**
077       * Gets the name of the XML element.
078       *
079       * @return XML element name
080       */
081      public abstract String getXmlElementName();
082    
083      /**
084       * Gets the name of the response XML element.
085       *
086       * @return XML element name
087       */
088      protected abstract String getResponseXmlElementName();
089    
090      /**
091       * Gets the minimum server version required to process this request.
092       *
093       * @return Exchange server version.
094       */
095      protected abstract ExchangeVersion getMinimumRequiredServerVersion();
096    
097      /**
098       * Parses the response.
099       *
100       * @param reader The reader.
101       * @return the Response Object.
102       * @throws Exception the exception
103       */
104      protected abstract T parseResponse(EwsServiceXmlReader reader) throws Exception;
105    
106      /**
107       * Writes XML elements.
108       *
109       * @param writer The writer.
110       * @throws Exception the exception
111       */
112      protected abstract void writeElementsToXml(EwsServiceXmlWriter writer) throws Exception;
113    
114      /**
115       * Validate request.
116       *
117       * @throws ServiceLocalException the service local exception
118       * @throws Exception             the exception
119       */
120      protected void validate() throws Exception {
121        this.service.validate();
122      }
123    
124      /**
125       * Writes XML body.
126       *
127       * @param writer The writer.
128       * @throws Exception the exception
129       */
130      protected void writeBodyToXml(EwsServiceXmlWriter writer) throws Exception {
131        writer.writeStartElement(XmlNamespace.Messages, this.getXmlElementName());
132    
133        this.writeAttributesToXml(writer);
134        this.writeElementsToXml(writer);
135    
136        writer.writeEndElement(); // m:this.GetXmlElementName()
137      }
138    
139      /**
140       * Writes XML attribute. Subclass will override if it has XML attribute.
141       *
142       * @param writer The writer.
143       * @throws ServiceXmlSerializationException the service xml serialization exception
144       */
145      protected void writeAttributesToXml(EwsServiceXmlWriter writer) throws ServiceXmlSerializationException {
146      }
147    
148      /**
149       * Initializes a new instance.
150       *
151       * @param service The service.
152       * @throws ServiceVersionException the service version exception
153       */
154      protected ServiceRequestBase(ExchangeService service) throws ServiceVersionException {
155        this.service = service;
156        this.throwIfNotSupportedByRequestedServerVersion();
157      }
158    
159      /**
160       * Gets the service.
161       *
162       * @return The service.
163       */
164      public ExchangeService getService() {
165        return service;
166      }
167    
168      /**
169       * Throw exception if request is not supported in requested server version.
170       *
171       * @throws ServiceVersionException the service version exception
172       */
173      protected void throwIfNotSupportedByRequestedServerVersion() throws ServiceVersionException {
174        if (this.service.getRequestedServerVersion().ordinal() < this.getMinimumRequiredServerVersion()
175            .ordinal()) {
176          throw new ServiceVersionException(String.format(
177              "The service request %s is only valid for Exchange version %s or later.", this.getXmlElementName(),
178              this.getMinimumRequiredServerVersion()));
179        }
180      }
181    
182      // HttpWebRequest-based implementation
183    
184      /**
185       * Writes XML.
186       *
187       * @param writer The writer.
188       * @throws Exception the exception
189       */
190      protected void writeToXml(EwsServiceXmlWriter writer) throws Exception {
191        writer.writeStartDocument();
192        writer.writeStartElement(XmlNamespace.Soap, XmlElementNames.SOAPEnvelopeElementName);
193        writer.writeAttributeValue("xmlns", EwsUtilities.getNamespacePrefix(XmlNamespace.Soap),
194                                   EwsUtilities.getNamespaceUri(XmlNamespace.Soap));
195        writer.writeAttributeValue("xmlns", EwsUtilities.EwsXmlSchemaInstanceNamespacePrefix,
196                                   EwsUtilities.EwsXmlSchemaInstanceNamespace);
197        writer.writeAttributeValue("xmlns", EwsUtilities.EwsMessagesNamespacePrefix,
198                                   EwsUtilities.EwsMessagesNamespace);
199        writer.writeAttributeValue("xmlns", EwsUtilities.EwsTypesNamespacePrefix, EwsUtilities.EwsTypesNamespace);
200        if (writer.isRequireWSSecurityUtilityNamespace()) {
201          writer.writeAttributeValue("xmlns", EwsUtilities.WSSecurityUtilityNamespacePrefix,
202                                     EwsUtilities.WSSecurityUtilityNamespace);
203        }
204    
205        writer.writeStartElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName);
206    
207        if (this.service.getCredentials() != null) {
208          this.service.getCredentials().emitExtraSoapHeaderNamespaceAliases(writer.getInternalWriter());
209        }
210    
211        // Emit the RequestServerVersion header
212        writer.writeStartElement(XmlNamespace.Types, XmlElementNames.RequestServerVersion);
213        writer.writeAttributeValue(XmlAttributeNames.Version, this.getRequestedServiceVersionString());
214        writer.writeEndElement(); // RequestServerVersion
215    
216                    /*
217                     * if ((this.getService().getRequestedServerVersion().ordinal() ==
218                     * ExchangeVersion.Exchange2007_SP1.ordinal() ||
219                     * this.EmitTimeZoneHeader()) &&
220                     * (!this.getService().getExchange2007CompatibilityMode())) {
221                     * writer.writeStartElement(XmlNamespace.Types,
222                     * XmlElementNames.TimeZoneContext);
223                     * 
224                     * this.getService().TimeZoneDefinition().WriteToXml(writer);
225                     * 
226                     * writer.WriteEndElement(); // TimeZoneContext
227                     * 
228                     * writer.IsTimeZoneHeaderEmitted = true; }
229                     */
230    
231        if (this.service.getPreferredCulture() != null) {
232          writer.writeElementValue(XmlNamespace.Types, XmlElementNames.MailboxCulture,
233                                   this.service.getPreferredCulture().getDisplayName());
234        }
235    
236        /** Emit the DateTimePrecision header */
237    
238        if (this.getService().getDateTimePrecision().ordinal() != DateTimePrecision.Default.ordinal()) {
239          writer.writeElementValue(XmlNamespace.Types, XmlElementNames.DateTimePrecision,
240                                   this.getService().getDateTimePrecision().toString());
241        }
242        if (this.service.getImpersonatedUserId() != null) {
243          this.service.getImpersonatedUserId().writeToXml(writer);
244        }
245    
246        if (this.service.getCredentials() != null) {
247          this.service.getCredentials()
248              .serializeExtraSoapHeaders(writer.getInternalWriter(), this.getXmlElementName());
249        }
250        this.service.doOnSerializeCustomSoapHeaders(writer.getInternalWriter());
251    
252        writer.writeEndElement(); // soap:Header
253    
254        writer.writeStartElement(XmlNamespace.Soap, XmlElementNames.SOAPBodyElementName);
255    
256        this.writeBodyToXml(writer);
257    
258        writer.writeEndElement(); // soap:Body
259        writer.writeEndElement(); // soap:Envelope
260        writer.flush();
261      }
262    
263      /**
264       * Gets st ring representation of requested server version. In order to support E12 RTM servers,
265       * ExchangeService has another flag indicating that we should use "Exchange2007" as the server version
266       * string rather than Exchange2007_SP1.
267       *
268       * @return String representation of requested server version.
269       */
270      private String getRequestedServiceVersionString() {
271        if (this.service.getRequestedServerVersion() == ExchangeVersion.Exchange2007_SP1 && this.service
272            .getExchange2007CompatibilityMode()) {
273          return "Exchange2007";
274        } else {
275          return this.service.getRequestedServerVersion().toString();
276        }
277      }
278    
279      /**
280       * Gets the response stream (may be wrapped with GZip/Deflate stream to decompress content).
281       *
282       * @param request HttpWebRequest object from which response stream can be read.
283       * @return ResponseStream
284       * @throws java.io.IOException Signals that an I/O exception has occurred.
285       * @throws EWSHttpException    the EWS http exception
286       */
287      protected static InputStream getResponseStream(HttpWebRequest request)
288          throws IOException, EWSHttpException {
289        String contentEncoding = "";
290    
291        if (null != request.getContentEncoding()) {
292          contentEncoding = request.getContentEncoding().toLowerCase();
293        }
294    
295        InputStream responseStream;
296    
297        if (contentEncoding.contains("gzip")) {
298          responseStream = new GZIPInputStream(request.getInputStream());
299        } else if (contentEncoding.contains("deflate")) {
300          responseStream = new InflaterInputStream(request.getInputStream());
301        } else {
302          responseStream = request.getInputStream();
303        }
304        return responseStream;
305      }
306    
307      /**
308       * Traces the response.
309       *
310       * @param request      the response
311       * @param memoryStream the response content in a MemoryStream
312       * @throws XMLStreamException the XML stream exception
313       * @throws IOException        signals that an I/O exception has occurred
314       * @throws EWSHttpException   the EWS http exception
315       */
316      protected void traceResponse(HttpWebRequest request, ByteArrayOutputStream memoryStream)
317          throws XMLStreamException, IOException, EWSHttpException {
318    
319        this.service.processHttpResponseHeaders(TraceFlags.EwsResponseHttpHeaders, request);
320        String contentType = request.getResponseContentType();
321    
322        if (!isNullOrEmpty(contentType) && (contentType.startsWith("text/") || contentType
323            .startsWith("application/soap"))) {
324          this.service.traceXml(TraceFlags.EwsResponse, memoryStream);
325        } else {
326          this.service.traceMessage(TraceFlags.EwsResponse, "Non-textual response");
327        }
328    
329      }
330    
331      /**
332       * Gets the response error stream.
333       *
334       * @param request the request
335       * @return the response error stream
336       * @throws EWSHttpException    the EWS http exception
337       * @throws java.io.IOException Signals that an I/O exception has occurred.
338       */
339      private static InputStream getResponseErrorStream(HttpWebRequest request)
340          throws EWSHttpException, IOException {
341        String contentEncoding = "";
342    
343        if (null != request.getContentEncoding()) {
344          contentEncoding = request.getContentEncoding().toLowerCase();
345        }
346    
347        InputStream responseStream;
348    
349        if (contentEncoding.contains("gzip")) {
350          responseStream = new GZIPInputStream(request.getErrorStream());
351        } else if (contentEncoding.contains("deflate")) {
352          responseStream = new InflaterInputStream(request.getErrorStream());
353        } else {
354          responseStream = request.getErrorStream();
355        }
356        return responseStream;
357      }
358    
359      /**
360       * Reads the response.
361       *
362       * @param response HTTP web request
363       * @return response response object
364       * @throws Exception on error
365       */
366      protected T readResponse(HttpWebRequest response) throws Exception {
367        T serviceResponse;
368    
369        if (!response.getResponseContentType().startsWith("text/xml")) {
370          throw new ServiceRequestException("The response received from the service didn't contain valid XML.");
371        }
372    
373        /**
374         * If tracing is enabled, we read the entire response into a
375         * MemoryStream so that we can pass it along to the ITraceListener. Then
376         * we parse the response from the MemoryStream.
377         */
378    
379        try {
380          this.getService().processHttpResponseHeaders(TraceFlags.EwsResponseHttpHeaders, response);
381    
382          if (this.getService().isTraceEnabledFor(TraceFlags.EwsResponse)) {
383            ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();
384            InputStream serviceResponseStream = ServiceRequestBase.getResponseStream(response);
385    
386            int data = serviceResponseStream.read();
387            while (data != -1) {
388              memoryStream.write(data);
389              data = serviceResponseStream.read();
390            }
391    
392            this.traceResponse(response, memoryStream);
393            ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream(memoryStream.toByteArray());
394            EwsServiceXmlReader ewsXmlReader = new EwsServiceXmlReader(memoryStreamIn, this.getService());
395            serviceResponse = this.readResponse(ewsXmlReader);
396            serviceResponseStream.close();
397            memoryStream.flush();
398          } else {
399            InputStream responseStream = ServiceRequestBase.getResponseStream(response);
400            EwsServiceXmlReader ewsXmlReader = new EwsServiceXmlReader(responseStream, this.getService());
401            serviceResponse = this.readResponse(ewsXmlReader);
402          }
403    
404          return serviceResponse;
405        } catch (HTTPException e) {
406          if (e.getMessage() != null) {
407            this.getService().processHttpResponseHeaders(TraceFlags.EwsResponseHttpHeaders, response);
408          }
409          throw new ServiceRequestException(String.format("The request failed. %s", e.getMessage()), e);
410        } catch (IOException e) {
411          throw new ServiceRequestException(String.format("The request failed. %s", e.getMessage()), e);
412        } finally { // close the underlying response
413          response.close();
414        }
415      }
416    
417      /**
418       * Reads the response.
419       *
420       * @param ewsXmlReader The XML reader.
421       * @return Service response.
422       * @throws Exception the exception
423       */
424      protected T readResponse(EwsServiceXmlReader ewsXmlReader) throws Exception {
425        T serviceResponse;
426        this.readPreamble(ewsXmlReader);
427        ewsXmlReader.readStartElement(XmlNamespace.Soap, XmlElementNames.SOAPEnvelopeElementName);
428        this.readSoapHeader(ewsXmlReader);
429        ewsXmlReader.readStartElement(XmlNamespace.Soap, XmlElementNames.SOAPBodyElementName);
430    
431        ewsXmlReader.readStartElement(XmlNamespace.Messages, this.getResponseXmlElementName());
432    
433        serviceResponse = this.parseResponse(ewsXmlReader);
434    
435        ewsXmlReader.readEndElementIfNecessary(XmlNamespace.Messages, this.getResponseXmlElementName());
436    
437        ewsXmlReader.readEndElement(XmlNamespace.Soap, XmlElementNames.SOAPBodyElementName);
438        ewsXmlReader.readEndElement(XmlNamespace.Soap, XmlElementNames.SOAPEnvelopeElementName);
439        return serviceResponse;
440      }
441    
442      /**
443       * Reads any preamble data not part of the core response.
444       *
445       * @param ewsXmlReader The EwsServiceXmlReader.
446       * @throws Exception on error
447       */
448      protected void readPreamble(EwsServiceXmlReader ewsXmlReader) throws Exception {
449        this.readXmlDeclaration(ewsXmlReader);
450      }
451    
452      /**
453       * Read SOAP header and extract server version.
454       *
455       * @param reader EwsServiceXmlReader
456       * @throws Exception the exception
457       */
458      private void readSoapHeader(EwsServiceXmlReader reader) throws Exception {
459        reader.readStartElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName);
460        do {
461          reader.read();
462    
463          // Is this the ServerVersionInfo?
464          if (reader.isStartElement(XmlNamespace.Types, XmlElementNames.ServerVersionInfo)) {
465            this.service.setServerInfo(ExchangeServerInfo.parse(reader));
466          }
467    
468          // Ignore anything else inside the SOAP header
469        } while (!reader.isEndElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName));
470      }
471    
472      /**
473       * Processes the web exception.
474       *
475       * @param webException the web exception
476       * @param req          HTTP Request object used to send the http request
477       * @throws Exception on error
478       */
479      protected void processWebException(Exception webException, HttpWebRequest req) throws Exception {
480        SoapFaultDetails soapFaultDetails;
481        if (null != req) {
482          this.getService().processHttpResponseHeaders(TraceFlags.EwsResponseHttpHeaders, req);
483          if (500 == req.getResponseCode()) {
484            if (this.service.isTraceEnabledFor(TraceFlags.EwsResponse)) {
485              ByteArrayOutputStream memoryStream = new ByteArrayOutputStream();
486              InputStream serviceResponseStream = ServiceRequestBase.getResponseErrorStream(req);
487              while (true) {
488                int data = serviceResponseStream.read();
489                if (-1 == data) {
490                  break;
491                } else {
492                  memoryStream.write(data);
493                }
494              }
495              memoryStream.flush();
496              serviceResponseStream.close();
497              this.traceResponse(req, memoryStream);
498              ByteArrayInputStream memoryStreamIn = new ByteArrayInputStream(memoryStream.toByteArray());
499              EwsServiceXmlReader reader = new EwsServiceXmlReader(memoryStreamIn, this.service);
500              soapFaultDetails = this.readSoapFault(reader);
501              memoryStream.close();
502            } else {
503              InputStream serviceResponseStream = ServiceRequestBase.getResponseStream(req);
504              EwsServiceXmlReader reader = new EwsServiceXmlReader(serviceResponseStream, this.service);
505              soapFaultDetails = this.readSoapFault(reader);
506              serviceResponseStream.close();
507    
508            }
509    
510            if (soapFaultDetails != null) {
511              switch (soapFaultDetails.getResponseCode()) {
512                case ErrorInvalidServerVersion:
513                  throw new ServiceVersionException("Exchange Server doesn't support the requested version.");
514    
515                case ErrorSchemaValidation:
516                  // If we're talking to an E12 server
517                  // (8.00.xxxx.xxx), a schema
518                  // validation error is the same as
519                  // a version mismatch error.
520                  // (Which only will happen if we
521                  // send a request that's not valid
522                  // for E12).
523                  if ((this.service.getServerInfo() != null) && (this.service.getServerInfo().getMajorVersion()
524                                                                 == 8) && (
525                          this.service.getServerInfo().getMinorVersion() == 0)) {
526                    throw new ServiceVersionException("Exchange Server doesn't support the requested version.");
527                  }
528    
529                  break;
530    
531                case ErrorIncorrectSchemaVersion:
532                  // This shouldn't happen. It
533                  // indicates that a request wasn't
534                  // valid for the version that was specified.
535                  EwsUtilities.ewsAssert(false, "ServiceRequestBase.ProcessWebException",
536                                         "Exchange server supports " + "requested version "
537                                         + "but request was invalid for that version");
538                  break;
539    
540                default:
541                  // Other error codes will
542                  // be reported as remote error
543                  break;
544              }
545    
546              // General fall-through case:
547              // throw a ServiceResponseException
548              throw new ServiceResponseException(new ServiceResponse(soapFaultDetails));
549            }
550          } else {
551            this.service.processHttpErrorResponse(req, webException);
552          }
553        }
554    
555      }
556    
557      /**
558       * Reads the SOAP fault.
559       *
560       * @param reader The reader.
561       * @return SOAP fault details.
562       */
563      protected SoapFaultDetails readSoapFault(EwsServiceXmlReader reader) {
564        SoapFaultDetails soapFaultDetails = null;
565    
566        try {
567          this.readXmlDeclaration(reader);
568    
569          reader.read();
570          if (!reader.isStartElement() || (!reader.getLocalName()
571              .equals(XmlElementNames.SOAPEnvelopeElementName))) {
572            return soapFaultDetails;
573          }
574    
575          // EWS can sometimes return SOAP faults using the SOAP 1.2
576          // namespace. Get the
577          // namespace URI from the envelope element and use it for the rest
578          // of the parsing.
579          // If it's not 1.1 or 1.2, we can't continue.
580          XmlNamespace soapNamespace = EwsUtilities.getNamespaceFromUri(reader.getNamespaceUri());
581          if (soapNamespace == XmlNamespace.NotSpecified) {
582            return soapFaultDetails;
583          }
584    
585          reader.read();
586    
587          // EWS doesn't always return a SOAP header. If this response
588          // contains a header element,
589          // read the server version information contained in the header.
590          if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPHeaderElementName)) {
591            do {
592              reader.read();
593    
594              if (reader.isStartElement(XmlNamespace.Types, XmlElementNames.ServerVersionInfo)) {
595                this.service.setServerInfo(ExchangeServerInfo.parse(reader));
596              }
597            } while (!reader.isEndElement(soapNamespace, XmlElementNames.SOAPHeaderElementName));
598    
599            // Queue up the next read
600            reader.read();
601          }
602    
603          // Parse the fault element contained within the SOAP body.
604          if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPBodyElementName)) {
605            do {
606              reader.read();
607    
608              // Parse Fault element
609              if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPFaultElementName)) {
610                soapFaultDetails = SoapFaultDetails.parse(reader, soapNamespace);
611              }
612            } while (!reader.isEndElement(soapNamespace, XmlElementNames.SOAPBodyElementName));
613          }
614    
615          reader.readEndElement(soapNamespace, XmlElementNames.SOAPEnvelopeElementName);
616        } catch (Exception e) {
617          // If response doesn't contain a valid SOAP fault, just ignore
618          // exception and
619          // return null for SOAP fault details.
620          LOG.error(e);
621        }
622    
623        return soapFaultDetails;
624      }
625    
626      /**
627       * Validates request parameters, and emits the request to the server.
628       *
629       * @return The response returned by the server.
630       * @throws Exception on error
631       */
632      protected HttpWebRequest validateAndEmitRequest() throws Exception {
633        this.validate();
634    
635        HttpWebRequest request = this.buildEwsHttpWebRequest();
636    
637        try {
638          try {
639            return this.getEwsHttpWebResponse(request);
640          } catch (HttpErrorException e) {
641            processWebException(e, request);
642    
643            // Wrap exception if the above code block didn't throw
644            throw new ServiceRequestException(String.format("The request failed. %s", e.getMessage()), e);
645          }
646        } catch (Exception e) {
647          try {
648            request.close();
649          } catch (Exception e2) {
650            // Ignore exception while closing the request.
651          }
652    
653          throw e;
654        }
655      }
656    
657      /**
658       * Builds the HttpWebRequest object for current service request with exception handling.
659       *
660       * @return An HttpWebRequest instance
661       * @throws Exception on error
662       */
663      protected HttpWebRequest buildEwsHttpWebRequest() throws Exception {
664          HttpWebRequest request = service.prepareHttpWebRequest();
665        return buildEwsHttpWebRequest(request);
666      }
667    
668      /**
669       * Builds a HttpWebRequest object from a pooling connection manager for current service request
670       * with exception handling.
671       * <p>
672       * Used for subscriptions.
673       * </p>
674       * 
675       * @return A HttpWebRequest instance
676       * @throws Exception on error
677       */
678      protected HttpWebRequest buildEwsHttpPoolingWebRequest() throws Exception {
679        HttpWebRequest request = service.prepareHttpPoolingWebRequest();
680        return buildEwsHttpWebRequest(request);
681      }
682    
683      private HttpWebRequest buildEwsHttpWebRequest(HttpWebRequest request) throws Exception {
684        try {
685    
686          service.traceHttpRequestHeaders(TraceFlags.EwsRequestHttpHeaders, request);
687    
688          ByteArrayOutputStream requestStream = (ByteArrayOutputStream) request.getOutputStream();
689    
690          EwsServiceXmlWriter writer = new EwsServiceXmlWriter(service, requestStream);
691    
692          boolean needSignature =
693              service.getCredentials() != null && service.getCredentials().isNeedSignature();
694          writer.setRequireWSSecurityUtilityNamespace(needSignature);
695    
696          writeToXml(writer);
697    
698          if (needSignature) {
699            service.getCredentials().sign(requestStream);
700          }
701    
702          service.traceXml(TraceFlags.EwsRequest, requestStream);
703    
704          return request;
705        } catch (IOException e) {
706          // Wrap exception.
707          throw new ServiceRequestException(String.format("The request failed. %s", e.getMessage()), e);
708        }
709      }
710    
711      /**
712       * Gets the IEwsHttpWebRequest object from the specifiedHttpWebRequest object with exception handling
713       *
714       * @param request The specified HttpWebRequest
715       * @return An HttpWebResponse instance
716       * @throws Exception on error
717       */
718      protected HttpWebRequest getEwsHttpWebResponse(HttpWebRequest request) throws Exception {
719        try {
720          request.executeRequest();
721    
722          if (request.getResponseCode() >= 400) {
723            throw new HttpErrorException(
724                "The remote server returned an error: (" + request.getResponseCode() + ")" +
725                request.getResponseText(), request.getResponseCode());
726          }
727        } catch (IOException e) {
728          // Wrap exception.
729          throw new ServiceRequestException(String.format("The request failed. %s", e.getMessage()), e);
730        }
731    
732        return request;
733      }
734    
735      /**
736       * Checks whether input string is null or empty.
737       *
738       * @param str The input string.
739       * @return true if input string is null or empty, otherwise false
740       */
741      private boolean isNullOrEmpty(String str) {
742        return null == str || str.isEmpty();
743      }
744    
745      /**
746       * Try to read the XML declaration. If it's not there, the server didn't return XML.
747       *
748       * @param reader The reader.
749       */
750      private void readXmlDeclaration(EwsServiceXmlReader reader) throws Exception {
751        try {
752          reader.read(new XmlNodeType(XmlNodeType.START_DOCUMENT));
753        } catch (XmlException ex) {
754          throw new ServiceRequestException("The response received from the service didn't contain valid XML.",
755                                            ex);
756        } catch (ServiceXmlDeserializationException ex) {
757          throw new ServiceRequestException("The response received from the service didn't contain valid XML.",
758                                            ex);
759        }
760      }
761    
762    }