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