1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.xml.internal.ws.message.stream; 27 28 import com.sun.istack.internal.NotNull; 29 import com.sun.istack.internal.Nullable; 30 import com.sun.istack.internal.XMLStreamReaderToContentHandler; 31 import com.sun.xml.internal.bind.api.Bridge; 32 import com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer; 33 import com.sun.xml.internal.stream.buffer.XMLStreamBuffer; 34 import com.sun.xml.internal.stream.buffer.XMLStreamBufferMark; 35 import com.sun.xml.internal.stream.buffer.stax.StreamReaderBufferCreator; 36 import com.sun.xml.internal.ws.api.SOAPVersion; 37 import com.sun.xml.internal.ws.api.message.AttachmentSet; 38 import com.sun.xml.internal.ws.api.message.Header; 39 import com.sun.xml.internal.ws.api.message.HeaderList; 40 import com.sun.xml.internal.ws.api.message.Message; 41 import com.sun.xml.internal.ws.api.message.MessageHeaders; 42 import com.sun.xml.internal.ws.api.message.StreamingSOAP; 43 import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory; 44 import com.sun.xml.internal.ws.encoding.TagInfoset; 45 import com.sun.xml.internal.ws.message.AbstractMessageImpl; 46 import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl; 47 import com.sun.xml.internal.ws.protocol.soap.VersionMismatchException; 48 import com.sun.xml.internal.ws.spi.db.XMLBridge; 49 import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil; 50 import com.sun.xml.internal.ws.util.xml.DummyLocation; 51 import com.sun.xml.internal.ws.util.xml.StAXSource; 52 import com.sun.xml.internal.ws.util.xml.XMLReaderComposite; 53 import com.sun.xml.internal.org.jvnet.staxex.util.XMLStreamReaderToXMLStreamWriter; 54 import com.sun.xml.internal.ws.util.xml.XMLReaderComposite.ElemInfo; 55 56 import org.xml.sax.ContentHandler; 57 import org.xml.sax.ErrorHandler; 58 import org.xml.sax.SAXException; 59 import org.xml.sax.SAXParseException; 60 import org.xml.sax.helpers.NamespaceSupport; 61 62 import javax.xml.bind.JAXBException; 63 import javax.xml.bind.Unmarshaller; 64 import javax.xml.namespace.QName; 65 import javax.xml.stream.*; 66 67 import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT; 68 import static javax.xml.stream.XMLStreamConstants.START_ELEMENT; 69 import static javax.xml.stream.XMLStreamConstants.END_ELEMENT; 70 import javax.xml.transform.Source; 71 import javax.xml.ws.WebServiceException; 72 import java.util.ArrayList; 73 import java.util.Enumeration; 74 import java.util.HashMap; 75 import java.util.List; 76 import java.util.Map; 77 78 /** 79 * {@link Message} implementation backed by {@link XMLStreamReader}. 80 * 81 * TODO: we need another message class that keeps {@link XMLStreamReader} that points 82 * at the start of the envelope element. 83 */ 84 public class StreamMessage extends AbstractMessageImpl implements StreamingSOAP { 85 /** 86 * The reader will be positioned at 87 * the first child of the SOAP body 88 */ 89 private @NotNull XMLStreamReader reader; 90 91 // lazily created 92 private @Nullable MessageHeaders headers; 93 94 /** 95 * Because the StreamMessage leaves out the white spaces around payload 96 * when being instantiated the space characters between soap:Body opening and 97 * payload is stored in this field to be reused later (necessary for message security); 98 * Instantiated after StreamMessage creation 99 */ 100 private String bodyPrologue = null; 101 102 /** 103 * instantiated after writing message to XMLStreamWriter 104 */ 105 private String bodyEpilogue = null; 106 107 private String payloadLocalName; 108 109 private String payloadNamespaceURI; 110 111 /** 112 * Used only for debugging. This records where the message was consumed. 113 */ 114 private Throwable consumedAt; 115 116 private XMLStreamReader envelopeReader; 117 118 public StreamMessage(SOAPVersion v) { 119 super(v); 120 payloadLocalName = null; 121 payloadNamespaceURI = null; 122 } 123 124 public StreamMessage(SOAPVersion v, @NotNull XMLStreamReader envelope, @NotNull AttachmentSet attachments) { 125 super(v); 126 envelopeReader = envelope; 127 attachmentSet = attachments; 128 } 129 130 public XMLStreamReader readEnvelope() { 131 if (envelopeReader == null) { 132 List<XMLStreamReader> hReaders = new java.util.ArrayList<XMLStreamReader>(); 133 ElemInfo envElem = new ElemInfo(envelopeTag, null); 134 ElemInfo hdrElem = (headerTag != null) ? new ElemInfo(headerTag, envElem) : null; 135 ElemInfo bdyElem = new ElemInfo(bodyTag, envElem); 136 for (Header h : getHeaders().asList()) { 137 try { 138 hReaders.add(h.readHeader()); 139 } catch (XMLStreamException e) { 140 throw new RuntimeException(e); 141 } 142 } 143 XMLStreamReader soapHeader = (hdrElem != null) ? new XMLReaderComposite(hdrElem, hReaders.toArray(new XMLStreamReader[hReaders.size()])) : null; 144 XMLStreamReader[] payload = {readPayload()}; 145 XMLStreamReader soapBody = new XMLReaderComposite(bdyElem, payload); 146 XMLStreamReader[] soapContent = (soapHeader != null) ? new XMLStreamReader[]{soapHeader, soapBody} : new XMLStreamReader[]{soapBody}; 147 return new XMLReaderComposite(envElem, soapContent); 148 } 149 return envelopeReader; 150 } 151 152 /** 153 * Creates a {@link StreamMessage} from a {@link XMLStreamReader} 154 * that points at the start element of the payload, and headers. 155 * 156 * <p> 157 * This method creates a {@link Message} from a payload. 158 * 159 * @param headers 160 * if null, it means no headers. if non-null, 161 * it will be owned by this message. 162 * @param reader 163 * points at the start element/document of the payload (or the end element of the <s:Body> 164 * if there's no payload) 165 */ 166 public StreamMessage(@Nullable MessageHeaders headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) { 167 super(soapVersion); 168 init(headers, attachmentSet, reader, soapVersion); 169 } 170 171 private void init(@Nullable MessageHeaders headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) { 172 this.headers = headers; 173 this.attachmentSet = attachmentSet; 174 this.reader = reader; 175 176 if(reader.getEventType()== START_DOCUMENT) 177 XMLStreamReaderUtil.nextElementContent(reader); 178 179 //if the reader is pointing to the end element </soapenv:Body> then its empty message 180 // or no payload 181 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){ 182 String body = reader.getLocalName(); 183 String nsUri = reader.getNamespaceURI(); 184 assert body != null; 185 assert nsUri != null; 186 //if its not soapenv:Body then throw exception, we received malformed stream 187 if(body.equals("Body") && nsUri.equals(soapVersion.nsUri)){ 188 this.payloadLocalName = null; 189 this.payloadNamespaceURI = null; 190 }else{ //TODO: i18n and also we should be throwing better message that this 191 throw new WebServiceException("Malformed stream: {"+nsUri+"}"+body); 192 } 193 }else{ 194 this.payloadLocalName = reader.getLocalName(); 195 this.payloadNamespaceURI = reader.getNamespaceURI(); 196 } 197 198 // use the default infoset representation for headers 199 int base = soapVersion.ordinal()*3; 200 this.envelopeTag = DEFAULT_TAGS.get(base); 201 this.headerTag = DEFAULT_TAGS.get(base+1); 202 this.bodyTag = DEFAULT_TAGS.get(base+2); 203 } 204 205 /** 206 * Creates a {@link StreamMessage} from a {@link XMLStreamReader} 207 * and the complete infoset of the SOAP envelope. 208 * 209 * <p> 210 * See {@link #StreamMessage(MessageHeaders, AttachmentSet, XMLStreamReader, SOAPVersion)} for 211 * the description of the basic parameters. 212 * 213 * @param headerTag 214 * Null if the message didn't have a header tag. 215 * 216 */ 217 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @NotNull TagInfoset bodyTag, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) { 218 this(envelopeTag, headerTag, attachmentSet, headers, null, bodyTag, null, reader, soapVersion); 219 } 220 221 public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @Nullable String bodyPrologue, @NotNull TagInfoset bodyTag, @Nullable String bodyEpilogue, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) { 222 super(soapVersion); 223 init(envelopeTag, headerTag, attachmentSet, headers, bodyPrologue, bodyTag, bodyEpilogue, reader, soapVersion); 224 } 225 226 private void init(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @Nullable String bodyPrologue, @NotNull TagInfoset bodyTag, @Nullable String bodyEpilogue, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) { 227 init(headers,attachmentSet,reader,soapVersion); 228 if(envelopeTag == null ) { 229 throw new IllegalArgumentException("EnvelopeTag TagInfoset cannot be null"); 230 } 231 if(bodyTag == null ) { 232 throw new IllegalArgumentException("BodyTag TagInfoset cannot be null"); 233 } 234 this.envelopeTag = envelopeTag; 235 this.headerTag = headerTag; 236 this.bodyTag = bodyTag; 237 this.bodyPrologue = bodyPrologue; 238 this.bodyEpilogue = bodyEpilogue; 239 } 240 241 public boolean hasHeaders() { 242 if ( envelopeReader != null ) readEnvelope(this); 243 return headers!=null && headers.hasHeaders(); 244 } 245 246 public MessageHeaders getHeaders() { 247 if ( envelopeReader != null ) readEnvelope(this); 248 if (headers == null) { 249 headers = new HeaderList(getSOAPVersion()); 250 } 251 return headers; 252 } 253 254 public String getPayloadLocalPart() { 255 if ( envelopeReader != null ) readEnvelope(this); 256 return payloadLocalName; 257 } 258 259 public String getPayloadNamespaceURI() { 260 if ( envelopeReader != null ) readEnvelope(this); 261 return payloadNamespaceURI; 262 } 263 264 public boolean hasPayload() { 265 if ( envelopeReader != null ) readEnvelope(this); 266 return payloadLocalName!=null; 267 } 268 269 public Source readPayloadAsSource() { 270 if(hasPayload()) { 271 assert unconsumed(); 272 return new StAXSource(reader, true, getInscopeNamespaces()); 273 } else 274 return null; 275 } 276 277 /** 278 * There is no way to enumerate inscope namespaces for XMLStreamReader. That means 279 * namespaces declared in envelope, and body tags need to be computed using their 280 * {@link TagInfoset}s. 281 * 282 * @return array of the even length of the form { prefix0, uri0, prefix1, uri1, ... } 283 */ 284 private String[] getInscopeNamespaces() { 285 NamespaceSupport nss = new NamespaceSupport(); 286 287 nss.pushContext(); 288 for(int i=0; i < envelopeTag.ns.length; i+=2) { 289 nss.declarePrefix(envelopeTag.ns[i], envelopeTag.ns[i+1]); 290 } 291 292 nss.pushContext(); 293 for(int i=0; i < bodyTag.ns.length; i+=2) { 294 nss.declarePrefix(bodyTag.ns[i], bodyTag.ns[i+1]); 295 } 296 297 List<String> inscope = new ArrayList<String>(); 298 for( Enumeration en = nss.getPrefixes(); en.hasMoreElements(); ) { 299 String prefix = (String)en.nextElement(); 300 inscope.add(prefix); 301 inscope.add(nss.getURI(prefix)); 302 } 303 return inscope.toArray(new String[inscope.size()]); 304 } 305 306 public Object readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException { 307 if(!hasPayload()) 308 return null; 309 assert unconsumed(); 310 // TODO: How can the unmarshaller process this as a fragment? 311 if(hasAttachments()) 312 unmarshaller.setAttachmentUnmarshaller(new AttachmentUnmarshallerImpl(getAttachments())); 313 try { 314 return unmarshaller.unmarshal(reader); 315 } finally{ 316 unmarshaller.setAttachmentUnmarshaller(null); 317 XMLStreamReaderUtil.readRest(reader); 318 XMLStreamReaderUtil.close(reader); 319 XMLStreamReaderFactory.recycle(reader); 320 } 321 } 322 /** @deprecated */ 323 public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException { 324 if(!hasPayload()) 325 return null; 326 assert unconsumed(); 327 T r = bridge.unmarshal(reader, 328 hasAttachments() ? new AttachmentUnmarshallerImpl(getAttachments()) : null); 329 XMLStreamReaderUtil.readRest(reader); 330 XMLStreamReaderUtil.close(reader); 331 XMLStreamReaderFactory.recycle(reader); 332 return r; 333 } 334 335 public <T> T readPayloadAsJAXB(XMLBridge<T> bridge) throws JAXBException { 336 if(!hasPayload()) 337 return null; 338 assert unconsumed(); 339 T r = bridge.unmarshal(reader, 340 hasAttachments() ? new AttachmentUnmarshallerImpl(getAttachments()) : null); 341 XMLStreamReaderUtil.readRest(reader); 342 XMLStreamReaderUtil.close(reader); 343 XMLStreamReaderFactory.recycle(reader); 344 return r; 345 } 346 347 @Override 348 public void consume() { 349 assert unconsumed(); 350 XMLStreamReaderUtil.readRest(reader); 351 XMLStreamReaderUtil.close(reader); 352 XMLStreamReaderFactory.recycle(reader); 353 } 354 355 public XMLStreamReader readPayload() { 356 if(!hasPayload()) 357 return null; 358 // TODO: What about access at and beyond </soap:Body> 359 assert unconsumed(); 360 return this.reader; 361 } 362 363 public void writePayloadTo(XMLStreamWriter writer)throws XMLStreamException { 364 if ( envelopeReader != null ) readEnvelope(this); 365 assert unconsumed(); 366 367 if(payloadLocalName==null) { 368 return; // no body 369 } 370 371 if (bodyPrologue != null) { 372 writer.writeCharacters(bodyPrologue); 373 } 374 375 XMLStreamReaderToXMLStreamWriter conv = new XMLStreamReaderToXMLStreamWriter(); 376 377 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){ 378 String name = reader.getLocalName(); 379 String nsUri = reader.getNamespaceURI(); 380 381 // After previous conv.bridge() call the cursor will be at END_ELEMENT. 382 // Check if its not soapenv:Body then move to next ELEMENT 383 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){ 384 385 if (!isBodyElement(name, nsUri)){ 386 // closing payload element: store epilogue for further signing, if applicable 387 // however if there more than one payloads exist - the last one is stored 388 String whiteSpaces = XMLStreamReaderUtil.nextWhiteSpaceContent(reader); 389 if (whiteSpaces != null) { 390 this.bodyEpilogue = whiteSpaces; 391 // write it to the message too 392 writer.writeCharacters(whiteSpaces); 393 } 394 } else { 395 // body closed > exit 396 break; 397 } 398 399 } else { 400 // payload opening element: copy payload to writer 401 conv.bridge(reader,writer); 402 } 403 } 404 405 XMLStreamReaderUtil.readRest(reader); 406 XMLStreamReaderUtil.close(reader); 407 XMLStreamReaderFactory.recycle(reader); 408 } 409 410 private boolean isBodyElement(String name, String nsUri) { 411 return name.equals("Body") && nsUri.equals(soapVersion.nsUri); 412 } 413 414 public void writeTo(XMLStreamWriter sw) throws XMLStreamException{ 415 if ( envelopeReader != null ) readEnvelope(this); 416 writeEnvelope(sw); 417 } 418 419 public void writeToBodyStart(XMLStreamWriter writer) throws XMLStreamException { 420 if ( envelopeReader != null ) readEnvelope(this); 421 writer.writeStartDocument(); 422 envelopeTag.writeStart(writer); 423 424 //write headers 425 MessageHeaders hl = getHeaders(); 426 if (hl.hasHeaders() && headerTag == null) headerTag = new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS); 427 if (headerTag != null) { 428 headerTag.writeStart(writer); 429 if (hl.hasHeaders()){ 430 for(Header h : hl.asList()){ 431 h.writeTo(writer); 432 } 433 } 434 writer.writeEndElement(); 435 } 436 bodyTag.writeStart(writer); 437 438 } 439 440 /** 441 * This method should be called when the StreamMessage is created with a payload 442 * @param writer 443 */ 444 private void writeEnvelope(XMLStreamWriter writer) throws XMLStreamException { 445 writeToBodyStart(writer); 446 if(hasPayload()) 447 writePayloadTo(writer); 448 writer.writeEndElement(); 449 writer.writeEndElement(); 450 writer.writeEndDocument(); 451 } 452 453 public void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException { 454 if ( envelopeReader != null ) readEnvelope(this); 455 assert unconsumed(); 456 457 try { 458 if(payloadLocalName==null) 459 return; // no body 460 461 if (bodyPrologue != null) { 462 char[] chars = bodyPrologue.toCharArray(); 463 contentHandler.characters(chars, 0, chars.length); 464 } 465 466 XMLStreamReaderToContentHandler conv = new XMLStreamReaderToContentHandler(reader,contentHandler,true,fragment,getInscopeNamespaces()); 467 468 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){ 469 String name = reader.getLocalName(); 470 String nsUri = reader.getNamespaceURI(); 471 472 // After previous conv.bridge() call the cursor will be at END_ELEMENT. 473 // Check if its not soapenv:Body then move to next ELEMENT 474 if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){ 475 476 if (!isBodyElement(name, nsUri)){ 477 // closing payload element: store epilogue for further signing, if applicable 478 // however if there more than one payloads exist - the last one is stored 479 String whiteSpaces = XMLStreamReaderUtil.nextWhiteSpaceContent(reader); 480 if (whiteSpaces != null) { 481 this.bodyEpilogue = whiteSpaces; 482 // write it to the message too 483 char[] chars = whiteSpaces.toCharArray(); 484 contentHandler.characters(chars, 0, chars.length); 485 } 486 } else { 487 // body closed > exit 488 break; 489 } 490 491 } else { 492 // payload opening element: copy payload to writer 493 conv.bridge(); 494 } 495 } 496 XMLStreamReaderUtil.readRest(reader); 497 XMLStreamReaderUtil.close(reader); 498 XMLStreamReaderFactory.recycle(reader); 499 } catch (XMLStreamException e) { 500 Location loc = e.getLocation(); 501 if(loc==null) loc = DummyLocation.INSTANCE; 502 503 SAXParseException x = new SAXParseException( 504 e.getMessage(),loc.getPublicId(),loc.getSystemId(),loc.getLineNumber(),loc.getColumnNumber(),e); 505 errorHandler.error(x); 506 } 507 } 508 509 // TODO: this method should be probably rewritten to respect spaces between elements; is it used at all? 510 @Override 511 public Message copy() { 512 if ( envelopeReader != null ) readEnvelope(this); 513 try { 514 assert unconsumed(); 515 consumedAt = null; // but we don't want to mark it as consumed 516 MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer(); 517 StreamReaderBufferCreator c = new StreamReaderBufferCreator(xsb); 518 519 // preserving inscope namespaces from envelope, and body. Other option 520 // would be to create a filtering XMLStreamReader from reader+envelopeTag+bodyTag 521 c.storeElement(envelopeTag.nsUri, envelopeTag.localName, envelopeTag.prefix, envelopeTag.ns); 522 c.storeElement(bodyTag.nsUri, bodyTag.localName, bodyTag.prefix, bodyTag.ns); 523 524 if (hasPayload()) { 525 // Loop all the way for multi payload case 526 while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){ 527 String name = reader.getLocalName(); 528 String nsUri = reader.getNamespaceURI(); 529 if(isBodyElement(name, nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT)) 530 break; 531 c.create(reader); 532 533 // Skip whitespaces in between payload and </Body> or between elements 534 // those won't be in the message itself, but we store them in field bodyEpilogue 535 if (reader.isWhiteSpace()) { 536 bodyEpilogue = XMLStreamReaderUtil.currentWhiteSpaceContent(reader); 537 } else { 538 // clear it in case the existing was not the last one 539 // (we are interested only in the last one?) 540 bodyEpilogue = null; 541 } 542 } 543 } 544 c.storeEndElement(); // create structure element for </Body> 545 c.storeEndElement(); // create structure element for </Envelope> 546 c.storeEndElement(); // create structure element for END_DOCUMENT 547 548 XMLStreamReaderUtil.readRest(reader); 549 XMLStreamReaderUtil.close(reader); 550 XMLStreamReaderFactory.recycle(reader); 551 552 reader = xsb.readAsXMLStreamReader(); 553 XMLStreamReader clone = xsb.readAsXMLStreamReader(); 554 555 // advance to the start tag of the <Body> first child element 556 proceedToRootElement(reader); 557 proceedToRootElement(clone); 558 559 return new StreamMessage(envelopeTag, headerTag, attachmentSet, HeaderList.copy(headers), bodyPrologue, bodyTag, bodyEpilogue, clone, soapVersion).copyFrom(this); 560 } catch (XMLStreamException e) { 561 throw new WebServiceException("Failed to copy a message",e); 562 } 563 } 564 565 private void proceedToRootElement(XMLStreamReader xsr) throws XMLStreamException { 566 assert xsr.getEventType()==START_DOCUMENT; 567 xsr.nextTag(); 568 xsr.nextTag(); 569 xsr.nextTag(); 570 assert xsr.getEventType()==START_ELEMENT || xsr.getEventType()==END_ELEMENT; 571 } 572 573 public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler ) throws SAXException { 574 if ( envelopeReader != null ) readEnvelope(this); 575 contentHandler.setDocumentLocator(NULL_LOCATOR); 576 contentHandler.startDocument(); 577 envelopeTag.writeStart(contentHandler); 578 if (hasHeaders() && headerTag == null) headerTag = new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS); 579 if (headerTag != null) { 580 headerTag.writeStart(contentHandler); 581 if (hasHeaders()) { 582 MessageHeaders headers = getHeaders(); 583 for (Header h : headers.asList()) { 584 // shouldn't JDK be smart enough to use array-style indexing for this foreach!? 585 h.writeTo(contentHandler,errorHandler); 586 } 587 } 588 headerTag.writeEnd(contentHandler); 589 } 590 bodyTag.writeStart(contentHandler); 591 writePayloadTo(contentHandler,errorHandler, true); 592 bodyTag.writeEnd(contentHandler); 593 envelopeTag.writeEnd(contentHandler); 594 contentHandler.endDocument(); 595 } 596 597 /** 598 * Used for an assertion. Returns true when the message is unconsumed, 599 * or otherwise throw an exception. 600 * 601 * <p> 602 * Calling this method also marks the stream as 'consumed' 603 */ 604 private boolean unconsumed() { 605 if(payloadLocalName==null) 606 return true; // no payload. can be consumed multiple times. 607 608 if(reader.getEventType()!=XMLStreamReader.START_ELEMENT) { 609 AssertionError error = new AssertionError("StreamMessage has been already consumed. See the nested exception for where it's consumed"); 610 error.initCause(consumedAt); 611 throw error; 612 } 613 consumedAt = new Exception().fillInStackTrace(); 614 return true; 615 } 616 617 public String getBodyPrologue() { 618 if ( envelopeReader != null ) readEnvelope(this); 619 return bodyPrologue; 620 } 621 622 public String getBodyEpilogue() { 623 if ( envelopeReader != null ) readEnvelope(this); 624 return bodyEpilogue; 625 } 626 627 public XMLStreamReader getReader() { 628 if ( envelopeReader != null ) readEnvelope(this); 629 assert unconsumed(); 630 return reader; 631 } 632 633 634 private static final String SOAP_ENVELOPE = "Envelope"; 635 private static final String SOAP_HEADER = "Header"; 636 private static final String SOAP_BODY = "Body"; 637 638 protected interface StreamHeaderDecoder { 639 public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark); 640 } 641 642 static final StreamHeaderDecoder SOAP12StreamHeaderDecoder = new StreamHeaderDecoder() { 643 @Override 644 public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark) { 645 return new StreamHeader12(reader, mark); 646 } 647 }; 648 649 static final StreamHeaderDecoder SOAP11StreamHeaderDecoder = new StreamHeaderDecoder() { 650 @Override 651 public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark) { 652 return new StreamHeader11(reader, mark); 653 } 654 }; 655 656 static private void readEnvelope(StreamMessage message) { 657 if ( message.envelopeReader == null ) return; 658 XMLStreamReader reader = message.envelopeReader; 659 message.envelopeReader = null; 660 SOAPVersion soapVersion = message.soapVersion; 661 // Move to soap:Envelope and verify 662 if(reader.getEventType()!=XMLStreamConstants.START_ELEMENT) 663 XMLStreamReaderUtil.nextElementContent(reader); 664 XMLStreamReaderUtil.verifyReaderState(reader,XMLStreamConstants.START_ELEMENT); 665 if (SOAP_ENVELOPE.equals(reader.getLocalName()) && !soapVersion.nsUri.equals(reader.getNamespaceURI())) { 666 throw new VersionMismatchException(soapVersion, soapVersion.nsUri, reader.getNamespaceURI()); 667 } 668 XMLStreamReaderUtil.verifyTag(reader, soapVersion.nsUri, SOAP_ENVELOPE); 669 670 TagInfoset envelopeTag = new TagInfoset(reader); 671 672 // Collect namespaces on soap:Envelope 673 Map<String,String> namespaces = new HashMap<String,String>(); 674 for(int i=0; i< reader.getNamespaceCount();i++){ 675 namespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); 676 } 677 678 // Move to next element 679 XMLStreamReaderUtil.nextElementContent(reader); 680 XMLStreamReaderUtil.verifyReaderState(reader, 681 javax.xml.stream.XMLStreamConstants.START_ELEMENT); 682 683 HeaderList headers = null; 684 TagInfoset headerTag = null; 685 686 if (reader.getLocalName().equals(SOAP_HEADER) 687 && reader.getNamespaceURI().equals(soapVersion.nsUri)) { 688 headerTag = new TagInfoset(reader); 689 690 // Collect namespaces on soap:Header 691 for(int i=0; i< reader.getNamespaceCount();i++){ 692 namespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); 693 } 694 // skip <soap:Header> 695 XMLStreamReaderUtil.nextElementContent(reader); 696 697 // If SOAP header blocks are present (i.e. not <soap:Header/>) 698 if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { 699 headers = new HeaderList(soapVersion); 700 701 try { 702 // Cache SOAP header blocks 703 StreamHeaderDecoder headerDecoder = SOAPVersion.SOAP_11.equals(soapVersion) ? SOAP11StreamHeaderDecoder : SOAP12StreamHeaderDecoder; 704 cacheHeaders(reader, namespaces, headers, headerDecoder); 705 } catch (XMLStreamException e) { 706 // TODO need to throw more meaningful exception 707 throw new WebServiceException(e); 708 } 709 } 710 711 // Move to soap:Body 712 XMLStreamReaderUtil.nextElementContent(reader); 713 } 714 715 // Verify that <soap:Body> is present 716 XMLStreamReaderUtil.verifyTag(reader, soapVersion.nsUri, SOAP_BODY); 717 TagInfoset bodyTag = new TagInfoset(reader); 718 719 String bodyPrologue = XMLStreamReaderUtil.nextWhiteSpaceContent(reader); 720 message.init(envelopeTag,headerTag,message.attachmentSet,headers,bodyPrologue,bodyTag,null,reader,soapVersion); 721 // when there's no payload, 722 // it's tempting to use EmptyMessageImpl, but it doesn't preserve the infoset 723 // of <envelope>,<header>, and <body>, so we need to stick to StreamMessage. 724 } 725 726 727 private static XMLStreamBuffer cacheHeaders(XMLStreamReader reader, 728 Map<String, String> namespaces, HeaderList headers, 729 StreamHeaderDecoder headerDecoder) throws XMLStreamException { 730 MutableXMLStreamBuffer buffer = createXMLStreamBuffer(); 731 StreamReaderBufferCreator creator = new StreamReaderBufferCreator(); 732 creator.setXMLStreamBuffer(buffer); 733 734 // Reader is positioned at the first header block 735 while(reader.getEventType() == javax.xml.stream.XMLStreamConstants.START_ELEMENT) { 736 Map<String,String> headerBlockNamespaces = namespaces; 737 738 // Collect namespaces on SOAP header block 739 if (reader.getNamespaceCount() > 0) { 740 headerBlockNamespaces = new HashMap<String,String>(namespaces); 741 for (int i = 0; i < reader.getNamespaceCount(); i++) { 742 headerBlockNamespaces.put(reader.getNamespacePrefix(i), reader.getNamespaceURI(i)); 743 } 744 } 745 746 // Mark 747 XMLStreamBuffer mark = new XMLStreamBufferMark(headerBlockNamespaces, creator); 748 // Create Header 749 headers.add(headerDecoder.decodeHeader(reader, mark)); 750 751 752 // Cache the header block 753 // After caching Reader will be positioned at next header block or 754 // the end of the </soap:header> 755 creator.createElementFragment(reader, false); 756 if (reader.getEventType() != XMLStreamConstants.START_ELEMENT && 757 reader.getEventType() != XMLStreamConstants.END_ELEMENT) { 758 XMLStreamReaderUtil.nextElementContent(reader); 759 } 760 } 761 762 return buffer; 763 } 764 765 private static MutableXMLStreamBuffer createXMLStreamBuffer() { 766 // TODO: Decode should own one MutableXMLStreamBuffer for reuse 767 // since it is more efficient. ISSUE: possible issue with 768 // lifetime of information in the buffer if accessed beyond 769 // the pipe line. 770 return new MutableXMLStreamBuffer(); 771 } 772 773 public boolean isPayloadStreamReader() { return true; } 774 775 public QName getPayloadQName() { 776 return this.hasPayload() ? new QName(payloadNamespaceURI, payloadLocalName) : null; 777 } 778 779 public XMLStreamReader readToBodyStarTag() { 780 if ( envelopeReader != null ) readEnvelope(this); 781 List<XMLStreamReader> hReaders = new java.util.ArrayList<XMLStreamReader>(); 782 ElemInfo envElem = new ElemInfo(envelopeTag, null); 783 ElemInfo hdrElem = (headerTag != null) ? new ElemInfo(headerTag, envElem) : null; 784 ElemInfo bdyElem = new ElemInfo(bodyTag, envElem); 785 for (Header h : getHeaders().asList()) { 786 try { 787 hReaders.add(h.readHeader()); 788 } catch (XMLStreamException e) { 789 throw new RuntimeException(e); 790 } 791 } 792 XMLStreamReader soapHeader = (hdrElem != null) ? new XMLReaderComposite(hdrElem, hReaders.toArray(new XMLStreamReader[hReaders.size()])) : null; 793 XMLStreamReader[] payload = {}; 794 XMLStreamReader soapBody = new XMLReaderComposite(bdyElem, payload); 795 XMLStreamReader[] soapContent = (soapHeader != null) ? new XMLStreamReader[]{soapHeader, soapBody} : new XMLStreamReader[]{soapBody}; 796 return new XMLReaderComposite(envElem, soapContent); 797 } 798 }