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;
025
026import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
027import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
028import microsoft.exchange.webservices.data.misc.OutParam;
029import microsoft.exchange.webservices.data.security.XmlNodeType;
030import org.apache.commons.codec.binary.Base64;
031import org.apache.commons.lang3.StringUtils;
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034
035import javax.xml.namespace.QName;
036import javax.xml.stream.XMLEventReader;
037import javax.xml.stream.XMLInputFactory;
038import javax.xml.stream.XMLStreamConstants;
039import javax.xml.stream.XMLStreamException;
040import javax.xml.stream.events.Attribute;
041import javax.xml.stream.events.Characters;
042import javax.xml.stream.events.EndElement;
043import javax.xml.stream.events.StartElement;
044import javax.xml.stream.events.XMLEvent;
045
046import java.io.ByteArrayInputStream;
047import java.io.ByteArrayOutputStream;
048import java.io.FileNotFoundException;
049import java.io.IOException;
050import java.io.InputStream;
051import java.io.OutputStream;
052import java.io.UnsupportedEncodingException;
053
054/**
055 * Defines the EwsXmlReader class.
056 */
057public class EwsXmlReader {
058
059  private static final Log LOG = LogFactory.getLog(EwsXmlReader.class);
060
061  /**
062   * The Read write buffer size.
063   */
064  private static final int ReadWriteBufferSize = 4096;
065
066  /**
067   * The xml reader.
068   */
069  private XMLEventReader xmlReader = null;
070
071  /**
072   * The present event.
073   */
074  private XMLEvent presentEvent;
075
076  /**
077   * The prev event.
078   */
079  private XMLEvent prevEvent;
080
081  /**
082   * Initializes a new instance of the EwsXmlReader class.
083   *
084   * @param stream the stream
085   * @throws Exception on error
086   */
087  public EwsXmlReader(InputStream stream) throws Exception {
088    this.xmlReader = initializeXmlReader(stream);
089  }
090
091  /**
092   * Initializes the XML reader.
093   *
094   * @param stream the stream
095   * @return An XML reader to use.
096   * @throws Exception on error
097   */
098  protected XMLEventReader initializeXmlReader(InputStream stream) throws Exception {
099    XMLInputFactory inputFactory = XMLInputFactory.newInstance();
100    inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
101
102    return inputFactory.createXMLEventReader(stream);
103  }
104
105
106  /**
107   * Formats the name of the element.
108   *
109   * @param namespacePrefix  The namespace prefix
110   * @param localElementName Element name
111   * @return the string
112   */
113  private static String formatElementName(String namespacePrefix,
114      String localElementName) {
115
116    return isNullOrEmpty(namespacePrefix) ? localElementName :
117        namespacePrefix + ":" + localElementName;
118  }
119
120  /**
121   * Read XML element.
122   *
123   * @param xmlNamespace The XML namespace
124   * @param localName    Name of the local
125   * @param nodeType     Type of the node
126   * @throws Exception the exception
127   */
128  private void internalReadElement(XmlNamespace xmlNamespace,
129      String localName, XmlNodeType nodeType) throws Exception {
130
131    if (xmlNamespace == XmlNamespace.NotSpecified) {
132      this.internalReadElement("", localName, nodeType);
133    } else {
134      this.read(nodeType);
135
136      if ((!this.getLocalName().equals(localName)) ||
137          (!this.getNamespaceUri().equals(EwsUtilities
138              .getNamespaceUri(xmlNamespace)))) {
139        throw new ServiceXmlDeserializationException(
140            String
141                .format(
142                    "An element node '%s:%s' of the type %s was expected, but node '%s' of type %s was found.",
143                    EwsUtilities
144                        .getNamespacePrefix(
145                            xmlNamespace),
146                    localName, nodeType.toString(), this
147                        .getName(), this.getNodeType()
148                        .toString()));
149      }
150    }
151  }
152
153  /**
154   * Read XML element.
155   *
156   * @param namespacePrefix The namespace prefix
157   * @param localName       Name of the local
158   * @param nodeType        Type of the node
159   * @throws Exception the exception
160   */
161  private void internalReadElement(String namespacePrefix, String localName,
162      XmlNodeType nodeType) throws Exception {
163    read(nodeType);
164
165    if ((!this.getLocalName().equals(localName)) ||
166        (!this.getNamespacePrefix().equals(namespacePrefix))) {
167      throw new ServiceXmlDeserializationException(String.format(
168          "An element node '%s:%s' of the type %s was expected, but node '%s' of type %s was found.", namespacePrefix, localName,
169          nodeType.toString(), this.getName(), this.getNodeType()
170              .toString()));
171    }
172  }
173
174  /**
175   * Reads the specified node type.
176   *
177   * @throws ServiceXmlDeserializationException  the service xml deserialization exception
178   * @throws XMLStreamException the XML stream exception
179   */
180  public void read() throws ServiceXmlDeserializationException,
181      XMLStreamException {
182    read(false);
183  }
184
185  /**
186   * Reads the specified node type.
187   *
188   * @param keepWhiteSpace Do not remove whitespace characters if true
189   * @throws ServiceXmlDeserializationException  the service xml deserialization exception
190   * @throws XMLStreamException the XML stream exception
191   */
192  private void read(boolean keepWhiteSpace) throws ServiceXmlDeserializationException,
193      XMLStreamException {
194    // The caller to EwsXmlReader.Read expects
195    // that there's another node to
196    // read. Throw an exception if not true.
197    while (true) {
198      if (!xmlReader.hasNext()) {
199        throw new ServiceXmlDeserializationException("Unexpected end of XML document.");
200      } else {
201        XMLEvent event = xmlReader.nextEvent();
202        if (event.getEventType() == XMLStreamConstants.CHARACTERS) {
203          Characters characters = (Characters) event;
204          if (!keepWhiteSpace)
205            if (characters.isIgnorableWhiteSpace()
206                || characters.isWhiteSpace()) {
207              continue;
208            }
209        }
210        this.prevEvent = this.presentEvent;
211        this.presentEvent = event;
212        break;
213      }
214    }
215  }
216
217  /**
218   * Reads the specified node type.
219   *
220   * @param nodeType Type of the node.
221   * @throws Exception the exception
222   */
223  public void read(XmlNodeType nodeType) throws Exception {
224    this.read();
225    if (!this.getNodeType().equals(nodeType)) {
226      throw new ServiceXmlDeserializationException(String
227          .format("The expected XML node type was %s, but the actual type is %s.", nodeType, this
228              .getNodeType()));
229    }
230  }
231
232  /**
233   * Read attribute value from QName.
234   *
235   * @param qName QName of the attribute
236   * @return Attribute Value
237   * @throws Exception thrown if attribute value can not be read
238   */
239  private String readAttributeValue(QName qName) throws Exception {
240    if (this.presentEvent.isStartElement()) {
241      StartElement startElement = this.presentEvent.asStartElement();
242      Attribute attr = startElement.getAttributeByName(qName);
243      if (null != attr) {
244        return attr.getValue();
245      } else {
246        return null;
247      }
248    } else {
249      String errMsg = String.format("Could not fetch attribute %s", qName
250          .toString());
251      throw new Exception(errMsg);
252    }
253  }
254
255  /**
256   * Reads the attribute value.
257   *
258   * @param xmlNamespace  The XML namespace.
259   * @param attributeName Name of the attribute
260   * @return Attribute Value
261   * @throws Exception the exception
262   */
263  public String readAttributeValue(XmlNamespace xmlNamespace,
264      String attributeName) throws Exception {
265    if (xmlNamespace == XmlNamespace.NotSpecified) {
266      return this.readAttributeValue(attributeName);
267    } else {
268      QName qName = new QName(EwsUtilities.getNamespaceUri(xmlNamespace),
269          attributeName);
270      return readAttributeValue(qName);
271    }
272  }
273
274  /**
275   * Reads the attribute value.
276   *
277   * @param attributeName Name of the attribute
278   * @return Attribute value.
279   * @throws Exception the exception
280   */
281  public String readAttributeValue(String attributeName) throws Exception {
282    QName qName = new QName(attributeName);
283    return readAttributeValue(qName);
284  }
285
286  /**
287   * Reads the attribute value.
288   *
289   * @param <T>           the generic type
290   * @param cls           the cls
291   * @param attributeName the attribute name
292   * @return T
293   * @throws Exception the exception
294   */
295  public <T> T readAttributeValue(Class<T> cls, String attributeName)
296      throws Exception {
297    return EwsUtilities.parse(cls, this.readAttributeValue(attributeName));
298  }
299
300  /**
301   * Reads a nullable attribute value.
302   *
303   * @param <T>           the generic type
304   * @param cls           the cls
305   * @param attributeName the attribute name
306   * @return T
307   * @throws Exception the exception
308   */
309  public <T> T readNullableAttributeValue(Class<T> cls, String attributeName)
310      throws Exception {
311    String attributeValue = this.readAttributeValue(attributeName);
312    if (attributeValue == null) {
313      return null;
314    } else {
315      return EwsUtilities.parse(cls, attributeValue);
316    }
317  }
318
319  /**
320   * Reads the element value.
321   *
322   * @param namespacePrefix the namespace prefix
323   * @param localName       the local name
324   * @return String
325   * @throws Exception the exception
326   */
327  public String readElementValue(String namespacePrefix, String localName)
328      throws Exception {
329    if (!this.isStartElement(namespacePrefix, localName)) {
330      this.readStartElement(namespacePrefix, localName);
331    }
332
333    String value = null;
334
335    if (!this.isEmptyElement()) {
336      value = this.readValue();
337    }
338    return value;
339  }
340
341  /**
342   * Reads the element value.
343   *
344   * @param xmlNamespace the xml namespace
345   * @param localName    the local name
346   * @return String
347   * @throws Exception the exception
348   */
349  public String readElementValue(XmlNamespace xmlNamespace, String localName)
350      throws Exception {
351
352    if (!this.isStartElement(xmlNamespace, localName)) {
353      this.readStartElement(xmlNamespace, localName);
354    }
355
356    String value = null;
357
358    if (!this.isEmptyElement()) {
359      value = this.readValue();
360    } else {
361      this.read();
362    }
363
364    return value;
365  }
366
367  /**
368   * Read element value.
369   *
370   * @return String
371   * @throws Exception the exception
372   */
373  public String readElementValue() throws Exception {
374    this.ensureCurrentNodeIsStartElement();
375
376    return this.readElementValue(this.getNamespacePrefix(), this
377        .getLocalName());
378  }
379
380  /**
381   * Reads the element value.
382   *
383   * @param <T>          the generic type
384   * @param cls          the cls
385   * @param xmlNamespace the xml namespace
386   * @param localName    the local name
387   * @return T
388   * @throws Exception the exception
389   */
390  public <T> T readElementValue(Class<T> cls, XmlNamespace xmlNamespace,
391      String localName) throws Exception {
392    if (!this.isStartElement(xmlNamespace, localName)) {
393      this.readStartElement(xmlNamespace, localName);
394    }
395
396    T value = null;
397
398    if (!this.isEmptyElement()) {
399      value = this.readValue(cls);
400    }
401
402    return value;
403  }
404
405  /**
406   * Read element value.
407   *
408   * @param <T> the generic type
409   * @param cls the cls
410   * @return T
411   * @throws Exception the exception
412   */
413  public <T> T readElementValue(Class<T> cls) throws Exception {
414    this.ensureCurrentNodeIsStartElement();
415
416    T value = null;
417
418    if (!this.isEmptyElement()) {
419      value = this.readValue(cls);
420    }
421
422    return value;
423  }
424
425  /**
426   * Reads the value. Should return content element or text node as string
427   * Present event must be START ELEMENT. After executing this function
428   * Present event will be set on END ELEMENT
429   *
430   * @return String
431   * @throws XMLStreamException the XML stream exception
432   * @throws ServiceXmlDeserializationException the service xml deserialization exception
433   */
434  public String readValue() throws XMLStreamException,
435      ServiceXmlDeserializationException {
436    return readValue(false);
437  }
438
439  /**
440   * Reads the value. Should return content element or text node as string
441   * Present event must be START ELEMENT. After executing this function
442   * Present event will be set on END ELEMENT
443   *
444   * @param keepWhiteSpace Do not remove whitespace characters if true
445   * @return String
446   * @throws XMLStreamException the XML stream exception
447   * @throws ServiceXmlDeserializationException the service xml deserialization exception
448   */
449  public String readValue(boolean keepWhiteSpace) throws XMLStreamException,
450      ServiceXmlDeserializationException {
451    if (this.presentEvent.isStartElement()) {
452      // Go to next event and check for Characters event
453      this.read(keepWhiteSpace);
454      if (this.presentEvent.isCharacters()) {
455        final StringBuilder elementValue = new StringBuilder();
456        do {
457          if (this.getNodeType().nodeType == XmlNodeType.CHARACTERS) {
458            Characters characters = (Characters) this.presentEvent;
459            if (keepWhiteSpace || (!characters.isIgnorableWhiteSpace()
460                && !characters.isWhiteSpace())) {
461              final String charactersData = characters.getData();
462              if (charactersData != null && !charactersData.isEmpty()) {
463                elementValue.append(charactersData);
464              }
465            }
466          }
467          this.read();
468        } while (!this.presentEvent.isEndElement());
469        // Characters chars = this.presentEvent.asCharacters();
470        // String elementValue = chars.getData();
471        // Advance to next event post Characters (ideally it will be End
472        // Element)
473        // this.read();
474        return elementValue.toString();
475      } else if (this.presentEvent.isEndElement()) {
476        return "";
477      } else {
478        throw new ServiceXmlDeserializationException(
479            getReadValueErrMsg("Could not find " + XmlNodeType.getString(XmlNodeType.CHARACTERS)));
480      }
481    } else if (this.presentEvent.getEventType() == XmlNodeType.CHARACTERS
482        && this.presentEvent.isCharacters()) {
483                        /*
484                         * if(this.presentEvent.asCharacters().getData().equals("<")) {
485                         */
486      final String charData = this.presentEvent.asCharacters().getData();
487      final StringBuilder data = new StringBuilder(charData == null ? "" : charData);
488      do {
489        this.read(keepWhiteSpace);
490        if (this.getNodeType().nodeType == XmlNodeType.CHARACTERS) {
491          Characters characters = (Characters) this.presentEvent;
492          if (keepWhiteSpace || (!characters.isIgnorableWhiteSpace()
493              && !characters.isWhiteSpace())) {
494            final String charactersData = characters.getData();
495            if (charactersData != null && !charactersData.isEmpty()) {
496              data.append(charactersData);
497            }
498          }
499        }
500      } while (!this.presentEvent.isEndElement());
501      return data.toString();// this.presentEvent. = new XMLEvent();
502                        /*
503                         * } else { Characters chars = this.presentEvent.asCharacters();
504                         * String elementValue = chars.getData(); // Advance to next event
505                         * post Characters (ideally it will be End // Element) this.read();
506                         * return elementValue; }
507                         */
508    } else {
509      throw new ServiceXmlDeserializationException(
510        getReadValueErrMsg("Expected is " + XmlNodeType.getString(XmlNodeType.START_ELEMENT))
511      );
512    }
513
514  }
515
516  /**
517   * Tries to read value.
518   *
519   * @param value the value
520   * @return boolean
521   * @throws XMLStreamException the XML stream exception
522   * @throws ServiceXmlDeserializationException  the service xml deserialization exception
523   */
524  public boolean tryReadValue(OutParam<String> value)
525      throws XMLStreamException, ServiceXmlDeserializationException {
526    if (!this.isEmptyElement()) {
527      this.read();
528
529      if (this.presentEvent.isCharacters()) {
530        value.setParam(this.readValue());
531        return true;
532      } else {
533        return false;
534      }
535    } else {
536      return false;
537    }
538  }
539
540  /**
541   * Reads the value.
542   *
543   * @param <T> the generic type
544   * @param cls the cls
545   * @return T
546   * @throws Exception the exception
547   */
548  public <T> T readValue(Class<T> cls) throws Exception {
549    return EwsUtilities.parse(cls, this.readValue());
550  }
551
552  /**
553   * Reads the base64 element value.
554   *
555   * @return byte[]
556   * @throws ServiceXmlDeserializationException the service xml deserialization exception
557   * @throws XMLStreamException the XML stream exception
558   * @throws IOException signals that an I/O exception has occurred
559   */
560  public byte[] readBase64ElementValue()
561      throws ServiceXmlDeserializationException, XMLStreamException,
562      IOException {
563    this.ensureCurrentNodeIsStartElement();
564    return Base64.decodeBase64(this.xmlReader.getElementText());
565  }
566
567  /**
568   * Reads the base64 element value.
569   *
570   * @param outputStream the output stream
571   * @throws Exception the exception
572   */
573  public void readBase64ElementValue(OutputStream outputStream)
574      throws Exception {
575    this.ensureCurrentNodeIsStartElement();
576
577    byte[] buffer = Base64.decodeBase64(this.xmlReader.getElementText());
578    outputStream.write(buffer);
579    outputStream.flush();
580  }
581
582  /**
583   * Reads the start element.
584   *
585   * @param namespacePrefix the namespace prefix
586   * @param localName       the local name
587   * @throws Exception the exception
588   */
589  public void readStartElement(String namespacePrefix, String localName)
590      throws Exception {
591    this.internalReadElement(namespacePrefix, localName, new XmlNodeType(
592        XmlNodeType.START_ELEMENT));
593  }
594
595  /**
596   * Reads the start element.
597   *
598   * @param xmlNamespace the xml namespace
599   * @param localName    the local name
600   * @throws Exception the exception
601   */
602  public void readStartElement(XmlNamespace xmlNamespace, String localName)
603      throws Exception {
604    this.internalReadElement(xmlNamespace, localName, new XmlNodeType(
605        XmlNodeType.START_ELEMENT));
606  }
607
608  /**
609   * Reads the end element.
610   *
611   * @param namespacePrefix the namespace prefix
612   * @param elementName     the element name
613   * @throws Exception the exception
614   */
615  public void readEndElement(String namespacePrefix, String elementName)
616      throws Exception {
617    this.internalReadElement(namespacePrefix, elementName, new XmlNodeType(
618        XmlNodeType.END_ELEMENT));
619  }
620
621  /**
622   * Reads the end element.
623   *
624   * @param xmlNamespace the xml namespace
625   * @param localName    the local name
626   * @throws Exception the exception
627   */
628  public void readEndElement(XmlNamespace xmlNamespace, String localName)
629      throws Exception {
630
631    this.internalReadElement(xmlNamespace, localName, new XmlNodeType(
632        XmlNodeType.END_ELEMENT));
633
634  }
635
636  /**
637   * Reads the end element if necessary.
638   *
639   * @param xmlNamespace the xml namespace
640   * @param localName    the local name
641   * @throws Exception the exception
642   */
643  public void readEndElementIfNecessary(XmlNamespace xmlNamespace,
644      String localName) throws Exception {
645
646    if (!(this.isStartElement(xmlNamespace, localName) && this
647        .isEmptyElement())) {
648      if (!this.isEndElement(xmlNamespace, localName)) {
649        this.readEndElement(xmlNamespace, localName);
650      }
651    }
652  }
653
654  /**
655   * Determines whether current element is a start element.
656   *
657   * @return boolean
658   */
659  public boolean isStartElement() {
660    return this.presentEvent.isStartElement();
661  }
662
663  /**
664   * Determines whether current element is a start element.
665   *
666   * @param namespacePrefix the namespace prefix
667   * @param localName       the local name
668   * @return boolean
669   */
670  public boolean isStartElement(String namespacePrefix, String localName) {
671    boolean isStart = false;
672    if (this.presentEvent.isStartElement()) {
673      StartElement startElement = this.presentEvent.asStartElement();
674      QName qName = startElement.getName();
675      isStart = qName.getLocalPart().equals(localName)
676          && qName.getPrefix().equals(namespacePrefix);
677    }
678    return isStart;
679  }
680
681  /**
682   * Determines whether current element is a start element.
683   *
684   * @param xmlNamespace the xml namespace
685   * @param localName    the local name
686   * @return true for matching start element; false otherwise.
687   */
688  public boolean isStartElement(XmlNamespace xmlNamespace, String localName) {
689    return this.isStartElement()
690      && StringUtils.equals(getLocalName(), localName)
691      && (
692         StringUtils.equals(getNamespacePrefix(), EwsUtilities.getNamespacePrefix(xmlNamespace)) ||
693         StringUtils.equals(getNamespaceUri(), EwsUtilities.getNamespaceUri(xmlNamespace)));
694  }
695
696  /**
697   * Determines whether current element is a end element.
698   *
699   * @param namespacePrefix the namespace prefix
700   * @param localName       the local name
701   * @return boolean
702   */
703  public boolean isEndElement(String namespacePrefix, String localName) {
704    boolean isEndElement = false;
705    if (this.presentEvent.isEndElement()) {
706      EndElement endElement = this.presentEvent.asEndElement();
707      QName qName = endElement.getName();
708      isEndElement = qName.getLocalPart().equals(localName)
709          && qName.getPrefix().equals(namespacePrefix);
710
711    }
712    return isEndElement;
713  }
714
715  /**
716   * Determines whether current element is a end element.
717   *
718   * @param xmlNamespace the xml namespace
719   * @param localName    the local name
720   * @return boolean
721   */
722  public boolean isEndElement(XmlNamespace xmlNamespace, String localName) {
723
724    boolean isEndElement = false;
725                /*
726                 * if(localName.equals("Body")) { return true; } else
727                 */
728    if (this.presentEvent.isEndElement()) {
729      EndElement endElement = this.presentEvent.asEndElement();
730      QName qName = endElement.getName();
731      isEndElement = qName.getLocalPart().equals(localName)
732          && (qName.getPrefix().equals(
733          EwsUtilities.getNamespacePrefix(xmlNamespace)) ||
734          qName.getNamespaceURI().equals(
735              EwsUtilities.getNamespaceUri(
736                  xmlNamespace)));
737
738    }
739    return isEndElement;
740  }
741
742  /**
743   * Skips the element.
744   *
745   * @param namespacePrefix the namespace prefix
746   * @param localName       the local name
747   * @throws Exception the exception
748   */
749  public void skipElement(String namespacePrefix, String localName)
750      throws Exception {
751    if (!this.isEndElement(namespacePrefix, localName)) {
752      if (!this.isStartElement(namespacePrefix, localName)) {
753        this.readStartElement(namespacePrefix, localName);
754      }
755
756      if (!this.isEmptyElement()) {
757        do {
758          this.read();
759        } while (!this.isEndElement(namespacePrefix, localName));
760      }
761    }
762  }
763
764  /**
765   * Skips the element.
766   *
767   * @param xmlNamespace the xml namespace
768   * @param localName    the local name
769   * @throws Exception the exception
770   */
771  public void skipElement(XmlNamespace xmlNamespace, String localName)
772      throws Exception {
773    if (!this.isEndElement(xmlNamespace, localName)) {
774      if (!this.isStartElement(xmlNamespace, localName)) {
775        this.readStartElement(xmlNamespace, localName);
776      }
777
778      if (!this.isEmptyElement()) {
779        do {
780          this.read();
781        } while (!this.isEndElement(xmlNamespace, localName));
782      }
783    }
784  }
785
786  /**
787   * Skips the current element.
788   *
789   * @throws Exception the exception
790   */
791  public void skipCurrentElement() throws Exception {
792    this.skipElement(this.getNamespacePrefix(), this.getLocalName());
793  }
794
795  /**
796   * Ensures the current node is start element.
797   *
798   * @param xmlNamespace the xml namespace
799   * @param localName    the local name
800   * @throws ServiceXmlDeserializationException the service xml deserialization exception
801   */
802  public void ensureCurrentNodeIsStartElement(XmlNamespace xmlNamespace,
803      String localName) throws ServiceXmlDeserializationException {
804
805    if (!this.isStartElement(xmlNamespace, localName)) {
806      throw new ServiceXmlDeserializationException(
807          String
808              .format("The element '%s' in namespace '%s' wasn't found at the current position.",
809                  localName, xmlNamespace));
810    }
811  }
812
813  /**
814   * Ensures the current node is start element.
815   *
816   * @throws ServiceXmlDeserializationException the service xml deserialization exception
817   */
818  public void ensureCurrentNodeIsStartElement()
819      throws ServiceXmlDeserializationException {
820    XmlNodeType presentNodeType = new XmlNodeType(this.presentEvent
821        .getEventType());
822    if (!this.presentEvent.isStartElement()) {
823      throw new ServiceXmlDeserializationException(String.format(
824          "The start element was expected, but node '%s' of type %s was found.",
825          this.presentEvent.toString(), presentNodeType.toString()));
826    }
827  }
828
829  /**
830   * Ensures the current node is start element.
831   *
832   * @param xmlNamespace the xml namespace
833   * @param localName    the local name
834   * @throws Exception the exception
835   */
836  public void ensureCurrentNodeIsEndElement(XmlNamespace xmlNamespace,
837      String localName) throws Exception {
838    if (!this.isEndElement(xmlNamespace, localName)) {
839      if (!(this.isStartElement(xmlNamespace, localName) && this
840          .isEmptyElement())) {
841        throw new ServiceXmlDeserializationException(
842            String
843                .format("The element '%s' in namespace '%s' wasn't found at the current position.",
844                    xmlNamespace, localName));
845      }
846    }
847  }
848
849  /**
850   * Outer XML as string.
851   *
852   * @return String
853   * @throws ServiceXmlDeserializationException the service xml deserialization exception
854   * @throws XMLStreamException the XML stream exception
855   */
856  public String readOuterXml() throws ServiceXmlDeserializationException,
857      XMLStreamException {
858    if (!this.isStartElement()) {
859      throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
860    }
861
862    XMLEvent startEvent = this.presentEvent;
863    XMLEvent event;
864    StringBuilder str = new StringBuilder();
865    str.append(startEvent);
866    do {
867      event = this.xmlReader.nextEvent();
868      str.append(event);
869    } while (!checkEndElement(startEvent, event));
870
871    return str.toString();
872  }
873
874  /**
875   * Reads the Inner XML at the given location.
876   *
877   * @return String
878   * @throws ServiceXmlDeserializationException the service xml deserialization exception
879   * @throws XMLStreamException the XML stream exception
880   */
881  public String readInnerXml() throws ServiceXmlDeserializationException,
882      XMLStreamException {
883    if (!this.isStartElement()) {
884      throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
885    }
886
887    XMLEvent startEvent = this.presentEvent;
888    StringBuilder str = new StringBuilder();
889    do {
890      XMLEvent event = this.xmlReader.nextEvent();
891      if (checkEndElement(startEvent, event)) {
892        break;
893      }
894      str.append(event);
895    } while (true);
896
897    return str.toString();
898  }
899
900  /**
901   * Check end element.
902   *
903   * @param startEvent the start event
904   * @param endEvent   the end event
905   * @return true, if successful
906   */
907  public static boolean checkEndElement(XMLEvent startEvent, XMLEvent endEvent) {
908    boolean isEndElement = false;
909    if (endEvent.isEndElement()) {
910      QName qEName = endEvent.asEndElement().getName();
911      QName qSName = startEvent.asStartElement().getName();
912      isEndElement = qEName.getLocalPart().equals(qSName.getLocalPart())
913          && (qEName.getPrefix().equals(qSName.getPrefix()) || qEName
914          .getNamespaceURI().equals(qSName.
915              getNamespaceURI()));
916
917    }
918    return isEndElement;
919  }
920
921  /**
922   * Gets the XML reader for node.
923   *
924   * @return null
925   * @throws XMLStreamException the XML stream exception
926   * @throws ServiceXmlDeserializationException the service xml deserialization exception
927   * @throws FileNotFoundException the file not found exception
928   */
929  public XMLEventReader getXmlReaderForNode()
930      throws FileNotFoundException, ServiceXmlDeserializationException, XMLStreamException {
931    return readSubtree();
932  }
933
934  public XMLEventReader readSubtree()
935      throws XMLStreamException, FileNotFoundException, ServiceXmlDeserializationException {
936
937    if (!this.isStartElement()) {
938      throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
939    }
940
941    XMLEventReader eventReader = null;
942    InputStream in = null;
943    XMLEvent startEvent = this.presentEvent;
944    XMLEvent event = startEvent;
945    StringBuilder str = new StringBuilder();
946    str.append(startEvent);
947    do {
948      event = this.xmlReader.nextEvent();
949      str.append(event);
950    } while (!checkEndElement(startEvent, event));
951
952    try {
953
954      XMLInputFactory inputFactory = XMLInputFactory.newInstance();
955
956      try {
957        in = new ByteArrayInputStream(str.toString().getBytes("UTF-8"));
958      } catch (UnsupportedEncodingException e) {
959        LOG.error(e);
960      }
961      eventReader = inputFactory.createXMLEventReader(in);
962
963    } catch (Exception e) {
964      LOG.error(e);
965    }
966    return eventReader;
967  }
968
969  /**
970   * Reads to the next descendant element with the specified local name and
971   * namespace.
972   *
973   * @param xmlNamespace The namespace of the element you with to move to.
974   * @param localName    The local name of the element you wish to move to.
975   * @throws XMLStreamException the XML stream exception
976   */
977  public void readToDescendant(XmlNamespace xmlNamespace, String localName) throws XMLStreamException {
978    readToDescendant(localName, EwsUtilities.getNamespaceUri(xmlNamespace));
979  }
980
981  public boolean readToDescendant(String localName, String namespaceURI) throws XMLStreamException {
982
983    if (!this.isStartElement()) {
984      return false;
985    }
986    XMLEvent startEvent = this.presentEvent;
987    XMLEvent event = this.presentEvent;
988    do {
989      if (event.isStartElement()) {
990        QName qEName = event.asStartElement().getName();
991        if (qEName.getLocalPart().equals(localName) &&
992            qEName.getNamespaceURI().equals(namespaceURI)) {
993          return true;
994        }
995      }
996      event = this.xmlReader.nextEvent();
997    } while (!checkEndElement(startEvent, event));
998
999    return false;
1000  }
1001
1002
1003
1004  /**
1005   * Gets a value indicating whether this instance has attribute.
1006   *
1007   * @return boolean
1008   */
1009  public boolean hasAttributes() {
1010
1011    if (this.presentEvent.isStartElement()) {
1012      StartElement startElement = this.presentEvent.asStartElement();
1013      return startElement.getAttributes().hasNext();
1014    } else {
1015      return false;
1016    }
1017  }
1018
1019  /**
1020   * Gets a value indicating whether current element is empty.
1021   *
1022   * @return boolean
1023   * @throws XMLStreamException the XML stream exception
1024   */
1025  public boolean isEmptyElement() throws XMLStreamException {
1026    boolean isPresentStartElement = this.presentEvent.isStartElement();
1027    boolean isNextEndElement = this.xmlReader.peek().isEndElement();
1028    return isPresentStartElement && isNextEndElement;
1029  }
1030
1031  /**
1032   * Gets the local name of the current element.
1033   *
1034   * @return String
1035   */
1036  public String getLocalName() {
1037
1038    String localName = null;
1039
1040    if (this.presentEvent.isStartElement()) {
1041      localName = this.presentEvent.asStartElement().getName()
1042          .getLocalPart();
1043    } else {
1044
1045      localName = this.presentEvent.asEndElement().getName()
1046          .getLocalPart();
1047    }
1048    return localName;
1049  }
1050
1051  /**
1052   * Gets the namespace prefix.
1053   *
1054   * @return String
1055   */
1056  protected String getNamespacePrefix() {
1057    if (this.presentEvent.isStartElement()) {
1058      return this.presentEvent.asStartElement().getName().getPrefix();
1059    }
1060    if (this.presentEvent.isEndElement()) {
1061      return this.presentEvent.asEndElement().getName().getPrefix();
1062    }
1063    return null;
1064  }
1065
1066  /**
1067   * Gets the namespace URI.
1068   *
1069   * @return String
1070   */
1071  public String getNamespaceUri() {
1072
1073    String nameSpaceUri = null;
1074    if (this.presentEvent.isStartElement()) {
1075      nameSpaceUri = this.presentEvent.asStartElement().getName()
1076          .getNamespaceURI();
1077    } else {
1078
1079      nameSpaceUri = this.presentEvent.asEndElement().getName()
1080          .getNamespaceURI();
1081    }
1082    return nameSpaceUri;
1083  }
1084
1085  /**
1086   * Gets the type of the node.
1087   *
1088   * @return XmlNodeType
1089   * @throws XMLStreamException the XML stream exception
1090   */
1091  public XmlNodeType getNodeType() throws XMLStreamException {
1092    XMLEvent event = this.presentEvent;
1093    return new XmlNodeType(event.getEventType());
1094  }
1095
1096  /**
1097   * Gets the name of the current element.
1098   *
1099   * @return Object
1100   */
1101  protected Object getName() {
1102    String name = null;
1103    if (this.presentEvent.isStartElement()) {
1104      name = this.presentEvent.asStartElement().getName().toString();
1105    } else {
1106
1107      name = this.presentEvent.asEndElement().getName().toString();
1108    }
1109    return name;
1110  }
1111
1112  /**
1113   * Checks is the string is null or empty.
1114   *
1115   * @param namespacePrefix the namespace prefix
1116   * @return true, if is null or empty
1117   */
1118  private static boolean isNullOrEmpty(String namespacePrefix) {
1119    return (namespacePrefix == null || namespacePrefix.isEmpty());
1120
1121  }
1122
1123  /**
1124   * Gets the error message which happened during {@link #readValue()}.
1125   *
1126   * @param details details message
1127   * @return error message with details
1128   */
1129  private String getReadValueErrMsg(final String details) {
1130    final int eventType = this.presentEvent.getEventType();
1131    return "Could not read value from " + XmlNodeType.getString(eventType) + "." + details;
1132  }
1133
1134}