microsoft.exchange.webservices.data.core.EwsXmlReader.java Source code

Java tutorial

Introduction

Here is the source code for microsoft.exchange.webservices.data.core.EwsXmlReader.java

Source

/*
 * The MIT License
 * Copyright (c) 2012 Microsoft Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package microsoft.exchange.webservices.data.core;

import static javax.xml.stream.XMLStreamConstants.CHARACTERS;
import static javax.xml.stream.XMLStreamConstants.COMMENT;
import static javax.xml.stream.XMLStreamConstants.PROCESSING_INSTRUCTION;

import com.google.common.base.Charsets;
import com.google.common.io.ByteSink;
import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
import microsoft.exchange.webservices.data.misc.OutParam;
import microsoft.exchange.webservices.data.security.XmlNodeType;
import microsoft.exchange.webservices.data.util.CachedContent;
import org.apache.commons.codec.binary.Base64OutputStream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

/**
 * Defines the EwsXmlReader class.
 */
public class EwsXmlReader {

    private static final Log LOG = LogFactory.getLog(EwsXmlReader.class);

    /**
     * The xml reader.
     */
    private XMLEventReader xmlReader = null;

    /**
     * The present event.
     */
    private XMLEvent presentEvent;

    /**
     * The prev event.
     */
    private XMLEvent prevEvent;

    private static final XMLInputFactory xmlInputFactory;
    static {
        xmlInputFactory = XMLInputFactory.newFactory();
        xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
    }

    /**
     * Initializes a new instance of the EwsXmlReader class.
     *
     * @param stream the stream
     * @throws Exception on error
     */
    public EwsXmlReader(InputStream stream) throws Exception {
        this.xmlReader = initializeXmlReader(stream);
    }

    /**
     * Initializes the XML reader.
     *
     * @param stream the stream
     * @return An XML reader to use.
     * @throws Exception on error
     */
    protected XMLEventReader initializeXmlReader(InputStream stream) throws Exception {
        return xmlInputFactory.createXMLEventReader(stream);
    }

    /**
     * Formats the name of the element.
     *
     * @param namespacePrefix  The namespace prefix
     * @param localElementName Element name
     * @return the string
     */
    private static String formatElementName(String namespacePrefix, String localElementName) {

        return isNullOrEmpty(namespacePrefix) ? localElementName : namespacePrefix + ":" + localElementName;
    }

    /**
     * Read XML element.
     *
     * @param xmlNamespace The XML namespace
     * @param localName    Name of the local
     * @param nodeType     Type of the node
     * @throws Exception the exception
     */
    private void internalReadElement(XmlNamespace xmlNamespace, String localName, XmlNodeType nodeType)
            throws Exception {

        if (xmlNamespace == XmlNamespace.NotSpecified) {
            this.internalReadElement("", localName, nodeType);
        } else {
            this.read(nodeType);

            if ((!this.getLocalName().equals(localName))
                    || (!this.getNamespaceUri().equals(EwsUtilities.getNamespaceUri(xmlNamespace)))) {
                throw new ServiceXmlDeserializationException(String.format(
                        "An element node '%s:%s' of the type %s was expected, but node '%s' of type %s was found.",
                        EwsUtilities.getNamespacePrefix(xmlNamespace), localName, nodeType.toString(),
                        this.getName(), this.getNodeType().toString()));
            }
        }
    }

    /**
     * Read XML element.
     *
     * @param namespacePrefix The namespace prefix
     * @param localName       Name of the local
     * @param nodeType        Type of the node
     * @throws Exception the exception
     */
    private void internalReadElement(String namespacePrefix, String localName, XmlNodeType nodeType)
            throws Exception {
        read(nodeType);

        if ((!this.getLocalName().equals(localName)) || (!this.getNamespacePrefix().equals(namespacePrefix))) {
            throw new ServiceXmlDeserializationException(String.format(
                    "An element node '%s:%s' of the type %s was expected, but node '%s' of type %s was found.",
                    namespacePrefix, localName, nodeType.toString(), this.getName(),
                    this.getNodeType().toString()));
        }
    }

    /**
     * Reads the specified node type.
     *
     * @throws ServiceXmlDeserializationException  the service xml deserialization exception
     * @throws XMLStreamException the XML stream exception
     */
    public void read() throws ServiceXmlDeserializationException, XMLStreamException {
        read(false);
    }

