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