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}