    /**
     * Reads the specified node type.
     *
     * @param keepWhiteSpace Do not remove whitespace characters if true
     * @throws ServiceXmlDeserializationException  the service xml deserialization exception
     * @throws XMLStreamException the XML stream exception
     */
    private void read(boolean keepWhiteSpace) throws ServiceXmlDeserializationException, XMLStreamException {
        // The caller to EwsXmlReader.Read expects
        // that there's another node to
        // read. Throw an exception if not true.
        while (true) {
            if (!xmlReader.hasNext()) {
                throw new ServiceXmlDeserializationException("Unexpected end of XML document.");
            } else {
                XMLEvent event = xmlReader.nextEvent();
                if (event.getEventType() == CHARACTERS) {
                    Characters characters = (Characters) event;
                    if (!keepWhiteSpace)
                        if (characters.isIgnorableWhiteSpace() || characters.isWhiteSpace()) {
                            continue;
                        }
                }
                this.prevEvent = this.presentEvent;
                this.presentEvent = event;
                break;
            }
        }
    }

    /**
     * Reads the specified node type.
     *
     * @param nodeType Type of the node.
     * @throws Exception the exception
     */
    public void read(XmlNodeType nodeType) throws Exception {
        this.read();
        if (!this.getNodeType().equals(nodeType)) {
            throw new ServiceXmlDeserializationException(String.format(
                    "The expected XML node type was %s, but the actual type is %s.", nodeType, this.getNodeType()));
        }
    }

    /**
     * Read attribute value from QName.
     *
     * @param qName QName of the attribute
     * @return Attribute Value
     * @throws Exception thrown if attribute value can not be read
     */
    private String readAttributeValue(QName qName) throws Exception {
        if (this.presentEvent.isStartElement()) {
            StartElement startElement = this.presentEvent.asStartElement();
            Attribute attr = startElement.getAttributeByName(qName);
            if (null != attr) {
                return attr.getValue();
            } else {
                return null;
            }
        } else {
            String errMsg = String.format("Could not fetch attribute %s", qName.toString());
            throw new Exception(errMsg);
        }
    }

    /**
     * Reads the attribute value.
     *
     * @param xmlNamespace  The XML namespace.
     * @param attributeName Name of the attribute
     * @return Attribute Value
     * @throws Exception the exception
     */
    public String readAttributeValue(XmlNamespace xmlNamespace, String attributeName) throws Exception {
        if (xmlNamespace == XmlNamespace.NotSpecified) {
            return this.readAttributeValue(attributeName);
        } else {
            QName qName = new QName(EwsUtilities.getNamespaceUri(xmlNamespace), attributeName);
            return readAttributeValue(qName);
        }
    }

    /**
     * Reads the attribute value.
     *
     * @param attributeName Name of the attribute
     * @return Attribute value.
     * @throws Exception the exception
     */
    public String readAttributeValue(String attributeName) throws Exception {
        QName qName = new QName(attributeName);
        return readAttributeValue(qName);
    }

    /**
     * Reads the attribute value.
     *
     * @param <T>           the generic type
     * @param cls           the cls
     * @param attributeName the attribute name
     * @return T
     * @throws Exception the exception
     */
    public <T> T readAttributeValue(Class<T> cls, String attributeName) throws Exception {
        return EwsUtilities.parse(cls, this.readAttributeValue(attributeName));
    }

    /**
     * Reads a nullable attribute value.
     *
     * @param <T>           the generic type
     * @param cls           the cls
     * @param attributeName the attribute name
     * @return T
     * @throws Exception the exception
     */
    public <T> T readNullableAttributeValue(Class<T> cls, String attributeName) throws Exception {
        String attributeValue = this.readAttributeValue(attributeName);
        if (attributeValue == null) {
            return null;
        } else {
            return EwsUtilities.parse(cls, attributeValue);
        }
    }

    /**
     * Reads the element value.
     *
     * @param namespacePrefix the namespace prefix
     * @param localName       the local name
     * @return String
     * @throws Exception the exception
     */
    public String readElementValue(String namespacePrefix, String localName) throws Exception {
        if (!this.isStartElement(namespacePrefix, localName)) {
            this.readStartElement(namespacePrefix, localName);
        }

        String value = null;

        if (!this.isEmptyElement()) {
            value = this.readValue();
        }
        return value;
    }

