/**
   * Reads the SOAP fault.
   *
   * @param reader The reader.
   * @return SOAP fault details.
   */
  private SoapFaultDetails readSoapFault(EwsXmlReader reader) {
    SoapFaultDetails soapFaultDetails = null;

    try {

      reader.read();
      if (reader.getNodeType().getNodeType() == XmlNodeType.START_DOCUMENT) {
        reader.read();
      }
      if (!reader.isStartElement()
          || (!reader.getLocalName().equals(XmlElementNames.SOAPEnvelopeElementName))) {
        return null;
      }

      // Get the namespace URI from the envelope element and use it for
      // the rest of the parsing.
      // If it's not 1.1 or 1.2, we can't continue.
      XmlNamespace soapNamespace = EwsUtilities.getNamespaceFromUri(reader.getNamespaceUri());
      if (soapNamespace == XmlNamespace.NotSpecified) {
        return null;
      }

      reader.read();

      // Skip SOAP header.
      if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPHeaderElementName)) {
        do {
          reader.read();
        } while (!reader.isEndElement(soapNamespace, XmlElementNames.SOAPHeaderElementName));

        // Queue up the next read
        reader.read();
      }

      // Parse the fault element contained within the SOAP body.
      if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPBodyElementName)) {
        do {
          reader.read();

          // Parse Fault element
          if (reader.isStartElement(soapNamespace, XmlElementNames.SOAPFaultElementName)) {
            soapFaultDetails = SoapFaultDetails.parse(reader, soapNamespace);
          }
        } while (!reader.isEndElement(soapNamespace, XmlElementNames.SOAPBodyElementName));
      }

      reader.readEndElement(soapNamespace, XmlElementNames.SOAPEnvelopeElementName);
    } catch (Exception e) {
      // If response doesn't contain a valid SOAP fault, just ignore
      // exception and
      // return null for SOAP fault details.
      e.printStackTrace();
    }

    return soapFaultDetails;
  }
  /**
   * Read SOAP header.
   *
   * @param reader EwsXmlReader.
   * @throws Exception the exception
   */
  protected void readSoapHeaders(EwsXmlReader reader) throws Exception {
    reader.readStartElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName);
    do {
      reader.read();

      this.readSoapHeader(reader);
    } while (!reader.isEndElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName));
  }
  /**
   * Parses the detail node.
   *
   * @param reader the reader
   * @throws ServiceXmlDeserializationException the service xml deserialization exception
   * @throws javax.xml.stream.XMLStreamException the xML stream exception
   * @throws Exception the exception
   */
  private void parseDetailNode(EwsXmlReader reader)
      throws ServiceXmlDeserializationException, XMLStreamException, Exception, Exception {
    do {
      reader.read();
      if (reader.getNodeType().equals(new XmlNodeType(XmlNodeType.START_ELEMENT))) {
        String localName = reader.getLocalName();
        if (localName.equals(XmlElementNames.EwsResponseCodeElementName)) {
          try {
            this.setResponseCode(reader.readElementValue(ServiceError.class));
          } catch (Exception e) {
            e.printStackTrace();

            // ServiceError couldn't be mapped to enum value, treat
            // as an ISE
            this.setResponseCode(ServiceError.ErrorInternalServerError);
          }

        } else if (localName.equals(XmlElementNames.EwsMessageElementName)) {
          this.setMessage(reader.readElementValue());
        } else if (localName.equals(XmlElementNames.EwsLineElementName)) {
          this.setLineNumber(reader.readElementValue(Integer.class));
        } else if (localName.equals(XmlElementNames.EwsPositionElementName)) {
          this.setPositionWithinLine(reader.readElementValue(Integer.class));
        } else if (localName.equals(XmlElementNames.EwsErrorCodeElementName)) {
          try {
            this.setErrorCode(reader.readElementValue(ServiceError.class));
          } catch (Exception e) {
            e.printStackTrace();

            // ServiceError couldn't be mapped to enum value, treat
            // as an ISE
            this.setErrorCode(ServiceError.ErrorInternalServerError);
          }

        } else if (localName.equals(XmlElementNames.EwsExceptionTypeElementName)) {
          try {
            this.setExceptionType(reader.readElementValue());
          } catch (Exception e) {
            e.printStackTrace();
            this.setExceptionType(null);
          }
        } else if (localName.equals(XmlElementNames.MessageXml)) {
          this.parseMessageXml(reader);
        }
      }
    } while (!reader.isEndElement(
        XmlNamespace.NotSpecified, XmlElementNames.SOAPDetailElementName));
  }
  /**
   * Parses the.
   *
   * @param reader the reader
   * @param soapNamespace the soap namespace
   * @return the soap fault details
   * @throws Exception the exception
   */
  public static SoapFaultDetails parse(EwsXmlReader reader, XmlNamespace soapNamespace)
      throws Exception {
    SoapFaultDetails soapFaultDetails = new SoapFaultDetails();

    do {
      reader.read();
      if (reader.getNodeType().equals(new XmlNodeType(XmlNodeType.START_ELEMENT))) {
        String localName = reader.getLocalName();
        if (localName.equals(XmlElementNames.SOAPFaultCodeElementName)) {
          soapFaultDetails.setFaultCode(reader.readElementValue());
        } else if (localName.equals(XmlElementNames.SOAPFaultStringElementName)) {
          soapFaultDetails.setFaultString(reader.readElementValue());
        } else if (localName.equals(XmlElementNames.SOAPFaultActorElementName)) {
          soapFaultDetails.setFaultActor(reader.readElementValue());
        } else if (localName.equals(XmlElementNames.SOAPDetailElementName)) {
          soapFaultDetails.parseDetailNode(reader);
        }
      }
    } while (!reader.isEndElement(soapNamespace, XmlElementNames.SOAPFaultElementName));

    return soapFaultDetails;
  }
  /**
   * Read ServerVersionInfo SOAP header.
   *
   * @param reader EwsXmlReader.
   * @return ExchangeServerInfo ExchangeServerInfo object
   * @throws Exception the exception
   */
  private ExchangeServerInfo readServerVersionInfo(EwsXmlReader reader) throws Exception {
    ExchangeServerInfo serverInfo = new ExchangeServerInfo();
    do {
      reader.read();

      if (reader.isStartElement()) {
        if (reader.getLocalName().equals(XmlElementNames.MajorVersion)) {
          serverInfo.setMajorVersion(reader.readElementValue(Integer.class));
        } else if (reader.getLocalName().equals(XmlElementNames.MinorVersion)) {
          serverInfo.setMinorVersion(reader.readElementValue(Integer.class));
        } else if (reader.getLocalName().equals(XmlElementNames.MajorBuildNumber)) {
          serverInfo.setMajorBuildNumber(reader.readElementValue(Integer.class));
        } else if (reader.getLocalName().equals(XmlElementNames.MinorBuildNumber)) {
          serverInfo.setMinorBuildNumber(reader.readElementValue(Integer.class));
        } else if (reader.getLocalName().equals(XmlElementNames.Version)) {
          serverInfo.setVersionString(reader.readElementValue());
        }
      }
    } while (!reader.isEndElement(XmlNamespace.Autodiscover, XmlElementNames.ServerVersionInfo));

    return serverInfo;
  }
  /**
   * Parses the message xml.
   *
   * @param reader the reader
   * @throws Exception the exception
   * @throws ServiceXmlDeserializationException the service xml deserialization exception
   */
  private void parseMessageXml(EwsXmlReader reader)
      throws Exception, ServiceXmlDeserializationException, Exception {
    // E14:172881: E12 and E14 return the MessageXml element in different
    // namespaces (types namespace for E12, errors namespace in E14). To
    // avoid this problem, the parser will match the namespace from the
    // start and end elements.
    XmlNamespace elementNS = EwsUtilities.getNamespaceFromUri(reader.getNamespaceUri());

    if (!reader.isEmptyElement()) {
      do {
        reader.read();

        if (reader.isStartElement() && !reader.isEmptyElement()) {
          String localName = reader.getLocalName();
          if (localName.equals(XmlElementNames.Value)) {
            this.errorDetails.put(
                reader.readAttributeValue(XmlAttributeNames.Name), reader.readElementValue());
          }
        }
      } while (!reader.isEndElement(elementNS, XmlElementNames.MessageXml));
    } else {
      reader.read();
    }
  }