325N/A/*
325N/A * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
325N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
325N/A *
325N/A * This code is free software; you can redistribute it and/or modify it
325N/A * under the terms of the GNU General Public License version 2 only, as
325N/A * published by the Free Software Foundation. Oracle designates this
325N/A * particular file as subject to the "Classpath" exception as provided
325N/A * by Oracle in the LICENSE file that accompanied this code.
325N/A *
325N/A * This code is distributed in the hope that it will be useful, but WITHOUT
325N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
325N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
325N/A * version 2 for more details (a copy is included in the LICENSE file that
325N/A * accompanied this code).
325N/A *
325N/A * You should have received a copy of the GNU General Public License version
325N/A * 2 along with this work; if not, write to the Free Software Foundation,
325N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
325N/A *
325N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
325N/A * or visit www.oracle.com if you need additional information or have any
325N/A * questions.
325N/A */
325N/A
325N/Apackage com.sun.xml.internal.ws.message.stream;
325N/A
325N/Aimport com.sun.istack.internal.NotNull;
325N/Aimport com.sun.istack.internal.Nullable;
325N/Aimport com.sun.istack.internal.XMLStreamReaderToContentHandler;
325N/Aimport com.sun.xml.internal.bind.api.Bridge;
325N/Aimport com.sun.xml.internal.stream.buffer.MutableXMLStreamBuffer;
325N/Aimport com.sun.xml.internal.stream.buffer.stax.StreamReaderBufferCreator;
325N/Aimport com.sun.xml.internal.ws.api.SOAPVersion;
325N/Aimport com.sun.xml.internal.ws.api.message.AttachmentSet;
325N/Aimport com.sun.xml.internal.ws.api.message.Header;
325N/Aimport com.sun.xml.internal.ws.api.message.HeaderList;
325N/Aimport com.sun.xml.internal.ws.api.message.Message;
325N/Aimport com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
325N/Aimport com.sun.xml.internal.ws.encoding.TagInfoset;
325N/Aimport com.sun.xml.internal.ws.message.AbstractMessageImpl;
325N/Aimport com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
325N/Aimport com.sun.xml.internal.ws.streaming.XMLStreamReaderUtil;
325N/Aimport com.sun.xml.internal.ws.util.xml.DummyLocation;
325N/Aimport com.sun.xml.internal.ws.util.xml.StAXSource;
325N/Aimport com.sun.xml.internal.ws.util.xml.XMLStreamReaderToXMLStreamWriter;
325N/Aimport org.xml.sax.ContentHandler;
325N/Aimport org.xml.sax.ErrorHandler;
325N/Aimport org.xml.sax.SAXException;
325N/Aimport org.xml.sax.SAXParseException;
325N/Aimport org.xml.sax.helpers.NamespaceSupport;
325N/A
325N/Aimport javax.xml.bind.JAXBException;
325N/Aimport javax.xml.bind.Unmarshaller;
325N/Aimport javax.xml.stream.*;
325N/Aimport static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
325N/Aimport static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
325N/Aimport static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
325N/Aimport javax.xml.transform.Source;
325N/Aimport javax.xml.ws.WebServiceException;
325N/Aimport java.util.ArrayList;
325N/Aimport java.util.Arrays;
325N/Aimport java.util.Enumeration;
325N/Aimport java.util.List;
325N/A
325N/A/**
325N/A * {@link Message} implementation backed by {@link XMLStreamReader}.
325N/A *
325N/A * TODO: we need another message class that keeps {@link XMLStreamReader} that points
325N/A * at the start of the envelope element.
325N/A */
325N/Apublic final class StreamMessage extends AbstractMessageImpl {
325N/A /**
325N/A * The reader will be positioned at
325N/A * the first child of the SOAP body
325N/A */
325N/A private @NotNull XMLStreamReader reader;
325N/A
325N/A // lazily created
325N/A private @Nullable HeaderList headers;
325N/A
325N/A private final String payloadLocalName;
325N/A
325N/A private final String payloadNamespaceURI;
325N/A
325N/A /**
325N/A * infoset about the SOAP envelope, header, and body.
325N/A *
325N/A * <p>
325N/A * If the creater of this object didn't care about those,
325N/A * we use stock values.
325N/A */
325N/A private /*almost final*/ @NotNull TagInfoset envelopeTag,headerTag,bodyTag;
325N/A
325N/A /**
325N/A * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
325N/A * that points at the start element of the payload, and headers.
325N/A *
325N/A * <p>
325N/A * This method creaets a {@link Message} from a payload.
325N/A *
325N/A * @param headers
325N/A * if null, it means no headers. if non-null,
325N/A * it will be owned by this message.
325N/A * @param reader
325N/A * points at the start element/document of the payload (or the end element of the &lt;s:Body>
325N/A * if there's no payload)
325N/A */
325N/A public StreamMessage(@Nullable HeaderList headers, @NotNull AttachmentSet attachmentSet, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
325N/A super(soapVersion);
325N/A this.headers = headers;
325N/A this.attachmentSet = attachmentSet;
325N/A this.reader = reader;
325N/A
325N/A if(reader.getEventType()== START_DOCUMENT)
325N/A XMLStreamReaderUtil.nextElementContent(reader);
325N/A
325N/A //if the reader is pointing to the end element </soapenv:Body> then its empty message
325N/A // or no payload
325N/A if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
325N/A String body = reader.getLocalName();
325N/A String nsUri = reader.getNamespaceURI();
325N/A assert body != null;
325N/A assert nsUri != null;
325N/A //if its not soapenv:Body then throw exception, we received malformed stream
325N/A if(body.equals("Body") && nsUri.equals(soapVersion.nsUri)){
325N/A this.payloadLocalName = null;
325N/A this.payloadNamespaceURI = null;
325N/A }else{ //TODO: i18n and also we should be throwing better message that this
325N/A throw new WebServiceException("Malformed stream: {"+nsUri+"}"+body);
325N/A }
325N/A }else{
325N/A this.payloadLocalName = reader.getLocalName();
325N/A this.payloadNamespaceURI = reader.getNamespaceURI();
325N/A }
325N/A
325N/A // use the default infoset representation for headers
325N/A int base = soapVersion.ordinal()*3;
325N/A this.envelopeTag = DEFAULT_TAGS[base];
325N/A this.headerTag = DEFAULT_TAGS[base+1];
325N/A this.bodyTag = DEFAULT_TAGS[base+2];
325N/A }
325N/A
325N/A /**
325N/A * Creates a {@link StreamMessage} from a {@link XMLStreamReader}
325N/A * and the complete infoset of the SOAP envelope.
325N/A *
325N/A * <p>
325N/A * See {@link #StreamMessage(HeaderList, AttachmentSet, XMLStreamReader, SOAPVersion)} for
325N/A * the description of the basic parameters.
325N/A *
325N/A * @param headerTag
325N/A * Null if the message didn't have a header tag.
325N/A *
325N/A */
325N/A public StreamMessage(@NotNull TagInfoset envelopeTag, @Nullable TagInfoset headerTag, @NotNull AttachmentSet attachmentSet, @Nullable HeaderList headers, @NotNull TagInfoset bodyTag, @NotNull XMLStreamReader reader, @NotNull SOAPVersion soapVersion) {
325N/A this(headers,attachmentSet,reader,soapVersion);
325N/A if(envelopeTag == null ) {
325N/A throw new IllegalArgumentException("EnvelopeTag TagInfoset cannot be null");
325N/A }
325N/A if(bodyTag == null ) {
325N/A throw new IllegalArgumentException("BodyTag TagInfoset cannot be null");
325N/A }
325N/A this.envelopeTag = envelopeTag;
325N/A this.headerTag = headerTag!=null ? headerTag :
325N/A new TagInfoset(envelopeTag.nsUri,"Header",envelopeTag.prefix,EMPTY_ATTS);
325N/A this.bodyTag = bodyTag;
325N/A }
325N/A
325N/A public boolean hasHeaders() {
325N/A return headers!=null && !headers.isEmpty();
325N/A }
325N/A
325N/A public HeaderList getHeaders() {
325N/A if (headers == null) {
325N/A headers = new HeaderList();
325N/A }
325N/A return headers;
325N/A }
325N/A
325N/A public String getPayloadLocalPart() {
325N/A return payloadLocalName;
325N/A }
325N/A
325N/A public String getPayloadNamespaceURI() {
325N/A return payloadNamespaceURI;
325N/A }
325N/A
325N/A public boolean hasPayload() {
325N/A return payloadLocalName!=null;
325N/A }
325N/A
325N/A public Source readPayloadAsSource() {
325N/A if(hasPayload()) {
325N/A assert unconsumed();
325N/A return new StAXSource(reader, true, getInscopeNamespaces());
325N/A } else
325N/A return null;
325N/A }
325N/A
325N/A /**
325N/A * There is no way to enumerate inscope namespaces for XMLStreamReader. That means
325N/A * namespaces declared in envelope, and body tags need to be computed using their
325N/A * {@link TagInfoset}s.
325N/A *
325N/A * @return array of the even length of the form { prefix0, uri0, prefix1, uri1, ... }
325N/A */
325N/A private String[] getInscopeNamespaces() {
325N/A NamespaceSupport nss = new NamespaceSupport();
325N/A
325N/A nss.pushContext();
325N/A for(int i=0; i < envelopeTag.ns.length; i+=2) {
325N/A nss.declarePrefix(envelopeTag.ns[i], envelopeTag.ns[i+1]);
325N/A }
325N/A
325N/A nss.pushContext();
325N/A for(int i=0; i < bodyTag.ns.length; i+=2) {
325N/A nss.declarePrefix(bodyTag.ns[i], bodyTag.ns[i+1]);
325N/A }
325N/A
325N/A List<String> inscope = new ArrayList<String>();
325N/A for( Enumeration en = nss.getPrefixes(); en.hasMoreElements(); ) {
325N/A String prefix = (String)en.nextElement();
325N/A inscope.add(prefix);
325N/A inscope.add(nss.getURI(prefix));
325N/A }
325N/A return inscope.toArray(new String[inscope.size()]);
325N/A }
325N/A
325N/A public Object readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException {
325N/A if(!hasPayload())
325N/A return null;
325N/A assert unconsumed();
325N/A // TODO: How can the unmarshaller process this as a fragment?
325N/A if(hasAttachments())
325N/A unmarshaller.setAttachmentUnmarshaller(new AttachmentUnmarshallerImpl(getAttachments()));
325N/A try {
325N/A return unmarshaller.unmarshal(reader);
325N/A } finally{
325N/A unmarshaller.setAttachmentUnmarshaller(null);
325N/A XMLStreamReaderUtil.readRest(reader);
325N/A XMLStreamReaderUtil.close(reader);
325N/A XMLStreamReaderFactory.recycle(reader);
325N/A }
325N/A }
325N/A
325N/A public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException {
325N/A if(!hasPayload())
325N/A return null;
325N/A assert unconsumed();
325N/A T r = bridge.unmarshal(reader,
325N/A hasAttachments() ? new AttachmentUnmarshallerImpl(getAttachments()) : null);
325N/A XMLStreamReaderUtil.readRest(reader);
325N/A XMLStreamReaderUtil.close(reader);
325N/A XMLStreamReaderFactory.recycle(reader);
325N/A return r;
325N/A }
325N/A
325N/A @Override
325N/A public void consume() {
325N/A assert unconsumed();
325N/A XMLStreamReaderUtil.readRest(reader);
325N/A XMLStreamReaderUtil.close(reader);
325N/A XMLStreamReaderFactory.recycle(reader);
325N/A }
325N/A
325N/A public XMLStreamReader readPayload() {
325N/A if(!hasPayload())
325N/A return null;
325N/A // TODO: What about access at and beyond </soap:Body>
325N/A assert unconsumed();
325N/A return this.reader;
325N/A }
325N/A
325N/A public void writePayloadTo(XMLStreamWriter writer)throws XMLStreamException {
325N/A if(payloadLocalName==null)
325N/A return; // no body
325N/A assert unconsumed();
325N/A XMLStreamReaderToXMLStreamWriter conv = new XMLStreamReaderToXMLStreamWriter();
325N/A while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
325N/A String name = reader.getLocalName();
325N/A String nsUri = reader.getNamespaceURI();
325N/A
325N/A //after previous conv.bridge() call the cursor will be at
325N/A //END_ELEMENT. Check if its not soapenv:Body then move to next
325N/A // ELEMENT
325N/A if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
325N/A if(!name.equals("Body") || !nsUri.equals(soapVersion.nsUri)){
325N/A XMLStreamReaderUtil.nextElementContent(reader);
325N/A if(reader.getEventType() == XMLStreamConstants.END_DOCUMENT)
325N/A break;
325N/A name = reader.getLocalName();
325N/A nsUri = reader.getNamespaceURI();
325N/A }
325N/A }
325N/A if(name.equals("Body") && nsUri.equals(soapVersion.nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
325N/A break;
325N/A conv.bridge(reader,writer);
325N/A }
325N/A XMLStreamReaderUtil.readRest(reader);
325N/A XMLStreamReaderUtil.close(reader);
325N/A XMLStreamReaderFactory.recycle(reader);
325N/A }
325N/A
325N/A public void writeTo(XMLStreamWriter sw) throws XMLStreamException{
325N/A writeEnvelope(sw);
325N/A }
325N/A
325N/A /**
325N/A * This method should be called when the StreamMessage is created with a payload
325N/A * @param writer
325N/A */
325N/A private void writeEnvelope(XMLStreamWriter writer) throws XMLStreamException {
325N/A writer.writeStartDocument();
325N/A envelopeTag.writeStart(writer);
325N/A
325N/A //write headers
325N/A HeaderList hl = getHeaders();
325N/A if(hl.size() > 0){
325N/A headerTag.writeStart(writer);
325N/A for(Header h:hl){
325N/A h.writeTo(writer);
325N/A }
325N/A writer.writeEndElement();
325N/A }
325N/A bodyTag.writeStart(writer);
325N/A if(hasPayload())
325N/A writePayloadTo(writer);
325N/A writer.writeEndElement();
325N/A writer.writeEndElement();
325N/A writer.writeEndDocument();
325N/A }
325N/A
325N/A public void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
325N/A assert unconsumed();
325N/A try {
325N/A if(payloadLocalName==null)
325N/A return; // no body
325N/A
325N/A XMLStreamReaderToContentHandler conv =
325N/A new XMLStreamReaderToContentHandler(reader,contentHandler,true,fragment,getInscopeNamespaces());
325N/A
325N/A while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
325N/A String name = reader.getLocalName();
325N/A String nsUri = reader.getNamespaceURI();
325N/A
325N/A //after previous conv.bridge() call the cursor will be at
325N/A //END_ELEMENT. Check if its not soapenv:Body then move to next
325N/A // ELEMENT
325N/A if(reader.getEventType() == XMLStreamConstants.END_ELEMENT){
325N/A if(!name.equals("Body") || !nsUri.equals(soapVersion.nsUri)){
325N/A XMLStreamReaderUtil.nextElementContent(reader);
325N/A if(reader.getEventType() == XMLStreamConstants.END_DOCUMENT)
325N/A break;
325N/A name = reader.getLocalName();
325N/A nsUri = reader.getNamespaceURI();
325N/A }
325N/A }
325N/A if(name.equals("Body") && nsUri.equals(soapVersion.nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
325N/A break;
325N/A
325N/A conv.bridge();
325N/A }
325N/A XMLStreamReaderUtil.readRest(reader);
325N/A XMLStreamReaderUtil.close(reader);
325N/A XMLStreamReaderFactory.recycle(reader);
325N/A } catch (XMLStreamException e) {
325N/A Location loc = e.getLocation();
325N/A if(loc==null) loc = DummyLocation.INSTANCE;
325N/A
325N/A SAXParseException x = new SAXParseException(
325N/A e.getMessage(),loc.getPublicId(),loc.getSystemId(),loc.getLineNumber(),loc.getColumnNumber(),e);
325N/A errorHandler.error(x);
325N/A }
325N/A }
325N/A
325N/A public Message copy() {
325N/A try {
325N/A assert unconsumed();
325N/A consumedAt = null; // but we don't want to mark it as consumed
325N/A MutableXMLStreamBuffer xsb = new MutableXMLStreamBuffer();
325N/A StreamReaderBufferCreator c = new StreamReaderBufferCreator(xsb);
325N/A
325N/A // preserving inscope namespaces from envelope, and body. Other option
325N/A // would be to create a filtering XMLStreamReader from reader+envelopeTag+bodyTag
325N/A c.storeElement(envelopeTag.nsUri, envelopeTag.localName, envelopeTag.prefix, envelopeTag.ns);
325N/A c.storeElement(bodyTag.nsUri, bodyTag.localName, bodyTag.prefix, bodyTag.ns);
325N/A
325N/A if (hasPayload()) {
325N/A // Loop all the way for multi payload case
325N/A while(reader.getEventType() != XMLStreamConstants.END_DOCUMENT){
325N/A String name = reader.getLocalName();
325N/A String nsUri = reader.getNamespaceURI();
325N/A if(name.equals("Body") && nsUri.equals(soapVersion.nsUri) || (reader.getEventType() == XMLStreamConstants.END_DOCUMENT))
325N/A break;
325N/A c.create(reader);
325N/A // Skip whitespaces in between payload and </Body> or between elements
325N/A if (reader.isWhiteSpace()) {
325N/A XMLStreamReaderUtil.nextElementContent(reader);
325N/A }
325N/A }
325N/A }
325N/A c.storeEndElement(); // create structure element for </Body>
325N/A c.storeEndElement(); // create structure element for </Envelope>
325N/A c.storeEndElement(); // create structure element for END_DOCUMENT
325N/A
325N/A XMLStreamReaderUtil.readRest(reader);
325N/A XMLStreamReaderUtil.close(reader);
325N/A XMLStreamReaderFactory.recycle(reader);
325N/A
325N/A reader = xsb.readAsXMLStreamReader();
325N/A XMLStreamReader clone = xsb.readAsXMLStreamReader();
325N/A
325N/A // advance to the start tag of the <Body> first child element
325N/A proceedToRootElement(reader);
325N/A proceedToRootElement(clone);
325N/A
325N/A return new StreamMessage(envelopeTag, headerTag, attachmentSet, HeaderList.copy(headers), bodyTag, clone, soapVersion);
325N/A } catch (XMLStreamException e) {
325N/A throw new WebServiceException("Failed to copy a message",e);
325N/A }
325N/A }
325N/A
325N/A private void proceedToRootElement(XMLStreamReader xsr) throws XMLStreamException {
325N/A assert xsr.getEventType()==START_DOCUMENT;
325N/A xsr.nextTag();
325N/A xsr.nextTag();
325N/A xsr.nextTag();
325N/A assert xsr.getEventType()==START_ELEMENT || xsr.getEventType()==END_ELEMENT;
325N/A }
325N/A
325N/A public void writeTo( ContentHandler contentHandler, ErrorHandler errorHandler ) throws SAXException {
325N/A contentHandler.setDocumentLocator(NULL_LOCATOR);
325N/A contentHandler.startDocument();
325N/A envelopeTag.writeStart(contentHandler);
325N/A headerTag.writeStart(contentHandler);
325N/A if(hasHeaders()) {
325N/A HeaderList headers = getHeaders();
325N/A int len = headers.size();
325N/A for( int i=0; i<len; i++ ) {
325N/A // shouldn't JDK be smart enough to use array-style indexing for this foreach!?
325N/A headers.get(i).writeTo(contentHandler,errorHandler);
325N/A }
325N/A }
325N/A headerTag.writeEnd(contentHandler);
325N/A bodyTag.writeStart(contentHandler);
325N/A writePayloadTo(contentHandler,errorHandler, true);
325N/A bodyTag.writeEnd(contentHandler);
325N/A envelopeTag.writeEnd(contentHandler);
325N/A
325N/A }
325N/A
325N/A /**
325N/A * Used for an assertion. Returns true when the message is unconsumed,
325N/A * or otherwise throw an exception.
325N/A *
325N/A * <p>
325N/A * Calling this method also marks the stream as 'consumed'
325N/A */
325N/A private boolean unconsumed() {
325N/A if(payloadLocalName==null)
325N/A return true; // no payload. can be consumed multiple times.
325N/A
325N/A if(reader.getEventType()!=XMLStreamReader.START_ELEMENT) {
325N/A AssertionError error = new AssertionError("StreamMessage has been already consumed. See the nested exception for where it's consumed");
325N/A error.initCause(consumedAt);
325N/A throw error;
325N/A }
325N/A consumedAt = new Exception().fillInStackTrace();
325N/A return true;
325N/A }
325N/A
325N/A /**
325N/A * Used only for debugging. This records where the message was consumed.
325N/A */
325N/A private Throwable consumedAt;
325N/A
325N/A /**
325N/A * Default s:Envelope, s:Header, and s:Body tag infoset definitions.
325N/A *
325N/A * We need 3 for SOAP 1.1, 3 for SOAP 1.2.
325N/A */
325N/A private static final TagInfoset[] DEFAULT_TAGS;
325N/A
325N/A static {
325N/A DEFAULT_TAGS = new TagInfoset[6];
325N/A create(SOAPVersion.SOAP_11);
325N/A create(SOAPVersion.SOAP_12);
325N/A }
325N/A
325N/A private static void create(SOAPVersion v) {
325N/A int base = v.ordinal()*3;
325N/A DEFAULT_TAGS[base ] = new TagInfoset(v.nsUri,"Envelope","S",EMPTY_ATTS,"S",v.nsUri);
325N/A DEFAULT_TAGS[base+1] = new TagInfoset(v.nsUri,"Header","S",EMPTY_ATTS);
325N/A DEFAULT_TAGS[base+2] = new TagInfoset(v.nsUri,"Body","S",EMPTY_ATTS);
325N/A }
325N/A}