    /**
     * Reads the element value.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @return String
     * @throws Exception the exception
     */
    public String readElementValue(XmlNamespace xmlNamespace, String localName) throws Exception {

        if (!this.isStartElement(xmlNamespace, localName)) {
            this.readStartElement(xmlNamespace, localName);
        }

        String value = null;

        if (!this.isEmptyElement()) {
            value = this.readValue();
        } else {
            this.read();
        }

        return value;
    }

    /**
     * Read element value.
     *
     * @return String
     * @throws Exception the exception
     */
    public String readElementValue() throws Exception {
        this.ensureCurrentNodeIsStartElement();

        return this.readElementValue(this.getNamespacePrefix(), this.getLocalName());
    }

    /**
     * Reads the element value.
     *
     * @param <T>          the generic type
     * @param cls          the cls
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @return T
     * @throws Exception the exception
     */
    public <T> T readElementValue(Class<T> cls, XmlNamespace xmlNamespace, String localName) throws Exception {
        if (!this.isStartElement(xmlNamespace, localName)) {
            this.readStartElement(xmlNamespace, localName);
        }

        T value = null;

        if (!this.isEmptyElement()) {
            value = this.readValue(cls);
        }

        return value;
    }

    /**
     * Read element value.
     *
     * @param <T> the generic type
     * @param cls the cls
     * @return T
     * @throws Exception the exception
     */
    public <T> T readElementValue(Class<T> cls) throws Exception {
        this.ensureCurrentNodeIsStartElement();

        T value = null;

        if (!this.isEmptyElement()) {
            value = this.readValue(cls);
        }

        return value;
    }

    /**
     * Reads the value. Should return content element or text node as string
     * Present event must be START ELEMENT. After executing this function
     * Present event will be set on END ELEMENT
     *
     * @return String
     * @throws XMLStreamException the XML stream exception
     * @throws ServiceXmlDeserializationException the service xml deserialization exception
     */
    public String readValue() throws XMLStreamException, ServiceXmlDeserializationException {
        return readValue(false);
    }

    /**
     * Reads the value. Should return content element or text node as string
     * Present event must be START ELEMENT. After executing this function
     * Present event will be set on END ELEMENT
     *
     * @param keepWhiteSpace Do not remove whitespace characters if true
     * @return String
     * @throws XMLStreamException the XML stream exception
     * @throws ServiceXmlDeserializationException the service xml deserialization exception
     */
    public String readValue(boolean keepWhiteSpace) throws XMLStreamException, ServiceXmlDeserializationException {
        if (this.presentEvent.isStartElement()) {
            // Go to next event and check for Characters event
            this.read(keepWhiteSpace);
            if (this.presentEvent.isCharacters()) {
                final StringBuilder elementValue = new StringBuilder();
                do {
                    if (this.getNodeType().nodeType == CHARACTERS) {
                        Characters characters = (Characters) this.presentEvent;
                        if (keepWhiteSpace || (!characters.isIgnorableWhiteSpace() && !characters.isWhiteSpace())) {
                            final String charactersData = characters.getData();
                            if (charactersData != null && !charactersData.isEmpty()) {
                                elementValue.append(charactersData);
                            }
                        }
                    }
                    this.read();
                } while (!this.presentEvent.isEndElement());
                // Characters chars = this.presentEvent.asCharacters();
                // String elementValue = chars.getData();
                // Advance to next event post Characters (ideally it will be End
                // Element)
                // this.read();
                return elementValue.toString();
            } else if (this.presentEvent.isEndElement()) {
                return "";
            } else {
                throw new ServiceXmlDeserializationException(
                        getReadValueErrMsg("Could not find " + XmlNodeType.getString(CHARACTERS)));
            }
        } else if (this.presentEvent.getEventType() == CHARACTERS && this.presentEvent.isCharacters()) {
            /*
             * if(this.presentEvent.asCharacters().getData().equals("<")) {
            */
            final String charData = this.presentEvent.asCharacters().getData();
            final StringBuilder data = new StringBuilder(charData == null ? "" : charData);
            do {
                this.read(keepWhiteSpace);
                if (this.getNodeType().nodeType == CHARACTERS) {
                    Characters characters = (Characters) this.presentEvent;
                    if (keepWhiteSpace || (!characters.isIgnorableWhiteSpace() && !characters.isWhiteSpace())) {
                        final String charactersData = characters.getData();
                        if (charactersData != null && !charactersData.isEmpty()) {
                            data.append(charactersData);
                        }
                    }
                }
            } while (!this.presentEvent.isEndElement());
            return data.toString();// this.presentEvent. = new XMLEvent();
            /*
             * } else { Characters chars = this.presentEvent.asCharacters();
             * String elementValue = chars.getData(); // Advance to next event
             * post Characters (ideally it will be End // Element) this.read();
             * return elementValue; }
             */
        } else {
            throw new ServiceXmlDeserializationException(
                    getReadValueErrMsg("Expected is " + XmlNodeType.getString(XmlNodeType.START_ELEMENT)));
        }

    }

