/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.xml.internal.ws.message.stream;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;
import com.sun.istack.internal.XMLStreamReaderToContentHandler;
import com.sun.xml.internal.bind.api.Bridge;
import com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer;
import com.sun.xml.internal.stream.buffer.XMLStreamBuffer;
import com.sun.xml.internal.stream.buffer.XMLStreamBufferMark;
import com.sun.xml.internal.stream.buffer.stax.StreamReaderBufferCreator;
import com.sun.xml.internal.ws.api.SOAPVersion;
import com.sun.xml.internal.ws.api.message.AttachmentSet;
import com.sun.xml.internal.ws.api.message.Header;
import com.sun.xml.internal.ws.api.message.HeaderList;
import com.sun.xml.internal.ws.api.message.Message;
import com.sun.xml.internal.ws.api.message.MessageHeaders;
import com.sun.xml.internal.ws.api.message.StreamingSOAP;
import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
import com.sun.xml.internal.ws.encoding.TagInfoset;
import com.sun.xml.internal.ws.message.AbstractMessageImpl;
import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
import com.sun.xml.internal.ws.protocol.soap.VersionMismatchException;
import com.sun.xml.internal.ws.spi.db.XMLBridge;
import com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
import com.sun.xml.internal.ws.util.xml.DummyLocation;
import com.sun.xml.internal.ws.util.xml.StAXSource;
import com.sun.xml.internal.ws.util.xml.XMLReaderComposite;
import com.sun.xml.internal.ws.util.xml.XMLStreamReaderToXMLStreamWriter;
import com.sun.xml.internal.ws.util.xml.XMLReaderComposite.ElemInfo;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.NamespaceSupport;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.*;
import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import javax.xml.transform.Source;
import javax.xml.ws.WebServiceException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* {@link Message} implementation backed by {@link XMLStreamReader}.
*
* TODO: we need another message class that keeps {@link XMLStreamReader} that points
* at the start of the envelope element.
*/
public class StreamMessage extends AbstractMessageImpl implements StreamingSOAP {
/**
* The reader will be positioned at
* the first child of the SOAP body
*/
private @NotNull XMLStreamReader reader;
// lazily created
private @Nullable MessageHeaders headers;
/**
* Because the StreamMessage leaves out the white spaces around payload
* when being instantiated the space characters between soap:Body opening and
* payload is stored in this field to be reused later (necessary for message security);
* Instantiated after StreamMessage creation
*/
private String bodyPrologue = null;
/**
* instantiated after writing message to XMLStreamWriter
*/
private String bodyEpilogue = null;
private String payloadLocalName;
private String payloadNamespaceURI;
/**
* Used only for debugging. This records where the message was consumed.
*/
private Throwable consumedAt;
private XMLStreamReader envelopeReader;
public StreamMessage(SOAPVersion v) {
super(v);
payloadLocalName = null;
payloadNamespaceURI = null;
}
public StreamMessage(SOAPVersion v, @NotNull XMLStreamReader envelope, @NotNull AttachmentSet attachments) {
super(v);
envelopeReader = envelope;
attachmentSet = attachments;
}
public XMLStreamReader readEnvelope() {
if (envelopeReader == null) {
List
* This method creates a {@link Message} from a payload.
*
* @param headers
* if null, it means no headers. if non-null,
* it will be owned by this message.
* @param reader
* points at the start element/document of the payload (or the end element of the <s:Body>
* if there's no payload)
*/
public StreamMessage(@Nullable MessageHeaders headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
super(soapVersion);
init(headers, attachmentSet, reader, soapVersion);
}
private void init(@Nullable MessageHeaders headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
this.headers = headers;
this.attachmentSet = attachmentSet;
this.reader = reader;
if(reader.getEventType()== START_DOCUMENT)
XMLStreamReaderUtil.nextElementContent(reader);
//if the reader is pointing to the end element then its empty message
// or no payload
if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
String body = reader.getLocalName();
String nsUri = reader.getNamespaceURI();
assert body != null;
assert nsUri != null;
//if its not soapenv:Body then throw exception, we received malformed stream
if(body.equals("Body") && nsUri.equals(soapVersion.nsUri)){
this.payloadLocalName = null;
this.payloadNamespaceURI = null;
}else{ //TODO: i18n and also we should be throwing better message that this
throw new WebServiceException("Malformed stream: {"+nsUri+"}"+body);
}
}else{
this.payloadLocalName = reader.getLocalName();
this.payloadNamespaceURI = reader.getNamespaceURI();
}
// use the default infoset representation for headers
int base = soapVersion.ordinal()*3;
this.envelopeTag = DEFAULT_TAGS.get(base);
this.headerTag = DEFAULT_TAGS.get(base+1);
this.bodyTag = DEFAULT_TAGS.get(base+2);
}
/**
* Creates a {@link StreamMessage} from a {@link XMLStreamReader}
* and the complete infoset of the SOAP envelope.
*
*
* See {@link #StreamMessage(MessageHeaders, AttachmentSet, XMLStreamReader, SOAPVersion)} for
* the description of the basic parameters.
*
* @param headerTag
* Null if the message didn't have a header tag.
*
*/
public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable MessageHeaders headers, @NotNull TagInfoset bodyTag, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
this(envelopeTag, headerTag, attachmentSet, headers, null, bodyTag, null, reader, soapVersion);
}
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) {
super(soapVersion);
init(envelopeTag, headerTag, attachmentSet, headers, bodyPrologue, bodyTag, bodyEpilogue, reader, soapVersion);
}
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) {
init(headers,attachmentSet,reader,soapVersion);
if(envelopeTag == null ) {
throw new IllegalArgumentException("EnvelopeTag TagInfoset cannot be null");
}
if(bodyTag == null ) {
throw new IllegalArgumentException("BodyTag TagInfoset cannot be null");
}
this.envelopeTag = envelopeTag;
this.headerTag = headerTag;
this.bodyTag = bodyTag;
this.bodyPrologue = bodyPrologue;
this.bodyEpilogue = bodyEpilogue;
}
public boolean hasHeaders() {
if ( envelopeReader != null ) readEnvelope(this);
return headers!=null && headers.hasHeaders();
}
public MessageHeaders getHeaders() {
if ( envelopeReader != null ) readEnvelope(this);
if (headers == null) {
headers = new HeaderList(getSOAPVersion());
}
return headers;
}
public String getPayloadLocalPart() {
if ( envelopeReader != null ) readEnvelope(this);
return payloadLocalName;
}
public String getPayloadNamespaceURI() {
if ( envelopeReader != null ) readEnvelope(this);
return payloadNamespaceURI;
}
public boolean hasPayload() {
if ( envelopeReader != null ) readEnvelope(this);
return payloadLocalName!=null;
}
public Source readPayloadAsSource() {
if(hasPayload()) {
assert unconsumed();
return new StAXSource(reader, true, getInscopeNamespaces());
} else
return null;
}
/**
* There is no way to enumerate inscope namespaces for XMLStreamReader. That means
* namespaces declared in envelope, and body tags need to be computed using their
* {@link TagInfoset}s.
*
* @return array of the even length of the form { prefix0, uri0, prefix1, uri1, ... }
*/
private String[] getInscopeNamespaces() {
NamespaceSupport nss = new NamespaceSupport();
nss.pushContext();
for(int i=0; i < envelopeTag.ns.length; i+=2) {
nss.declarePrefix(envelopeTag.ns[i], envelopeTag.ns[i+1]);
}
nss.pushContext();
for(int i=0; i < bodyTag.ns.length; i+=2) {
nss.declarePrefix(bodyTag.ns[i], bodyTag.ns[i+1]);
}
List
* Calling this method also marks the stream as 'consumed'
*/
private boolean unconsumed() {
if(payloadLocalName==null)
return true; // no payload. can be consumed multiple times.
if(reader.getEventType()!=XMLStreamReader.START_ELEMENT) {
AssertionError error = new AssertionError("StreamMessage has been already consumed. See the nested exception for where it's consumed");
error.initCause(consumedAt);
throw error;
}
consumedAt = new Exception().fillInStackTrace();
return true;
}
public String getBodyPrologue() {
if ( envelopeReader != null ) readEnvelope(this);
return bodyPrologue;
}
public String getBodyEpilogue() {
if ( envelopeReader != null ) readEnvelope(this);
return bodyEpilogue;
}
public XMLStreamReader getReader() {
if ( envelopeReader != null ) readEnvelope(this);
assert unconsumed();
return reader;
}
private static final String SOAP_ENVELOPE = "Envelope";
private static final String SOAP_HEADER = "Header";
private static final String SOAP_BODY = "Body";
protected interface StreamHeaderDecoder {
public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark);
}
static final StreamHeaderDecoder SOAP12StreamHeaderDecoder = new StreamHeaderDecoder() {
@Override
public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark) {
return new StreamHeader12(reader, mark);
}
};
static final StreamHeaderDecoder SOAP11StreamHeaderDecoder = new StreamHeaderDecoder() {
@Override
public Header decodeHeader(XMLStreamReader reader, XMLStreamBuffer mark) {
return new StreamHeader11(reader, mark);
}
};
static private void readEnvelope(StreamMessage message) {
if ( message.envelopeReader == null ) return;
XMLStreamReader reader = message.envelopeReader;
message.envelopeReader = null;
SOAPVersion soapVersion = message.soapVersion;
// Move to soap:Envelope and verify
if(reader.getEventType()!=XMLStreamConstants.START_ELEMENT)
XMLStreamReaderUtil.nextElementContent(reader);
XMLStreamReaderUtil.verifyReaderState(reader,XMLStreamConstants.START_ELEMENT);
if (SOAP_ENVELOPE.equals(reader.getLocalName()) && !soapVersion.nsUri.equals(reader.getNamespaceURI())) {
throw new VersionMismatchException(soapVersion, soapVersion.nsUri, reader.getNamespaceURI());
}
XMLStreamReaderUtil.verifyTag(reader, soapVersion.nsUri, SOAP_ENVELOPE);
TagInfoset envelopeTag = new TagInfoset(reader);
// Collect namespaces on soap:Envelope
Map