blob: 8a313d638569d4c32f3e611c1344445daa202921 [file] [log] [blame]
/*
* Copyright 2005-2006 Sun Microsystems, Inc. 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.sun.xml.internal.ws.api.message;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;
import com.sun.xml.internal.bind.api.Bridge;
import com.sun.xml.internal.ws.api.BindingID;
import com.sun.xml.internal.ws.api.SOAPVersion;
import com.sun.xml.internal.ws.api.WSBinding;
import com.sun.xml.internal.ws.api.addressing.AddressingVersion;
import com.sun.xml.internal.ws.api.model.JavaMethod;
import com.sun.xml.internal.ws.api.model.SEIModel;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLBoundOperation;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLBoundPortType;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLPort;
import com.sun.xml.internal.ws.api.pipe.Codec;
import com.sun.xml.internal.ws.api.pipe.Pipe;
import com.sun.xml.internal.ws.api.streaming.XMLStreamReaderFactory;
import com.sun.xml.internal.ws.client.dispatch.DispatchImpl;
import com.sun.xml.internal.ws.message.AttachmentSetImpl;
import com.sun.xml.internal.ws.message.jaxb.JAXBMessage;
import com.sun.xml.internal.org.jvnet.staxex.XMLStreamReaderEx;
import com.sun.xml.internal.org.jvnet.staxex.XMLStreamWriterEx;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;
import javax.xml.ws.Dispatch;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.UUID;
/**
* Represents a SOAP message.
*
*
* <h2>What is a message?</h2>
* <p>
* A {@link Message} consists of the following:
*
* <ol>
* <li>
* Random-accessible list of headers.
* a header is a representation of an element inside
* &lt;soap:Header>.
* It can be read multiple times,
* can be added or removed, but it is not modifiable.
* See {@link HeaderList} for more about headers.
*
* <li>
* The payload of the message, which is a representation
* of an element inside &lt;soap:Body>.
* the payload is streamed, and therefore it can be
* only read once (or can be only written to something once.)
* once a payload is used, a message is said to be <b>consumed</b>.
* A message {@link #hasPayload() may not have any payload.}
*
* <li>
* Attachments.
* TODO: can attachments be streamed? I suspect so.
* does anyone need to read attachment twice?
*
* </ol>
*
*
* <h2>How does this abstraction work?</h2>
* <p>
* The basic idea behind the {@link Message} is to hide the actual
* data representation. For example, a {@link Message} might be
* constructed on top of an {@link InputStream} from the accepted HTTP connection,
* or it might be constructed on top of a JAXB object as a result
* of the method invocation through {@link Proxy}. There will be
* a {@link Message} implementation for each of those cases.
*
* <p>
* This interface provides a lot of methods that access the payload
* in many different forms, and implementations can implement those
* methods in the best possible way.
*
* <p>
* A particular attention is paid to make sure that a {@link Message}
* object can be constructed on a stream that is not fully read yet.
* We believe this improves the turn-around time on the server side.
*
* <p>
* It is often useful to wrap a {@link Message} into another {@link Message},
* for example to encrypt the body, or to verify the signature as the body
* is read.
*
* <p>
* This representation is also used for a REST-ful XML message.
* In such case we'll construct a {@link Message} with empty
* attachments and headers, and when serializing all headers
* and attachments will be ignored.
*
*
*
* <h2>Message and XOP</h2>
* <p>
* XOP is considered as an {@link Codec}, and therefore when you are looking at
* {@link Message}, you'll never see &lt;xop:Include> or any such elements
* (instead you'll see the base64 data inlined.) If a consumer of infoset isn't
* interested in handling XOP by himself, this allows him to work with XOP
* correctly even without noticing it.
*
* <p>
* For producers and consumers that are interested in accessing the binary data
* more efficiently, they can use {@link XMLStreamReaderEx} and
* {@link XMLStreamWriterEx}.
*
*
*
* <h2>Message lifespan</h2>
* <p>
* Often {@link Packet} include information local to a particular
* invocaion (such as {@code HttpServletRequest}, from this angle, it makes sense
* to tie a lifespan of a message to one pipeline invocation.
* <p>
* On the other hand, if you think about WS-RM, it often needs to hold on to
* a message longer than a pipeline invocation (you might get an HTTP request,
* get a message X, get a second HTTP request, get another message Y, and
* only then you might want to process X.)
* <p>
* TODO: what do we do about this?
*
*
* <pre>
* TODO: can body element have foreign attributes? maybe ID for security?
* Yes, when the SOAP body is signed there will be an ID attribute present
* But in this case any security based impl may need access
* to the concrete representation.
* TODO: HTTP headers?
* Yes. Abstracted as transport-based properties.
* TODO: who handles SOAP 1.1 and SOAP 1.2 difference?
* As separate channel implementations responsible for the creation of the
* message?
* TODO: session?
* TODO: Do we need to expose SOAPMessage explicitly?
* SOAPMessage could be the concrete representation but is it necessary to
* transform between different concrete representations?
* Perhaps this comes down to how use channels for creation and processing.
* TODO: Do we need to distinguish better between creation and processing?
* Do we really need the requirement that a created message can be resused
* for processing. Shall we bifurcate?
*
* TODO: SOAP version issue
* SOAP version is determined by the context, so message itself doesn't carry it around (?)
*
* TODO: wrapping message needs easier. in particular properties and attachments.
* </pre>
*
* @author Kohsuke Kawaguchi
*/
public abstract class Message {
/**
* Returns true if headers are present in the message.
*
* @return
* true if headers are present.
*/
public abstract boolean hasHeaders();
/**
* Gets all the headers of this message.
*
* <h3>Implementation Note</h3>
* <p>
* {@link Message} implementation is allowed to defer
* the construction of {@link HeaderList} object. So
* if you only want to check for the existence of any header
* element, use {@link #hasHeaders()}.
*
* @return
* always return the same non-null object.
*/
public abstract @NotNull HeaderList getHeaders();
/**
* Gets the attachments of this message
* (attachments live outside a message.)
*/
public @NotNull AttachmentSet getAttachments() {
if (attachmentSet == null) {
attachmentSet = new AttachmentSetImpl();
}
return attachmentSet;
}
/**
* Optimization hint for the derived class to check
* if we may have some attachments.
*/
protected boolean hasAttachments() {
return attachmentSet!=null;
}
protected AttachmentSet attachmentSet;
private WSDLBoundOperation operation = null;
/**
* Returns the operation of which this message is an instance of.
*
* <p>
* This method relies on {@link WSDLBoundPortType#getOperation(String, String)} but
* it does so in an efficient way.
*
* <p>
* This method works only for a request. A pipe can determine an operation for a request,
* and then keep it in a local variable to use it with a response, so there should be
* no need to find out operation from a response (besides, there might not be any response!).
*
* @param boundPortType
* This represents the port for which this message is used.
* Most {@link Pipe}s should get this information when they are created,
* since a pippeline always work against a particular type of {@link WSDLPort}.
*
* @return
* Null if the operation was not found. This is possible, for example when a protocol
* message is sent through a pipeline, or when we receive an invalid request on the server,
* or when we are on the client and the user appliation sends a random DOM through
* {@link Dispatch}, so this error needs to be handled gracefully.
*/
public final @Nullable WSDLBoundOperation getOperation(@NotNull WSDLBoundPortType boundPortType) {
if(operation==null)
operation = boundPortType.getOperation(getPayloadNamespaceURI(),getPayloadLocalPart());
return operation;
}
/**
* The same as {@link #getOperation(WSDLBoundPortType)} but
* takes {@link WSDLPort} for convenience.
*/
public final @Nullable WSDLBoundOperation getOperation(@NotNull WSDLPort port) {
return getOperation(port.getBinding());
}
/**
* Returns the java Method of which this message is an instance of.
*
*
* <p>
* This method works only for a request. A pipe can determine a {@link Method}
* for a request, and then keep it in a local variable to use it with a response,
* so there should be no need to find out operation from a response (besides,
* there might not be any response!).
*
* @param seiModel
* This represents the java model for the endpoint
* Some server {@link Pipe}s would get this information when they are created.
*
* @return
* Null if there is no corresponding Method for this message. This is
* possible, for example when a protocol message is sent through a
* pipeline, or when we receive an invalid request on the server,
* or when we are on the client and the user appliation sends a random
* DOM through {@link Dispatch}, so this error needs to be handled
* gracefully.
*/
public final @Nullable JavaMethod getMethod(@NotNull SEIModel seiModel) {
String localPart = getPayloadLocalPart();
String nsUri;
if (localPart == null) {
localPart = "";
nsUri = "";
} else {
nsUri = getPayloadNamespaceURI();
}
QName name = new QName(nsUri, localPart);
return seiModel.getJavaMethod(name);
}
private Boolean isOneWay;
/**
* Returns true if this message is a request message for a
* one way operation according to the given WSDL. False otherwise.
*
* <p>
* This method is functionally equivalent as doing
* {@code getOperation(port).getOperation().isOneWay()}
* (with proper null check and all.) But this method
* can sometimes work faster than that (for example,
* on the client side when used with SEI.)
*
* @param port
* {@link Message}s are always created under the context of
* one {@link WSDLPort} and they never go outside that context.
* Pass in that "governing" {@link WSDLPort} object here.
* We chose to receive this as a parameter instead of
* keeping {@link WSDLPort} in a message, just to save the storage.
*
* <p>
* The implementation of this method involves caching the return
* value, so the behavior is undefined if multiple callers provide
* different {@link WSDLPort} objects, which is a bug of the caller.
*/
public boolean isOneWay(@NotNull WSDLPort port) {
if(isOneWay==null) {
// we don't know, so compute.
WSDLBoundOperation op = getOperation(port);
if(op!=null)
isOneWay = op.getOperation().isOneWay();
else
// the contract is to return true only when it's known to be one way.
isOneWay = false;
}
return isOneWay;
}
/**
* Makes an assertion that this {@link Message} is
* a request message for an one-way operation according
* to the context WSDL.
*
* <p>
* This method is really only intended to be invoked from within
* the JAX-WS runtime, and not by any code building on top of it.
*
* <p>
* This method can be invoked only when the caller "knows" what
* WSDL says. Also, there's no point in invoking this method if the caller
* is doing {@code getOperation(port).getOperation().isOneWay()},
* or sniffing the payload tag name.
* In particular, this includes {@link DispatchImpl}.
*
* <p>
* Once called, this allows {@link #isOneWay(WSDLPort)} method
* to return a value quickly.
*
* @see #isOneWay(WSDLPort)
*/
public final void assertOneWay(boolean value) {
// if two callers make different assertions, that's a bug.
// this is an assertion, not a runtime check because
// nobody outside JAX-WS should be using this.
assert isOneWay==null || isOneWay==value;
isOneWay = value;
}
/**
* Gets the local name of the payload element.
*
* @return
* null if a {@link Message} doesn't have any payload.
*/
public abstract @Nullable String getPayloadLocalPart();
/**
* Gets the namespace URI of the payload element.
*
* @return
* null if a {@link Message} doesn't have any payload.
*/
public abstract String getPayloadNamespaceURI();
// I'm not putting @Nullable on it because doing null check on getPayloadLocalPart() should be suffice
/**
* Returns true if a {@link Message} has a payload.
*
* <p>
* A message without a payload is a SOAP message that looks like:
* <pre><xmp>
* <S:Envelope>
* <S:Header>
* ...
* </S:Header>
* <S:Body />
* </S:Envelope>
* </xmp></pre>
*/
public abstract boolean hasPayload();
/**
* Returns true if this message is a fault.
*
* <p>
* Just a convenience method built on {@link #getPayloadNamespaceURI()}
* and {@link #getPayloadLocalPart()}.
*/
public boolean isFault() {
// TODO: is SOAP version a property of a Message?
// or is it defined by external factors?
// how do I compare?
String localPart = getPayloadLocalPart();
if(localPart==null || !localPart.equals("Fault"))
return false;
String nsUri = getPayloadNamespaceURI();
return nsUri.equals(SOAPVersion.SOAP_11.nsUri) || nsUri.equals(SOAPVersion.SOAP_12.nsUri);
}
/**
* Consumes this message including the envelope.
* returns it as a {@link Source} object.
*/
public abstract Source readEnvelopeAsSource();
/**
* Returns the payload as a {@link Source} object.
*
* This consumes the message.
*
* @return
* if there's no payload, this method returns null.
*/
public abstract Source readPayloadAsSource();
/**
* Creates the equivalent {@link SOAPMessage} from this message.
*
* This consumes the message.
*
* @throws SOAPException
* if there's any error while creating a {@link SOAPMessage}.
*/
public abstract SOAPMessage readAsSOAPMessage() throws SOAPException ;
/**
* Creates the equivalent {@link SOAPMessage} from this message. It also uses
* transport specific headers from Packet during the SOAPMessage construction
* so that {@link SOAPMessage#getMimeHeaders()} gives meaningful transport
* headers.
*
* This consumes the message.
*
* @throws SOAPException
* if there's any error while creating a {@link SOAPMessage}.
*/
public SOAPMessage readAsSOAPMessage(Packet packet, boolean inbound) throws SOAPException {
return readAsSOAPMessage();
}
/**
* Reads the payload as a JAXB object by using the given unmarshaller.
*
* This consumes the message.
*
* @throws JAXBException
* If JAXB reports an error during the processing.
*/
public abstract <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException;
/**
* Reads the payload as a JAXB object according to the given {@link Bridge}.
*
* This consumes the message.
*
* @throws JAXBException
* If JAXB reports an error during the processing.
*/
public abstract <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException;
/**
* Reads the payload as a {@link XMLStreamReader}
*
* This consumes the message. The caller is encouraged to call
* {@link XMLStreamReaderFactory#recycle(XMLStreamReader)} when finished using
* the instance.
*
* @return
* If there's no payload, this method returns null.
* Otherwise always non-null valid {@link XMLStreamReader} that points to
* the payload tag name.
*/
public abstract XMLStreamReader readPayload() throws XMLStreamException;
/**
* Marks the message as consumed, without actually reading the contents.
*
* <p>
* This method provides an opportunity for implementations to reuse
* any reusable resources needed for representing the payload.
*/
public void consume() {}
/**
* Writes the payload to StAX.
*
* This method writes just the payload of the message to the writer.
* This consumes the message.
* The implementation will not write
* {@link XMLStreamWriter#writeStartDocument()}
* nor
* {@link XMLStreamWriter#writeEndDocument()}
*
* <p>
* If there's no payload, this method is no-op.
*
* @throws XMLStreamException
* If the {@link XMLStreamWriter} reports an error,
* or some other errors happen during the processing.
*/
public abstract void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException;
/**
* Writes the whole SOAP message (but not attachments)
* to the given writer.
*
* This consumes the message.
*
* @throws XMLStreamException
* If the {@link XMLStreamWriter} reports an error,
* or some other errors happen during the processing.
*/
public abstract void writeTo(XMLStreamWriter sw) throws XMLStreamException;
/**
* Writes the whole SOAP envelope as SAX events.
*
* <p>
* This consumes the message.
*
* @param contentHandler
* must not be nulll.
* @param errorHandler
* must not be null.
* any error encountered during the SAX event production must be
* first reported to this error handler. Fatal errors can be then
* thrown as {@link SAXParseException}. {@link SAXException}s thrown
* from {@link ErrorHandler} should propagate directly through this method.
*/
public abstract void writeTo( ContentHandler contentHandler, ErrorHandler errorHandler ) throws SAXException;
// TODO: do we need a method that reads payload as a fault?
// do we want a separte streaming representation of fault?
// or would SOAPFault in SAAJ do?
/**
* Creates a copy of a {@link Message}.
*
* <p>
* This method creates a new {@link Message} whose header/payload/attachments/properties
* are identical to this {@link Message}. Once created, the created {@link Message}
* and the original {@link Message} behaves independently --- adding header/
* attachment to one {@link Message} doesn't affect another {@link Message}
* at all.
*
* <p>
* This method does <b>NOT</b> consume a message.
*
* <p>
* To enable efficient copy operations, there's a few restrictions on
* how copied message can be used.
*
* <ol>
* <li>The original and the copy may not be
* used concurrently by two threads (this allows two {@link Message}s
* to share some internal resources, such as JAXB marshallers.)
* Note that it's OK for the original and the copy to be processed
* by two threads, as long as they are not concurrent.
*
* <li>The copy has the same 'life scope'
* as the original (this allows shallower copy, such as
* JAXB beans wrapped in {@link JAXBMessage}.)
* </ol>
*
* <p>
* A 'life scope' of a message created during a message processing
* in a pipeline is until a pipeline processes the next message.
* A message cannot be kept beyond its life scope.
*
* (This experimental design is to allow message objects to be reused
* --- feedback appreciated.)
*
*
*
* <h3>Design Rationale</h3>
* <p>
* Since a {@link Message} body is read-once, sometimes
* (such as when you do fail-over, or WS-RM) you need to
* create an idential copy of a {@link Message}.
*
* <p>
* The actual copy operation depends on the layout
* of the data in memory, hence it's best to be done by
* the {@link Message} implementation itself.
*
* <p>
* The restrictions placed on the use of copied {@link Message} can be
* relaxed if necessary, but it will make the copy method more expensive.
*/
// TODO: update the class javadoc with 'lifescope'
// and move the discussion about life scope there.
public abstract Message copy();
private String uuid;
/**
* Retuns a unique id for the message. The id can be used for various things,
* like debug assistance, logging, and MIME encoding(say for boundary).
*
* <p>
* This method will check the existence of the addressing <MessageID> header,
* and if present uses that value. Otherwise it generates one from UUID.random(),
* and return it without adding a new header. But it doesn't add a <MessageID>
* to the header list since we expect them to be added before calling this
* method.
*
* <p>
* Addressing tube will go do a separate verification on inbound
* headers to make sure that <MessageID> header is present when it's
* supposed to be.
*
* @param binding object created by {@link BindingID#createBinding()}
*
* @return unique id for the message
*/
public @NotNull String getID(@NotNull WSBinding binding) {
return getID(binding.getAddressingVersion(), binding.getSOAPVersion());
}
/**
* Retuns a unique id for the message.
* <p><p>
* @see {@link #getID(com.sun.xml.internal.ws.api.WSBinding)} for detailed description.
* @param av WS-Addressing version
* @param sv SOAP version
* @return unique id for the message
*/
public @NotNull String getID(AddressingVersion av, SOAPVersion sv) {
if (uuid == null) {
if (av != null) {
uuid = getHeaders().getMessageID(av, sv);
}
if (uuid == null) {
uuid = "uuid:" + UUID.randomUUID().toString();
}
}
return uuid;
}
}