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