001    /*
002     * The MIT License
003     * Copyright (c) 2012 Microsoft Corporation
004     *
005     * Permission is hereby granted, free of charge, to any person obtaining a copy
006     * of this software and associated documentation files (the "Software"), to deal
007     * in the Software without restriction, including without limitation the rights
008     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
009     * copies of the Software, and to permit persons to whom the Software is
010     * furnished to do so, subject to the following conditions:
011     *
012     * The above copyright notice and this permission notice shall be included in
013     * all copies or substantial portions of the Software.
014     *
015     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
016     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
017     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
018     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
019     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
020     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
021     * THE SOFTWARE.
022     */
023    
024    package microsoft.exchange.webservices.data.core;
025    
026    import microsoft.exchange.webservices.data.core.enumeration.misc.XmlNamespace;
027    import microsoft.exchange.webservices.data.core.exception.service.local.ServiceXmlDeserializationException;
028    import microsoft.exchange.webservices.data.misc.OutParam;
029    import microsoft.exchange.webservices.data.security.XmlNodeType;
030    import org.apache.commons.codec.binary.Base64;
031    import org.apache.commons.lang3.StringUtils;
032    import org.apache.commons.logging.Log;
033    import org.apache.commons.logging.LogFactory;
034    
035    import javax.xml.namespace.QName;
036    import javax.xml.stream.XMLEventReader;
037    import javax.xml.stream.XMLInputFactory;
038    import javax.xml.stream.XMLStreamConstants;
039    import javax.xml.stream.XMLStreamException;
040    import javax.xml.stream.events.Attribute;
041    import javax.xml.stream.events.Characters;
042    import javax.xml.stream.events.EndElement;
043    import javax.xml.stream.events.StartElement;
044    import javax.xml.stream.events.XMLEvent;
045    
046    import java.io.ByteArrayInputStream;
047    import java.io.ByteArrayOutputStream;
048    import java.io.FileNotFoundException;
049    import java.io.IOException;
050    import java.io.InputStream;
051    import java.io.OutputStream;
052    import java.io.UnsupportedEncodingException;
053    
054    /**
055     * Defines the EwsXmlReader class.
056     */
057    public 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    
565        byte[] buffer = null;
566    
567        ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream();
568    
569        buffer = Base64.decodeBase64(this.xmlReader.getElementText().toString());
570        byteArrayStream.write(buffer);
571    
572        return byteArrayStream.toByteArray();
573    
574      }
575    
576      /**
577       * Reads the base64 element value.
578       *
579       * @param outputStream the output stream
580       * @throws Exception the exception
581       */
582      public void readBase64ElementValue(OutputStream outputStream)
583          throws Exception {
584        this.ensureCurrentNodeIsStartElement();
585    
586        byte[] buffer = null;
587        buffer = Base64.decodeBase64(this.xmlReader.getElementText().toString());
588        outputStream.write(buffer);
589        outputStream.flush();
590      }
591    
592      /**
593       * Reads the start element.
594       *
595       * @param namespacePrefix the namespace prefix
596       * @param localName       the local name
597       * @throws Exception the exception
598       */
599      public void readStartElement(String namespacePrefix, String localName)
600          throws Exception {
601        this.internalReadElement(namespacePrefix, localName, new XmlNodeType(
602            XmlNodeType.START_ELEMENT));
603      }
604    
605      /**
606       * Reads the start element.
607       *
608       * @param xmlNamespace the xml namespace
609       * @param localName    the local name
610       * @throws Exception the exception
611       */
612      public void readStartElement(XmlNamespace xmlNamespace, String localName)
613          throws Exception {
614        this.internalReadElement(xmlNamespace, localName, new XmlNodeType(
615            XmlNodeType.START_ELEMENT));
616      }
617    
618      /**
619       * Reads the end element.
620       *
621       * @param namespacePrefix the namespace prefix
622       * @param elementName     the element name
623       * @throws Exception the exception
624       */
625      public void readEndElement(String namespacePrefix, String elementName)
626          throws Exception {
627        this.internalReadElement(namespacePrefix, elementName, new XmlNodeType(
628            XmlNodeType.END_ELEMENT));
629      }
630    
631      /**
632       * Reads the end element.
633       *
634       * @param xmlNamespace the xml namespace
635       * @param localName    the local name
636       * @throws Exception the exception
637       */
638      public void readEndElement(XmlNamespace xmlNamespace, String localName)
639          throws Exception {
640    
641        this.internalReadElement(xmlNamespace, localName, new XmlNodeType(
642            XmlNodeType.END_ELEMENT));
643    
644      }
645    
646      /**
647       * Reads the end element if necessary.
648       *
649       * @param xmlNamespace the xml namespace
650       * @param localName    the local name
651       * @throws Exception the exception
652       */
653      public void readEndElementIfNecessary(XmlNamespace xmlNamespace,
654          String localName) throws Exception {
655    
656        if (!(this.isStartElement(xmlNamespace, localName) && this
657            .isEmptyElement())) {
658          if (!this.isEndElement(xmlNamespace, localName)) {
659            this.readEndElement(xmlNamespace, localName);
660          }
661        }
662      }
663    
664      /**
665       * Determines whether current element is a start element.
666       *
667       * @return boolean
668       */
669      public boolean isStartElement() {
670        return this.presentEvent.isStartElement();
671      }
672    
673      /**
674       * Determines whether current element is a start element.
675       *
676       * @param namespacePrefix the namespace prefix
677       * @param localName       the local name
678       * @return boolean
679       */
680      public boolean isStartElement(String namespacePrefix, String localName) {
681        boolean isStart = false;
682        if (this.presentEvent.isStartElement()) {
683          StartElement startElement = this.presentEvent.asStartElement();
684          QName qName = startElement.getName();
685          isStart = qName.getLocalPart().equals(localName)
686              && qName.getPrefix().equals(namespacePrefix);
687        }
688        return isStart;
689      }
690    
691      /**
692       * Determines whether current element is a start element.
693       *
694       * @param xmlNamespace the xml namespace
695       * @param localName    the local name
696       * @return true for matching start element; false otherwise.
697       */
698      public boolean isStartElement(XmlNamespace xmlNamespace, String localName) {
699        return this.isStartElement()
700          && StringUtils.equals(getLocalName(), localName)
701          && (
702             StringUtils.equals(getNamespacePrefix(), EwsUtilities.getNamespacePrefix(xmlNamespace)) ||
703             StringUtils.equals(getNamespaceUri(), EwsUtilities.getNamespaceUri(xmlNamespace)));
704      }
705    
706      /**
707       * Determines whether current element is a end element.
708       *
709       * @param namespacePrefix the namespace prefix
710       * @param localName       the local name
711       * @return boolean
712       */
713      public boolean isEndElement(String namespacePrefix, String localName) {
714        boolean isEndElement = false;
715        if (this.presentEvent.isEndElement()) {
716          EndElement endElement = this.presentEvent.asEndElement();
717          QName qName = endElement.getName();
718          isEndElement = qName.getLocalPart().equals(localName)
719              && qName.getPrefix().equals(namespacePrefix);
720    
721        }
722        return isEndElement;
723      }
724    
725      /**
726       * Determines whether current element is a end element.
727       *
728       * @param xmlNamespace the xml namespace
729       * @param localName    the local name
730       * @return boolean
731       */
732      public boolean isEndElement(XmlNamespace xmlNamespace, String localName) {
733    
734        boolean isEndElement = false;
735                    /*
736                     * if(localName.equals("Body")) { return true; } else
737                     */
738        if (this.presentEvent.isEndElement()) {
739          EndElement endElement = this.presentEvent.asEndElement();
740          QName qName = endElement.getName();
741          isEndElement = qName.getLocalPart().equals(localName)
742              && (qName.getPrefix().equals(
743              EwsUtilities.getNamespacePrefix(xmlNamespace)) ||
744              qName.getNamespaceURI().equals(
745                  EwsUtilities.getNamespaceUri(
746                      xmlNamespace)));
747    
748        }
749        return isEndElement;
750      }
751    
752      /**
753       * Skips the element.
754       *
755       * @param namespacePrefix the namespace prefix
756       * @param localName       the local name
757       * @throws Exception the exception
758       */
759      public void skipElement(String namespacePrefix, String localName)
760          throws Exception {
761        if (!this.isEndElement(namespacePrefix, localName)) {
762          if (!this.isStartElement(namespacePrefix, localName)) {
763            this.readStartElement(namespacePrefix, localName);
764          }
765    
766          if (!this.isEmptyElement()) {
767            do {
768              this.read();
769            } while (!this.isEndElement(namespacePrefix, localName));
770          }
771        }
772      }
773    
774      /**
775       * Skips the element.
776       *
777       * @param xmlNamespace the xml namespace
778       * @param localName    the local name
779       * @throws Exception the exception
780       */
781      public void skipElement(XmlNamespace xmlNamespace, String localName)
782          throws Exception {
783        if (!this.isEndElement(xmlNamespace, localName)) {
784          if (!this.isStartElement(xmlNamespace, localName)) {
785            this.readStartElement(xmlNamespace, localName);
786          }
787    
788          if (!this.isEmptyElement()) {
789            do {
790              this.read();
791            } while (!this.isEndElement(xmlNamespace, localName));
792          }
793        }
794      }
795    
796      /**
797       * Skips the current element.
798       *
799       * @throws Exception the exception
800       */
801      public void skipCurrentElement() throws Exception {
802        this.skipElement(this.getNamespacePrefix(), this.getLocalName());
803      }
804    
805      /**
806       * Ensures the current node is start element.
807       *
808       * @param xmlNamespace the xml namespace
809       * @param localName    the local name
810       * @throws ServiceXmlDeserializationException the service xml deserialization exception
811       */
812      public void ensureCurrentNodeIsStartElement(XmlNamespace xmlNamespace,
813          String localName) throws ServiceXmlDeserializationException {
814    
815        if (!this.isStartElement(xmlNamespace, localName)) {
816          throw new ServiceXmlDeserializationException(
817              String
818                  .format("The element '%s' in namespace '%s' wasn't found at the current position.",
819                      localName, xmlNamespace));
820        }
821      }
822    
823      /**
824       * Ensures the current node is start element.
825       *
826       * @throws ServiceXmlDeserializationException the service xml deserialization exception
827       */
828      public void ensureCurrentNodeIsStartElement()
829          throws ServiceXmlDeserializationException {
830        XmlNodeType presentNodeType = new XmlNodeType(this.presentEvent
831            .getEventType());
832        if (!this.presentEvent.isStartElement()) {
833          throw new ServiceXmlDeserializationException(String.format(
834              "The start element was expected, but node '%s' of type %s was found.",
835              this.presentEvent.toString(), presentNodeType.toString()));
836        }
837      }
838    
839      /**
840       * Ensures the current node is start element.
841       *
842       * @param xmlNamespace the xml namespace
843       * @param localName    the local name
844       * @throws Exception the exception
845       */
846      public void ensureCurrentNodeIsEndElement(XmlNamespace xmlNamespace,
847          String localName) throws Exception {
848        if (!this.isEndElement(xmlNamespace, localName)) {
849          if (!(this.isStartElement(xmlNamespace, localName) && this
850              .isEmptyElement())) {
851            throw new ServiceXmlDeserializationException(
852                String
853                    .format("The element '%s' in namespace '%s' wasn't found at the current position.",
854                        xmlNamespace, localName));
855          }
856        }
857      }
858    
859      /**
860       * Outer XML as string.
861       *
862       * @return String
863       * @throws ServiceXmlDeserializationException the service xml deserialization exception
864       * @throws XMLStreamException the XML stream exception
865       */
866      public String readOuterXml() throws ServiceXmlDeserializationException,
867          XMLStreamException {
868        if (!this.isStartElement()) {
869          throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
870        }
871    
872        XMLEvent startEvent = this.presentEvent;
873        XMLEvent event;
874        StringBuilder str = new StringBuilder();
875        str.append(startEvent);
876        do {
877          event = this.xmlReader.nextEvent();
878          str.append(event);
879        } while (!checkEndElement(startEvent, event));
880    
881        return str.toString();
882      }
883    
884      /**
885       * Reads the Inner XML at the given location.
886       *
887       * @return String
888       * @throws ServiceXmlDeserializationException the service xml deserialization exception
889       * @throws XMLStreamException the XML stream exception
890       */
891      public String readInnerXml() throws ServiceXmlDeserializationException,
892          XMLStreamException {
893        if (!this.isStartElement()) {
894          throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
895        }
896    
897        XMLEvent startEvent = this.presentEvent;
898        StringBuilder str = new StringBuilder();
899        do {
900          XMLEvent event = this.xmlReader.nextEvent();
901          if (checkEndElement(startEvent, event)) {
902            break;
903          }
904          str.append(event);
905        } while (true);
906    
907        return str.toString();
908      }
909    
910      /**
911       * Check end element.
912       *
913       * @param startEvent the start event
914       * @param endEvent   the end event
915       * @return true, if successful
916       */
917      public static boolean checkEndElement(XMLEvent startEvent, XMLEvent endEvent) {
918        boolean isEndElement = false;
919        if (endEvent.isEndElement()) {
920          QName qEName = endEvent.asEndElement().getName();
921          QName qSName = startEvent.asStartElement().getName();
922          isEndElement = qEName.getLocalPart().equals(qSName.getLocalPart())
923              && (qEName.getPrefix().equals(qSName.getPrefix()) || qEName
924              .getNamespaceURI().equals(qSName.
925                  getNamespaceURI()));
926    
927        }
928        return isEndElement;
929      }
930    
931      /**
932       * Gets the XML reader for node.
933       *
934       * @return null
935       * @throws XMLStreamException the XML stream exception
936       * @throws ServiceXmlDeserializationException the service xml deserialization exception
937       * @throws FileNotFoundException the file not found exception
938       */
939      public XMLEventReader getXmlReaderForNode()
940          throws FileNotFoundException, ServiceXmlDeserializationException, XMLStreamException {
941        return readSubtree();
942      }
943    
944      public XMLEventReader readSubtree()
945          throws XMLStreamException, FileNotFoundException, ServiceXmlDeserializationException {
946    
947        if (!this.isStartElement()) {
948          throw new ServiceXmlDeserializationException("The current position is not the start of an element.");
949        }
950    
951        XMLEventReader eventReader = null;
952        InputStream in = null;
953        XMLEvent startEvent = this.presentEvent;
954        XMLEvent event = startEvent;
955        StringBuilder str = new StringBuilder();
956        str.append(startEvent);
957        do {
958          event = this.xmlReader.nextEvent();
959          str.append(event);
960        } while (!checkEndElement(startEvent, event));
961    
962        try {
963    
964          XMLInputFactory inputFactory = XMLInputFactory.newInstance();
965    
966          try {
967            in = new ByteArrayInputStream(str.toString().getBytes("UTF-8"));
968          } catch (UnsupportedEncodingException e) {
969            LOG.error(e);
970          }
971          eventReader = inputFactory.createXMLEventReader(in);
972    
973        } catch (Exception e) {
974          LOG.error(e);
975        }
976        return eventReader;
977      }
978    
979      /**
980       * Reads to the next descendant element with the specified local name and
981       * namespace.
982       *
983       * @param xmlNamespace The namespace of the element you with to move to.
984       * @param localName    The local name of the element you wish to move to.
985       * @throws XMLStreamException the XML stream exception
986       */
987      public void readToDescendant(XmlNamespace xmlNamespace, String localName) throws XMLStreamException {
988        readToDescendant(localName, EwsUtilities.getNamespaceUri(xmlNamespace));
989      }
990    
991      public boolean readToDescendant(String localName, String namespaceURI) throws XMLStreamException {
992    
993        if (!this.isStartElement()) {
994          return false;
995        }
996        XMLEvent startEvent = this.presentEvent;
997        XMLEvent event = this.presentEvent;
998        do {
999          if (event.isStartElement()) {
1000            QName qEName = event.asStartElement().getName();
1001            if (qEName.getLocalPart().equals(localName) &&
1002                qEName.getNamespaceURI().equals(namespaceURI)) {
1003              return true;
1004            }
1005          }
1006          event = this.xmlReader.nextEvent();
1007        } while (!checkEndElement(startEvent, event));
1008    
1009        return false;
1010      }
1011    
1012    
1013    
1014      /**
1015       * Gets a value indicating whether this instance has attribute.
1016       *
1017       * @return boolean
1018       */
1019      public boolean hasAttributes() {
1020    
1021        if (this.presentEvent.isStartElement()) {
1022          StartElement startElement = this.presentEvent.asStartElement();
1023          return startElement.getAttributes().hasNext();
1024        } else {
1025          return false;
1026        }
1027      }
1028    
1029      /**
1030       * Gets a value indicating whether current element is empty.
1031       *
1032       * @return boolean
1033       * @throws XMLStreamException the XML stream exception
1034       */
1035      public boolean isEmptyElement() throws XMLStreamException {
1036        boolean isPresentStartElement = this.presentEvent.isStartElement();
1037        boolean isNextEndElement = this.xmlReader.peek().isEndElement();
1038        return isPresentStartElement && isNextEndElement;
1039      }
1040    
1041      /**
1042       * Gets the local name of the current element.
1043       *
1044       * @return String
1045       */
1046      public String getLocalName() {
1047    
1048        String localName = null;
1049    
1050        if (this.presentEvent.isStartElement()) {
1051          localName = this.presentEvent.asStartElement().getName()
1052              .getLocalPart();
1053        } else {
1054    
1055          localName = this.presentEvent.asEndElement().getName()
1056              .getLocalPart();
1057        }
1058        return localName;
1059      }
1060    
1061      /**
1062       * Gets the namespace prefix.
1063       *
1064       * @return String
1065       */
1066      protected String getNamespacePrefix() {
1067        if (this.presentEvent.isStartElement()) {
1068          return this.presentEvent.asStartElement().getName().getPrefix();
1069        }
1070        if (this.presentEvent.isEndElement()) {
1071          return this.presentEvent.asEndElement().getName().getPrefix();
1072        }
1073        return null;
1074      }
1075    
1076      /**
1077       * Gets the namespace URI.
1078       *
1079       * @return String
1080       */
1081      public String getNamespaceUri() {
1082    
1083        String nameSpaceUri = null;
1084        if (this.presentEvent.isStartElement()) {
1085          nameSpaceUri = this.presentEvent.asStartElement().getName()
1086              .getNamespaceURI();
1087        } else {
1088    
1089          nameSpaceUri = this.presentEvent.asEndElement().getName()
1090              .getNamespaceURI();
1091        }
1092        return nameSpaceUri;
1093      }
1094    
1095      /**
1096       * Gets the type of the node.
1097       *
1098       * @return XmlNodeType
1099       * @throws XMLStreamException the XML stream exception
1100       */
1101      public XmlNodeType getNodeType() throws XMLStreamException {
1102        XMLEvent event = this.presentEvent;
1103        return new XmlNodeType(event.getEventType());
1104      }
1105    
1106      /**
1107       * Gets the name of the current element.
1108       *
1109       * @return Object
1110       */
1111      protected Object getName() {
1112        String name = null;
1113        if (this.presentEvent.isStartElement()) {
1114          name = this.presentEvent.asStartElement().getName().toString();
1115        } else {
1116    
1117          name = this.presentEvent.asEndElement().getName().toString();
1118        }
1119        return name;
1120      }
1121    
1122      /**
1123       * Checks is the string is null or empty.
1124       *
1125       * @param namespacePrefix the namespace prefix
1126       * @return true, if is null or empty
1127       */
1128      private static boolean isNullOrEmpty(String namespacePrefix) {
1129        return (namespacePrefix == null || namespacePrefix.isEmpty());
1130    
1131      }
1132    
1133      /**
1134       * Gets the error message which happened during {@link #readValue()}.
1135       *
1136       * @param details details message
1137       * @return error message with details
1138       */
1139      private String getReadValueErrMsg(final String details) {
1140        final int eventType = this.presentEvent.getEventType();
1141        return "Could not read value from " + XmlNodeType.getString(eventType) + "." + details;
1142      }
1143    
1144    }