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 }