com.sun.xml.ws.message.saaj.SAAJMessage Maven / Gradle / Ivy
Show all versions of jaxws-rt Show documentation
/*
* Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0, which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package com.sun.xml.ws.message.saaj;
import com.sun.istack.FragmentContentHandler;
import com.sun.istack.NotNull;
import com.sun.istack.Nullable;
import com.sun.istack.XMLStreamException2;
import org.glassfish.jaxb.core.unmarshaller.DOMScanner;
import com.sun.xml.ws.api.SOAPVersion;
import com.sun.xml.ws.api.message.*;
import com.sun.xml.ws.message.AttachmentUnmarshallerImpl;
import com.sun.xml.ws.spi.db.XMLBridge;
import com.sun.xml.ws.streaming.DOMStreamReader;
import com.sun.xml.ws.util.ASCIIUtility;
import com.sun.xml.ws.util.DOMUtil;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.LocatorImpl;
import jakarta.activation.DataHandler;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.soap.*;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import jakarta.xml.ws.WebServiceException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* {@link Message} implementation backed by {@link SOAPMessage}.
*
* @author Vivek Pandey
* @author Rama Pulavarthi
*/
public class SAAJMessage extends Message {
// flag to switch between representations
private boolean parsedMessage;
// flag to check if Message API is exercised;
private boolean accessedMessage;
private final SOAPMessage sm;
private MessageHeaders headers;
private List bodyParts;
private Element payload;
private String payloadLocalName;
private String payloadNamespace;
private SOAPVersion soapVersion;
//Collect the attrbutes on the enclosing elements so that the same message can be reproduced without loss of any
// valuable info
private NamedNodeMap bodyAttrs, headerAttrs, envelopeAttrs;
public SAAJMessage(SOAPMessage sm) {
this.sm = sm;
}
/**
* This constructor is a convenience and called by the {@link #copy}
*
*/
private SAAJMessage(MessageHeaders headers, AttachmentSet as, SOAPMessage sm, SOAPVersion version) {
this.sm = sm;
this.parse();
if (headers == null) {
headers = new HeaderList(version);
}
this.headers = headers;
this.attachmentSet = as;
}
private void parse() {
if (!parsedMessage) {
try {
access();
if (headers == null) {
headers = new HeaderList(getSOAPVersion());
}
SOAPHeader header = sm.getSOAPHeader();
if (header != null) {
headerAttrs = header.getAttributes();
Iterator iter = header.examineAllHeaderElements();
while (iter.hasNext()) {
headers.add(new SAAJHeader((SOAPHeaderElement) iter.next()));
}
}
attachmentSet = new SAAJAttachmentSet(sm);
parsedMessage = true;
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
}
protected void access() {
if (!accessedMessage) {
try {
envelopeAttrs = sm.getSOAPPart().getEnvelope().getAttributes();
Node body = sm.getSOAPBody();
bodyAttrs = body.getAttributes();
soapVersion = SOAPVersion.fromNsUri(body.getNamespaceURI());
//cature all the body elements
bodyParts = DOMUtil.getChildElements(body);
//we treat payload as the first body part
payload = bodyParts.size() > 0 ? bodyParts.get(0) : null;
// hope this is correct. Caching the localname and namespace of the payload should be fine
// but what about if a Handler replaces the payload with something else? Weel, may be it
// will be error condition anyway
if (payload != null) {
payloadLocalName = payload.getLocalName();
payloadNamespace = payload.getNamespaceURI();
}
accessedMessage = true;
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
}
@Override
public boolean hasHeaders() {
parse();
return headers.hasHeaders();
}
@Override
public @NotNull
MessageHeaders getHeaders() {
parse();
return headers;
}
/**
* Gets the attachments of this message (attachments live outside a
* message.)
*/
@Override
public @NotNull
AttachmentSet getAttachments() {
if (attachmentSet == null) {
attachmentSet = new SAAJAttachmentSet(sm);
}
return attachmentSet;
}
/**
* Optimization hint for the derived class to check if we may have some
* attachments.
*/
@Override
protected boolean hasAttachments() {
return !getAttachments().isEmpty();
}
@Override
public @Nullable
String getPayloadLocalPart() {
soapBodyFirstChild();
return payloadLocalName;
}
@Override
public String getPayloadNamespaceURI() {
soapBodyFirstChild();
return payloadNamespace;
}
@Override
public boolean hasPayload() {
return soapBodyFirstChild() != null;
}
private void addAttributes(Element e, NamedNodeMap attrs) {
if (attrs == null) {
return;
}
String elPrefix = e.getPrefix();
for (int i = 0; i < attrs.getLength(); i++) {
Attr a = (Attr) attrs.item(i);
//check if attr is ns declaration
if ("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
if (elPrefix == null && a.getLocalName().equals("xmlns")) {
// the target element has already default ns declaration, dont' override it
continue;
} else if (elPrefix != null && "xmlns".equals(a.getPrefix()) && elPrefix.equals(a.getLocalName())) {
//dont bind the prefix to ns again, its already in the target element.
continue;
}
e.setAttributeNS(a.getNamespaceURI(), a.getName(), a.getValue());
continue;
}
e.setAttributeNS(a.getNamespaceURI(), a.getName(), a.getValue());
}
}
@Override
public Source readEnvelopeAsSource() {
try {
if (!parsedMessage) {
SOAPEnvelope se = sm.getSOAPPart().getEnvelope();
return new DOMSource(se);
} else {
SOAPMessage msg = soapVersion.getMessageFactory().createMessage();
addAttributes(msg.getSOAPPart().getEnvelope(), envelopeAttrs);
SOAPBody newBody = msg.getSOAPPart().getEnvelope().getBody();
addAttributes(newBody, bodyAttrs);
for (Element part : bodyParts) {
Node n = newBody.getOwnerDocument().importNode(part, true);
newBody.appendChild(n);
}
addAttributes(msg.getSOAPHeader(), headerAttrs);
for (Header header : headers.asList()) {
header.writeTo(msg);
}
SOAPEnvelope se = msg.getSOAPPart().getEnvelope();
return new DOMSource(se);
}
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
@Override
public SOAPMessage readAsSOAPMessage() throws SOAPException {
if (!parsedMessage) {
return sm;
} else {
SOAPMessage msg = soapVersion.getMessageFactory().createMessage();
addAttributes(msg.getSOAPPart().getEnvelope(), envelopeAttrs);
SOAPBody newBody = msg.getSOAPPart().getEnvelope().getBody();
addAttributes(newBody, bodyAttrs);
for (Element part : bodyParts) {
Node n = newBody.getOwnerDocument().importNode(part, true);
newBody.appendChild(n);
}
addAttributes(msg.getSOAPHeader(), headerAttrs);
for (Header header : headers.asList()) {
header.writeTo(msg);
}
for (Attachment att : getAttachments()) {
AttachmentPart part = msg.createAttachmentPart();
part.setDataHandler(att.asDataHandler());
part.setContentId('<' + att.getContentId() + '>');
addCustomMimeHeaders(att, part);
msg.addAttachmentPart(part);
}
msg.saveChanges();
return msg;
}
}
private void addCustomMimeHeaders(Attachment att, AttachmentPart part) {
if (att instanceof AttachmentEx) {
Iterator allMimeHeaders = ((AttachmentEx) att).getMimeHeaders();
while (allMimeHeaders.hasNext()) {
AttachmentEx.MimeHeader mh = allMimeHeaders.next();
String name = mh.getName();
if (!"Content-Type".equalsIgnoreCase(name)
&& !"Content-ID".equalsIgnoreCase(name)) {
part.addMimeHeader(name, mh.getValue());
}
}
}
}
@Override
public Source readPayloadAsSource() {
access();
return (payload != null) ? new DOMSource(payload) : null;
}
@Override
@SuppressWarnings({"unchecked"})
public T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException {
access();
if (payload != null) {
if (hasAttachments()) {
unmarshaller.setAttachmentUnmarshaller(new AttachmentUnmarshallerImpl(getAttachments()));
}
return (T) unmarshaller.unmarshal(payload);
}
return null;
}
@Override
public T readPayloadAsJAXB(XMLBridge bridge) throws JAXBException {
access();
if (payload != null) {
return bridge.unmarshal(payload, hasAttachments() ? new AttachmentUnmarshallerImpl(getAttachments()) : null);
}
return null;
}
@Override
public XMLStreamReader readPayload() throws XMLStreamException {
return soapBodyFirstChildReader();
}
@Override
public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException {
access();
try {
for (Element part : bodyParts) {
DOMUtil.serializeNode(part, sw);
}
} catch (XMLStreamException e) {
throw new WebServiceException(e);
}
}
@Override
public void writeTo(XMLStreamWriter writer) throws XMLStreamException {
try {
writer.writeStartDocument();
if (!parsedMessage) {
DOMUtil.serializeNode(sm.getSOAPPart().getEnvelope(), writer);
} else {
SOAPEnvelope env = sm.getSOAPPart().getEnvelope();
DOMUtil.writeTagWithAttributes(env, writer);
if (hasHeaders()) {
if (env.getHeader() != null) {
DOMUtil.writeTagWithAttributes(env.getHeader(), writer);
} else {
writer.writeStartElement(env.getPrefix(), "Header", env.getNamespaceURI());
}
for (Header h : headers.asList()) {
h.writeTo(writer);
}
writer.writeEndElement();
}
DOMUtil.serializeNode(sm.getSOAPBody(), writer);
writer.writeEndElement();
}
writer.writeEndDocument();
writer.flush();
} catch (SOAPException ex) {
throw new XMLStreamException2(ex);
//for now. ask jaxws team what to do.
}
}
@Override
public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler) throws SAXException {
String soapNsUri = soapVersion.nsUri;
if (!parsedMessage) {
DOMScanner ds = new DOMScanner();
ds.setContentHandler(contentHandler);
ds.scan(sm.getSOAPPart());
} else {
contentHandler.setDocumentLocator(NULL_LOCATOR);
contentHandler.startDocument();
contentHandler.startPrefixMapping("S", soapNsUri);
startPrefixMapping(contentHandler, envelopeAttrs, "S");
contentHandler.startElement(soapNsUri, "Envelope", "S:Envelope", getAttributes(envelopeAttrs));
if (hasHeaders()) {
startPrefixMapping(contentHandler, headerAttrs, "S");
contentHandler.startElement(soapNsUri, "Header", "S:Header", getAttributes(headerAttrs));
MessageHeaders headers = getHeaders();
for (Header h : headers.asList()) {
h.writeTo(contentHandler, errorHandler);
}
endPrefixMapping(contentHandler, headerAttrs, "S");
contentHandler.endElement(soapNsUri, "Header", "S:Header");
}
startPrefixMapping(contentHandler, bodyAttrs, "S");
// write the body
contentHandler.startElement(soapNsUri, "Body", "S:Body", getAttributes(bodyAttrs));
writePayloadTo(contentHandler, errorHandler, true);
endPrefixMapping(contentHandler, bodyAttrs, "S");
contentHandler.endElement(soapNsUri, "Body", "S:Body");
endPrefixMapping(contentHandler, envelopeAttrs, "S");
contentHandler.endElement(soapNsUri, "Envelope", "S:Envelope");
}
}
/**
* Gets the Attributes that are not namesapce declarations
*
*/
private AttributesImpl getAttributes(NamedNodeMap attrs) {
AttributesImpl atts = new AttributesImpl();
if (attrs == null) {
return EMPTY_ATTS;
}
for (int i = 0; i < attrs.getLength(); i++) {
Attr a = (Attr) attrs.item(i);
//check if attr is ns declaration
if ("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
continue;
}
atts.addAttribute(fixNull(a.getNamespaceURI()), a.getLocalName(), a.getName(), a.getSchemaTypeInfo().getTypeName(), a.getValue());
}
return atts;
}
/**
* Collects the ns declarations and starts the prefix mapping, consequently
* the associated endPrefixMapping needs to be called.
*
* @param excludePrefix , this is to excldue the global prefix mapping "S"
* used at the start
*/
private void startPrefixMapping(ContentHandler contentHandler, NamedNodeMap attrs, String excludePrefix) throws SAXException {
if (attrs == null) {
return;
}
for (int i = 0; i < attrs.getLength(); i++) {
Attr a = (Attr) attrs.item(i);
//check if attr is ns declaration
if ("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
if (!fixNull(a.getPrefix()).equals(excludePrefix)) {
contentHandler.startPrefixMapping(fixNull(a.getPrefix()), a.getNamespaceURI());
}
}
}
}
private void endPrefixMapping(ContentHandler contentHandler, NamedNodeMap attrs, String excludePrefix) throws SAXException {
if (attrs == null) {
return;
}
for (int i = 0; i < attrs.getLength(); i++) {
Attr a = (Attr) attrs.item(i);
//check if attr is ns declaration
if ("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
if (!fixNull(a.getPrefix()).equals(excludePrefix)) {
contentHandler.endPrefixMapping(fixNull(a.getPrefix()));
}
}
}
}
private static String fixNull(String s) {
if (s == null) {
return "";
} else {
return s;
}
}
private void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
if (fragment) {
contentHandler = new FragmentContentHandler(contentHandler);
}
DOMScanner ds = new DOMScanner();
ds.setContentHandler(contentHandler);
ds.scan(payload);
}
/**
* Creates a copy of a {@link com.sun.xml.ws.api.message.Message}.
*
*
* This method creates a new {@link com.sun.xml.ws.api.message.Message}
* whose header/payload/attachments/properties are identical to this
* {@link com.sun.xml.ws.api.message.Message}. Once created, the created
* {@link com.sun.xml.ws.api.message.Message} and the original
* {@link com.sun.xml.ws.api.message.Message} behaves independently ---
* adding header/ attachment to one
* {@link com.sun.xml.ws.api.message.Message} doesn't affect another
* {@link com.sun.xml.ws.api.message.Message} at all.
*
* Design Rationale
*
* Since a {@link com.sun.xml.ws.api.message.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 com.sun.xml.ws.api.message.Message}.
*
*
* The actual copy operation depends on the layout of the data in memory,
* hence it's best to be done by the
* {@link com.sun.xml.ws.api.message.Message} implementation itself.
*/
@Override
public Message copy() {
Message result = null;
try {
access();
if (!parsedMessage) {
result = new SAAJMessage(readAsSOAPMessage());
} else {
SOAPMessage msg = soapVersion.getMessageFactory().createMessage();
SOAPBody newBody = msg.getSOAPPart().getEnvelope().getBody();
for (Element part : bodyParts) {
Node n = newBody.getOwnerDocument().importNode(part, true);
newBody.appendChild(n);
}
addAttributes(newBody, bodyAttrs);
result = new SAAJMessage(getHeaders(), getAttachments(), msg, soapVersion);
}
return result.copyFrom(this);
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
private static final AttributesImpl EMPTY_ATTS = new AttributesImpl();
private static final LocatorImpl NULL_LOCATOR = new LocatorImpl();
protected static class SAAJAttachment implements AttachmentEx {
final AttachmentPart ap;
String contentIdNoAngleBracket;
public SAAJAttachment(AttachmentPart part) {
this.ap = part;
}
/**
* Content ID of the attachment. Uniquely identifies an attachment.
*/
@Override
public String getContentId() {
if (contentIdNoAngleBracket == null) {
contentIdNoAngleBracket = ap.getContentId();
if (contentIdNoAngleBracket != null && contentIdNoAngleBracket.charAt(0) == '<') {
contentIdNoAngleBracket = contentIdNoAngleBracket.substring(1, contentIdNoAngleBracket.length() - 1);
}
}
return contentIdNoAngleBracket;
}
/**
* Gets the MIME content-type of this attachment.
*/
@Override
public String getContentType() {
return ap.getContentType();
}
/**
* Gets the attachment as an exact-length byte array.
*/
@Override
public byte[] asByteArray() {
try {
return ap.getRawContentBytes();
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
/**
* Gets the attachment as a {@link jakarta.activation.DataHandler}.
*/
@Override
public DataHandler asDataHandler() {
try {
return ap.getDataHandler();
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
/**
* Gets the attachment as a {@link javax.xml.transform.Source}. Note
* that there's no guarantee that the attachment is actually an XML.
*/
@Override
public Source asSource() {
try {
return new StreamSource(ap.getRawContent());
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
/**
* Obtains this attachment as an {@link java.io.InputStream}.
*/
@Override
public InputStream asInputStream() {
try {
return ap.getRawContent();
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
/**
* Writes the contents of the attachment into the given stream.
*/
@Override
public void writeTo(OutputStream os) throws IOException {
try {
ASCIIUtility.copyStream(ap.getRawContent(), os);
} catch (SOAPException e) {
throw new WebServiceException(e);
}
}
/**
* Writes this attachment to the given
* {@link jakarta.xml.soap.SOAPMessage}.
*/
@Override
public void writeTo(SOAPMessage saaj) {
saaj.addAttachmentPart(ap);
}
AttachmentPart asAttachmentPart() {
return ap;
}
@Override
public Iterator getMimeHeaders() {
final Iterator it = ap.getAllMimeHeaders();
return new Iterator<>() {
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public MimeHeader next() {
final jakarta.xml.soap.MimeHeader mh = (jakarta.xml.soap.MimeHeader) it.next();
return new MimeHeader() {
@Override
public String getName() {
return mh.getName();
}
@Override
public String getValue() {
return mh.getValue();
}
};
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* {@link AttachmentSet} for SAAJ.
*
* SAAJ wants '<' and '>' for the content ID, but
* {@link AttachmentSet} doesn't. S this class also does the conversion
* between them.
*/
protected static class SAAJAttachmentSet implements AttachmentSet {
private Map attMap;
private Iterator attIter;
public SAAJAttachmentSet(SOAPMessage sm) {
attIter = sm.getAttachments();
}
/**
* Gets the attachment by the content ID.
*
* @return null if no such attachment exist.
*/
@Override
public Attachment get(String contentId) {
// if this is the first time then create the attachment Map
if (attMap == null) {
if (!attIter.hasNext()) {
return null;
}
attMap = createAttachmentMap();
}
if (contentId.charAt(0) != '<') {
return attMap.get('<' + contentId + '>');
}
return attMap.get(contentId);
}
@Override
public boolean isEmpty() {
if (attMap != null) {
return attMap.isEmpty();
} else {
return !attIter.hasNext();
}
}
/**
* Returns an iterator over a set of elements of type T.
*
* @return an Iterator.
*/
@Override
public Iterator iterator() {
if (attMap == null) {
attMap = createAttachmentMap();
}
return attMap.values().iterator();
}
private Map createAttachmentMap() {
HashMap map = new HashMap<>();
while (attIter.hasNext()) {
AttachmentPart ap = (AttachmentPart) attIter.next();
map.put(ap.getContentId(), new SAAJAttachment(ap));
}
return map;
}
@Override
public void add(Attachment att) {
attMap.put('<' + att.getContentId() + '>', att);
}
}
@Override
public SOAPVersion getSOAPVersion() {
return soapVersion;
}
private XMLStreamReader soapBodyFirstChildReader;
/**
* This allow the subclass to retain the XMLStreamReader.
*/
protected XMLStreamReader getXMLStreamReader(SOAPElement soapElement) {
return null;
}
protected XMLStreamReader createXMLStreamReader(SOAPElement soapElement) {
DOMStreamReader dss = new DOMStreamReader();
dss.setCurrentNode(soapElement);
return dss;
}
protected XMLStreamReader soapBodyFirstChildReader() {
if (soapBodyFirstChildReader != null) {
return soapBodyFirstChildReader;
}
soapBodyFirstChild();
if (soapBodyFirstChild != null) {
soapBodyFirstChildReader = getXMLStreamReader(soapBodyFirstChild);
if (soapBodyFirstChildReader == null) {
soapBodyFirstChildReader
= createXMLStreamReader(soapBodyFirstChild);
}
if (soapBodyFirstChildReader.getEventType() == XMLStreamReader.START_DOCUMENT) {
try {
while (soapBodyFirstChildReader.getEventType() != XMLStreamReader.START_ELEMENT) {
soapBodyFirstChildReader.next();
}
} catch (XMLStreamException e) {
throw new RuntimeException(e);
}
}
return soapBodyFirstChildReader;
} else {
payloadLocalName = null;
payloadNamespace = null;
return null;
}
}
private SOAPElement soapBodyFirstChild;
SOAPElement soapBodyFirstChild() {
if (soapBodyFirstChild != null) {
return soapBodyFirstChild;
}
try {
boolean foundElement = false;
for (Node n = sm.getSOAPBody().getFirstChild(); n != null && !foundElement; n = n.getNextSibling()) {
if (n.getNodeType() == Node.ELEMENT_NODE) {
foundElement = true;
if (n instanceof SOAPElement) {
soapBodyFirstChild = (SOAPElement) n;
payloadLocalName = soapBodyFirstChild.getLocalName();
payloadNamespace = soapBodyFirstChild.getNamespaceURI();
return soapBodyFirstChild;
}
}
}
if (foundElement) {
for (Iterator i = sm.getSOAPBody().getChildElements(); i.hasNext();) {
Object o = i.next();
if (o instanceof SOAPElement) {
soapBodyFirstChild = (SOAPElement) o;
payloadLocalName = soapBodyFirstChild.getLocalName();
payloadNamespace = soapBodyFirstChild.getNamespaceURI();
return soapBodyFirstChild;
}
}
}
} catch (SOAPException e) {
throw new RuntimeException(e);
}
return soapBodyFirstChild;
}
}