    /**
     * Tries to read value.
     *
     * @param value the value
     * @return boolean
     * @throws XMLStreamException the XML stream exception
     * @throws ServiceXmlDeserializationException  the service xml deserialization exception
     */
    public boolean tryReadValue(OutParam<String> value)
            throws XMLStreamException, ServiceXmlDeserializationException {
        if (!this.isEmptyElement()) {
            this.read();

            if (this.presentEvent.isCharacters()) {
                value.setParam(this.readValue());
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    /**
     * Reads the value.
     *
     * @param <T> the generic type
     * @param cls the cls
     * @return T
     * @throws Exception the exception
     */
    public <T> T readValue(Class<T> cls) throws Exception {
        return EwsUtilities.parse(cls, this.readValue());
    }

    public CachedContent readBase64ElementValue() throws Exception {
        CachedContent content = new CachedContent();
        try {
            readBase64ElementValue(content.sink());
            return content;
        } catch (Exception e) {
            content.delete();
            throw e;
        }
    }

    public byte[] readBase64ElementValueAsBytes() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        readBase64ElementValue(baos);
        return baos.toByteArray();
    }

    public void readBase64ElementValue(ByteSink sink) throws Exception {
        try (OutputStream os = sink.openBufferedStream()) {
            readBase64ElementValue(os);
        }
    }

    // Note: Closed output stream upon completion
    private void readBase64ElementValue(OutputStream os) throws Exception {
        if (isStartElement()) {
            read();
        }
        Base64OutputStream decoderOut = new Base64OutputStream(os, false);
        while (!presentEvent.isEndElement()) {
            switch (presentEvent.getEventType()) {
            case CHARACTERS:
                String s = ((Characters) presentEvent).getData();
                decoderOut.write(s.getBytes(Charsets.UTF_8));
                break;
            case PROCESSING_INSTRUCTION:
            case COMMENT:
                // Skip processing events
                break;
            default:
                throw new XMLStreamException("Unexpected event while parsing BASE64 element value: " + presentEvent,
                        presentEvent.getLocation());
            }
            read();
        }
        decoderOut.close();
    }

    /**
     * Reads the start element.
     *
     * @param namespacePrefix the namespace prefix
     * @param localName       the local name
     * @throws Exception the exception
     */
    public void readStartElement(String namespacePrefix, String localName) throws Exception {
        this.internalReadElement(namespacePrefix, localName, new XmlNodeType(XmlNodeType.START_ELEMENT));
    }

    /**
     * Reads the start element.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @throws Exception the exception
     */
    public void readStartElement(XmlNamespace xmlNamespace, String localName) throws Exception {
        this.internalReadElement(xmlNamespace, localName, new XmlNodeType(XmlNodeType.START_ELEMENT));
    }

    /**
     * Reads the end element.
     *
     * @param namespacePrefix the namespace prefix
     * @param elementName     the element name
     * @throws Exception the exception
     */
    public void readEndElement(String namespacePrefix, String elementName) throws Exception {
        this.internalReadElement(namespacePrefix, elementName, new XmlNodeType(XmlNodeType.END_ELEMENT));
    }

    /**
     * Reads the end element.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @throws Exception the exception
     */
    public void readEndElement(XmlNamespace xmlNamespace, String localName) throws Exception {

        this.internalReadElement(xmlNamespace, localName, new XmlNodeType(XmlNodeType.END_ELEMENT));

    }

    /**
     * Reads the end element if necessary.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @throws Exception the exception
     */
    public void readEndElementIfNecessary(XmlNamespace xmlNamespace, String localName) throws Exception {

        if (!(this.isStartElement(xmlNamespace, localName) && this.isEmptyElement())) {
            if (!this.isEndElement(xmlNamespace, localName)) {
                this.readEndElement(xmlNamespace, localName);
            }
        }
    }

    /**
     * Determines whether current element is a start element.
     *
     * @return boolean
     */
    public boolean isStartElement() {
        return this.presentEvent.isStartElement();
    }

    /**
     * Determines whether current element is a start element.
     *
     * @param namespacePrefix the namespace prefix
     * @param localName       the local name
     * @return boolean
     */
    public boolean isStartElement(String namespacePrefix, String localName) {
        boolean isStart = false;
        if (this.presentEvent.isStartElement()) {
            StartElement startElement = this.presentEvent.asStartElement();
            QName qName = startElement.getName();
            isStart = qName.getLocalPart().equals(localName) && qName.getPrefix().equals(namespacePrefix);
        }
        return isStart;
    }

    /**
     * Determines whether current element is a start element.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @return true for matching start element; false otherwise.
     */
    public boolean isStartElement(XmlNamespace xmlNamespace, String localName) {
        return this.isStartElement() && StringUtils.equals(getLocalName(), localName)
                && (StringUtils.equals(getNamespacePrefix(), EwsUtilities.getNamespacePrefix(xmlNamespace))
                        || StringUtils.equals(getNamespaceUri(), EwsUtilities.getNamespaceUri(xmlNamespace)));
    }

    /**
     * Determines whether current element is a end element.
     *
     * @param namespacePrefix the namespace prefix
     * @param localName       the local name
     * @return boolean
     */
    public boolean isEndElement(String namespacePrefix, String localName) {
        boolean isEndElement = false;
        if (this.presentEvent.isEndElement()) {
            EndElement endElement = this.presentEvent.asEndElement();
            QName qName = endElement.getName();
            isEndElement = qName.getLocalPart().equals(localName) && qName.getPrefix().equals(namespacePrefix);

        }
        return isEndElement;
    }

    /**
     * Determines whether current element is a end element.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @return boolean
     */
    public boolean isEndElement(XmlNamespace xmlNamespace, String localName) {

        boolean isEndElement = false;
        /*
         * if(localName.equals("Body")) { return true; } else
         */
        if (this.presentEvent.isEndElement()) {
            EndElement endElement = this.presentEvent.asEndElement();
            QName qName = endElement.getName();
            isEndElement = qName.getLocalPart().equals(localName)
                    && (qName.getPrefix().equals(EwsUtilities.getNamespacePrefix(xmlNamespace))
                            || qName.getNamespaceURI().equals(EwsUtilities.getNamespaceUri(xmlNamespace)));

        }
        return isEndElement;
    }

    /**
     * Skips the element.
     *
     * @param namespacePrefix the namespace prefix
     * @param localName       the local name
     * @throws Exception the exception
     */
    public void skipElement(String namespacePrefix, String localName) throws Exception {
        if (!this.isEndElement(namespacePrefix, localName)) {
            if (!this.isStartElement(namespacePrefix, localName)) {
                this.readStartElement(namespacePrefix, localName);
            }

            if (!this.isEmptyElement()) {
                do {
                    this.read();
                } while (!this.isEndElement(namespacePrefix, localName));
            }
        }
    }

    /**
     * Skips the element.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @throws Exception the exception
     */
    public void skipElement(XmlNamespace xmlNamespace, String localName) throws Exception {
        if (!this.isEndElement(xmlNamespace, localName)) {
            if (!this.isStartElement(xmlNamespace, localName)) {
                this.readStartElement(xmlNamespace, localName);
            }

            if (!this.isEmptyElement()) {
                do {
                    this.read();
                } while (!this.isEndElement(xmlNamespace, localName));
            }
        }
    }

    /**
     * Skips the current element.
     *
     * @throws Exception the exception
     */
    public void skipCurrentElement() throws Exception {
        this.skipElement(this.getNamespacePrefix(), this.getLocalName());
    }

    /**
     * Ensures the current node is start element.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @throws ServiceXmlDeserializationException the service xml deserialization exception
     */
    public void ensureCurrentNodeIsStartElement(XmlNamespace xmlNamespace, String localName)
            throws ServiceXmlDeserializationException {

        if (!this.isStartElement(xmlNamespace, localName)) {
            throw new ServiceXmlDeserializationException(
                    String.format("The element '%s' in namespace '%s' wasn't found at the current position.",
                            localName, xmlNamespace));
        }
    }

    /**
     * Ensures the current node is start element.
     *
     * @throws ServiceXmlDeserializationException the service xml deserialization exception
     */
    public void ensureCurrentNodeIsStartElement() throws ServiceXmlDeserializationException {
        XmlNodeType presentNodeType = new XmlNodeType(this.presentEvent.getEventType());
        if (!this.presentEvent.isStartElement()) {
            throw new ServiceXmlDeserializationException(
                    String.format("The start element was expected, but node '%s' of type %s was found.",
                            this.presentEvent.toString(), presentNodeType.toString()));
        }
    }

    /**
     * Ensures the current node is start element.
     *
     * @param xmlNamespace the xml namespace
     * @param localName    the local name
     * @throws Exception the exception
     */
    public void ensureCurrentNodeIsEndElement(XmlNamespace xmlNamespace, String localName) throws Exception {
        if (!this.isEndElement(xmlNamespace, localName)) {
            if (!(this.isStartElement(xmlNamespace, localName) && this.isEmptyElement())) {
                throw new ServiceXmlDeserializationException(
                        String.format("The element '%s' in namespace '%s' wasn't found at the current position.",
                                xmlNamespace, localName));
            }
        }
    }

    /**
     * Outer XML as string.
     *
     * @return String
     * @throws ServiceXmlDeserializationException the service xml deserialization exception
     * @throws XMLStreamException the XML stream exception
     */
    public String readOuterXml() throws ServiceXmlDeserializationException, XMLStreamException {
        if (!this.isStartElement()) {
            throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
        }

        XMLEvent startEvent = this.presentEvent;
        XMLEvent event;
        StringBuilder str = new StringBuilder();
        str.append(startEvent);
        do {
            event = this.xmlReader.nextEvent();
            str.append(event);
        } while (!checkEndElement(startEvent, event));

        return str.toString();
    }

    /**
     * Reads the Inner XML at the given location.
     *
     * @return String
     * @throws ServiceXmlDeserializationException the service xml deserialization exception
     * @throws XMLStreamException the XML stream exception
     */
    public String readInnerXml() throws ServiceXmlDeserializationException, XMLStreamException {
        if (!this.isStartElement()) {
            throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
        }

        XMLEvent startEvent = this.presentEvent;
        StringBuilder str = new StringBuilder();
        do {
            XMLEvent event = this.xmlReader.nextEvent();
            if (checkEndElement(startEvent, event)) {
                break;
            }
            str.append(event);
        } while (true);

        return str.toString();
    }

    /**
     * Check end element.
     *
     * @param startEvent the start event
     * @param endEvent   the end event
     * @return true, if successful
     */
    public static boolean checkEndElement(XMLEvent startEvent, XMLEvent endEvent) {
        boolean isEndElement = false;
        if (endEvent.isEndElement()) {
            QName qEName = endEvent.asEndElement().getName();
            QName qSName = startEvent.asStartElement().getName();
            isEndElement = qEName.getLocalPart().equals(qSName.getLocalPart())
                    && (qEName.getPrefix().equals(qSName.getPrefix())
                            || qEName.getNamespaceURI().equals(qSName.getNamespaceURI()));

        }
        return isEndElement;
    }

    /**
     * Gets the XML reader for node.
     *
     * @return null
     * @throws XMLStreamException the XML stream exception
     * @throws ServiceXmlDeserializationException the service xml deserialization exception
     * @throws FileNotFoundException the file not found exception
     */
    public XMLEventReader getXmlReaderForNode()
            throws FileNotFoundException, ServiceXmlDeserializationException, XMLStreamException {
        return readSubtree();
    }

    public XMLEventReader readSubtree()
            throws XMLStreamException, FileNotFoundException, ServiceXmlDeserializationException {

        if (!this.isStartElement()) {
            throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
        }

        XMLEventReader eventReader = null;
        InputStream in = null;
        XMLEvent startEvent = this.presentEvent;
        XMLEvent event = startEvent;
        StringBuilder str = new StringBuilder();
        str.append(startEvent);
        do {
            event = this.xmlReader.nextEvent();
            str.append(event);
        } while (!checkEndElement(startEvent, event));

        try {

            XMLInputFactory inputFactory = XMLInputFactory.newInstance();

            try {
                in = new ByteArrayInputStream(str.toString().getBytes("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                LOG.error(e);
            }
            eventReader = inputFactory.createXMLEventReader(in);

        } catch (Exception e) {
            LOG.error(e);
        }
        return eventReader;
    }

    /**
     * Reads to the next descendant element with the specified local name and
     * namespace.
     *
     * @param xmlNamespace The namespace of the element you with to move to.
     * @param localName    The local name of the element you wish to move to.
     * @throws XMLStreamException the XML stream exception
     */
    public void readToDescendant(XmlNamespace xmlNamespace, String localName) throws XMLStreamException {
        readToDescendant(localName, EwsUtilities.getNamespaceUri(xmlNamespace));
    }

    public boolean readToDescendant(String localName, String namespaceURI) throws XMLStreamException {

        if (!this.isStartElement()) {
            return false;
        }
        XMLEvent startEvent = this.presentEvent;
        XMLEvent event = this.presentEvent;
        do {
            if (event.isStartElement()) {
                QName qEName = event.asStartElement().getName();
                if (qEName.getLocalPart().equals(localName) && qEName.getNamespaceURI().equals(namespaceURI)) {
                    return true;
                }
            }
            event = this.xmlReader.nextEvent();
        } while (!checkEndElement(startEvent, event));

        return false;
    }

    /**
     * Gets a value indicating whether this instance has attribute.
     *
     * @return boolean
     */
    public boolean hasAttributes() {

        if (this.presentEvent.isStartElement()) {
            StartElement startElement = this.presentEvent.asStartElement();
            return startElement.getAttributes().hasNext();
        } else {
            return false;
        }
    }

    /**
     * Gets a value indicating whether current element is empty.
     *
     * @return boolean
     * @throws XMLStreamException the XML stream exception
     */
    public boolean isEmptyElement() throws XMLStreamException {
        boolean isPresentStartElement = this.presentEvent.isStartElement();
        boolean isNextEndElement = this.xmlReader.peek().isEndElement();
        return isPresentStartElement && isNextEndElement;
    }

    /**
     * Gets the local name of the current element.
     *
     * @return String
     */
    public String getLocalName() {

        String localName = null;

        if (this.presentEvent.isStartElement()) {
            localName = this.presentEvent.asStartElement().getName().getLocalPart();
        } else {

            localName = this.presentEvent.asEndElement().getName().getLocalPart();
        }
        return localName;
    }

    /**
     * Gets the namespace prefix.
     *
     * @return String
     */
    protected String getNamespacePrefix() {
        if (this.presentEvent.isStartElement()) {
            return this.presentEvent.asStartElement().getName().getPrefix();
        }
        if (this.presentEvent.isEndElement()) {
            return this.presentEvent.asEndElement().getName().getPrefix();
        }
        return null;
    }

    /**
     * Gets the namespace URI.
     *
     * @return String
     */
    public String getNamespaceUri() {

        String nameSpaceUri = null;
        if (this.presentEvent.isStartElement()) {
            nameSpaceUri = this.presentEvent.asStartElement().getName().getNamespaceURI();
        } else {

            nameSpaceUri = this.presentEvent.asEndElement().getName().getNamespaceURI();
        }
        return nameSpaceUri;
    }

    /**
     * Gets the type of the node.
     *
     * @return XmlNodeType
     * @throws XMLStreamException the XML stream exception
     */
    public XmlNodeType getNodeType() throws XMLStreamException {
        XMLEvent event = this.presentEvent;
        return new XmlNodeType(event.getEventType());
    }

    /**
     * Gets the name of the current element.
     *
     * @return Object
     */
    protected Object getName() {
        String name = null;
        if (this.presentEvent.isStartElement()) {
            name = this.presentEvent.asStartElement().getName().toString();
        } else {

            name = this.presentEvent.asEndElement().getName().toString();
        }
        return name;
    }

    /**
     * Checks is the string is null or empty.
     *
     * @param namespacePrefix the namespace prefix
     * @return true, if is null or empty
     */
    private static boolean isNullOrEmpty(String namespacePrefix) {
        return (namespacePrefix == null || namespacePrefix.isEmpty());

    }

    /**
     * Gets the error message which happened during {@link #readValue()}.
     *
     * @param details details message
     * @return error message with details
     */
    private String getReadValueErrMsg(final String details) {
        final int eventType = this.presentEvent.getEventType();
        return "Could not read value from " + XmlNodeType.getString(eventType) + "." + details;
    }

}