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 &lt;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 }