org.apache.axis2.saaj.SOAPMessageImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of axis2-saaj Show documentation
Show all versions of axis2-saaj Show documentation
Axis2 SAAJ implementation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.axis2.saaj;
import org.apache.axiom.attachments.Attachments;
import org.apache.axiom.mime.ContentTypeBuilder;
import org.apache.axiom.mime.MediaType;
import org.apache.axiom.om.OMException;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axiom.om.impl.OMMultipartWriter;
import org.apache.axiom.soap.SOAP11Version;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axiom.util.UIDGenerator;
import org.apache.axis2.saaj.util.SAAJUtil;
import org.apache.axis2.transport.http.HTTPConstants;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.MimeHeader;
import javax.xml.soap.MimeHeaders;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPElement;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
public class SOAPMessageImpl extends SOAPMessage {
private SOAPPart soapPart;
private Collection attachmentParts = new ArrayList();
private MimeHeaders mimeHeaders;
private Map props = new Hashtable();
private boolean saveRequired;
public SOAPMessageImpl(SOAPEnvelopeImpl soapEnvelope) {
this.mimeHeaders = new MimeHeaders();
this.mimeHeaders.addHeader("content-type", ((SOAPFactory)soapEnvelope.omTarget.getOMFactory()).getSOAPVersion().getMediaType().toString());
soapPart = new SOAPPartImpl(this, soapEnvelope);
}
public SOAPMessageImpl(InputStream inputstream, MimeHeaders mimeHeaders, boolean processMTOM)
throws SOAPException {
String contentType = null;
String tmpContentType = "";
if (mimeHeaders != null) {
String contentTypes[] = mimeHeaders.getHeader(HTTPConstants.HEADER_CONTENT_TYPE);
if (contentTypes != null && contentTypes.length > 0) {
tmpContentType = contentTypes[0];
contentType = SAAJUtil.normalizeContentType(tmpContentType);
}
}
if (HTTPConstants.MEDIA_TYPE_MULTIPART_RELATED.equals(contentType)) {
try {
Attachments attachments =
new Attachments(inputstream, tmpContentType, false, "", "");
// Axiom doesn't give us access to the MIME headers of the individual
// parts of the SOAP message package. We need to reconstruct them from
// the available information.
MimeHeaders soapPartHeaders = new MimeHeaders();
soapPartHeaders.addHeader(HTTPConstants.HEADER_CONTENT_TYPE,
attachments.getRootPartContentType());
String soapPartContentId = attachments.getRootPartContentID();
soapPartHeaders.addHeader("Content-ID", "<" + soapPartContentId + ">");
soapPart = new SOAPPartImpl(this, attachments.getRootPartInputStream(),
soapPartHeaders, processMTOM ? attachments : null);
for (String contentId : attachments.getAllContentIDs()) {
if (!contentId.equals(soapPartContentId)) {
AttachmentPart ap =
createAttachmentPart(attachments.getDataHandler(contentId));
ap.setContentId("<" + contentId + ">");
attachmentParts.add(ap);
}
}
} catch (OMException e) {
throw new SOAPException(e);
}
} else {
initCharsetEncodingFromContentType(tmpContentType);
soapPart = new SOAPPartImpl(this, inputstream, mimeHeaders, null);
}
this.mimeHeaders = (mimeHeaders == null) ?
new MimeHeaders() :
SAAJUtil.copyMimeHeaders(mimeHeaders);
}
/**
* Retrieves a description of this SOAPMessage
object's content.
*
* @return a String
describing the content of this message or null
if
* no description has been set
* @see #setContentDescription(String) setContentDescription(java.lang.String)
*/
public String getContentDescription() {
String values[] = mimeHeaders.getHeader(HTTPConstants.HEADER_CONTENT_DESCRIPTION);
if (values != null && values.length > 0) {
return values[0];
}
return null;
}
/**
* Sets the description of this SOAPMessage
object's content with the given
* description.
*
* @param description a String
describing the content of this message
* @see #getContentDescription() getContentDescription()
*/
public void setContentDescription(String description) {
mimeHeaders.setHeader(HTTPConstants.HEADER_CONTENT_DESCRIPTION, description);
}
/**
* Gets the SOAP part of this SOAPMessage
object.
*
*
* If a SOAPMessage
object contains one or more attachments, the SOAP Part must
* be the first MIME body part in the message.
*
* @return the SOAPPart
object for this SOAPMessage
object
*/
public SOAPPart getSOAPPart() {
return soapPart;
}
/**
* Removes all AttachmentPart
objects that have been added to this
* SOAPMessage
object.
*
* This method does not touch the SOAP part.
*/
public void removeAllAttachments() {
attachmentParts.clear();
saveRequired = true;
}
/**
* Gets a count of the number of attachments in this message. This count does not include the
* SOAP part.
*
* @return the number of AttachmentPart
objects that are part of this
* SOAPMessage
object
*/
public int countAttachments() {
return attachmentParts.size();
}
/**
* Retrieves all the AttachmentPart
objects that are part of this
* SOAPMessage
object.
*
* @return an iterator over all the attachments in this message
*/
public Iterator getAttachments() {
return attachmentParts.iterator();
}
/**
* Retrieves all the AttachmentPart objects that have header entries that match the specified
* headers. Note that a returned attachment could have headers in addition to those specified.
*
* @param headers a {@link javax.xml.soap.MimeHeaders} object containing the MIME headers for
* which to search
* @return an iterator over all attachments({@link javax.xml.soap.AttachmentPart}) that have a
* header that matches one of the given headers
*/
public Iterator getAttachments(javax.xml.soap.MimeHeaders headers) {
Collection matchingAttachmentParts = new ArrayList();
Iterator iterator = getAttachments();
{
AttachmentPartImpl part;
while (iterator.hasNext()) {
part = (AttachmentPartImpl)iterator.next();
if (part.matches(headers)) {
matchingAttachmentParts.add(part);
}
}
}
return matchingAttachmentParts.iterator();
}
/**
* Adds the given AttachmentPart
object to this SOAPMessage
object. An
* AttachmentPart
object must be created before it can be added to a message.
*
* @param attachmentPart an AttachmentPart
object that is to become part of this
* SOAPMessage
object
* @throws IllegalArgumentException
*
*/
public void addAttachmentPart(AttachmentPart attachmentPart) {
if (attachmentPart != null) {
attachmentParts.add(attachmentPart);
mimeHeaders.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, "multipart/related");
saveRequired = true;
}
}
/**
* Creates a new empty AttachmentPart
object. Note that the method
* addAttachmentPart
must be called with this new AttachmentPart
* object as the parameter in order for it to become an attachment to this
* SOAPMessage
object.
*
* @return a new AttachmentPart
object that can be populated and added to this
* SOAPMessage
object
*/
public AttachmentPart createAttachmentPart() {
return new AttachmentPartImpl();
}
/**
* Returns all the transport-specific MIME headers for this SOAPMessage
object in a
* transport-independent fashion.
*
* @return a MimeHeaders
object containing the MimeHeader
objects
*/
public javax.xml.soap.MimeHeaders getMimeHeaders() {
return mimeHeaders;
}
/**
* Updates this SOAPMessage
object with all the changes that have been made to it.
* This method is called automatically when a message is sent or written to by the methods
* ProviderConnection.send
, SOAPConnection.call
, or
* SOAPMessage.writeTo
. However, if changes are made to a message that was received or to
* one that has already been sent, the method saveChanges
needs to be called
* explicitly in order to save the changes. The method saveChanges
also generates
* any changes that can be read back (for example, a MessageId in profiles that support a
* message id). All MIME headers in a message that is created for sending purposes are
* guaranteed to have valid values only after saveChanges
has been called.
*
* In addition, this method marks the point at which the data from all constituent
* AttachmentPart
objects are pulled into the message.
*
* @throws SOAPException if there was a problem saving changes to this message.
*/
public void saveChanges() throws SOAPException {
try {
String contentTypeValue = getSingleHeaderValue(HTTPConstants.HEADER_CONTENT_TYPE);
ContentTypeBuilder contentType;
if (isEmptyString(contentTypeValue)) {
contentType = new ContentTypeBuilder(attachmentParts.size() > 0 ? MediaType.MULTIPART_RELATED : getMediaType());
} else {
contentType = new ContentTypeBuilder(contentTypeValue);
//Use configures the baseType with multipart/related while no attachment exists or all the attachments are removed
if (contentType.getMediaType().equals(MediaType.MULTIPART_RELATED) && attachmentParts.size() == 0) {
contentType.setMediaType(getMediaType());
contentType.clearParameters();
}
}
//If it is of multipart/related, initialize those required values in the content-type, including boundary etc.
if (contentType.getMediaType().equals(MediaType.MULTIPART_RELATED)) {
//Configure boundary
String boundaryParam = contentType.getParameter("boundary");
if (isEmptyString(boundaryParam)) {
contentType.setParameter("boundary", UIDGenerator.generateMimeBoundary());
}
//Configure start content id, always get it from soapPart in case it is changed
String soapPartContentId = soapPart.getContentId();
if (isEmptyString(soapPartContentId)) {
soapPartContentId = "<" + UIDGenerator.generateContentId() + ">";
soapPart.setContentId(soapPartContentId);
}
contentType.setParameter("start", soapPartContentId);
//Configure contentId for each attachments
for(AttachmentPart attachmentPart : attachmentParts) {
if(isEmptyString(attachmentPart.getContentId())) {
attachmentPart.setContentId("<" + UIDGenerator.generateContentId() + ">");
}
}
//Configure type
contentType.setParameter("type", getMediaType().toString());
//Configure charset
String soapPartContentTypeValue = getSingleHeaderValue(soapPart.getMimeHeader(HTTPConstants.HEADER_CONTENT_TYPE));
ContentTypeBuilder soapPartContentType = null;
if (isEmptyString(soapPartContentTypeValue)) {
soapPartContentType = new ContentTypeBuilder(soapPartContentTypeValue);
} else {
soapPartContentType = new ContentTypeBuilder(getMediaType());
}
setCharsetParameter(soapPartContentType);
} else {
//Configure charset
setCharsetParameter(contentType);
}
mimeHeaders.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, contentType.toString());
} catch (ParseException e) {
throw new SOAPException("Invalid Content Type Field in the Mime Message", e);
}
saveRequired = false;
}
public void setSaveRequired() {
this.saveRequired = true;
}
/**
* Indicates whether this SOAPMessage
object has had the method {@link
* #saveChanges()} called on it.
*
* @return true
if saveChanges
has been called on this message at
* least once; false
otherwise.
*/
public boolean saveRequired() {
return saveRequired;
}
/**
* Writes this SOAPMessage
object to the given output stream. The externalization
* format is as defined by the SOAP 1.1 with Attachments specification.
*
* If there are no attachments, just an XML stream is written out. For those messages that
* have attachments, writeTo
writes a MIME-encoded byte stream.
*
* @param out the OutputStream
object to which this SOAPMessage
object
* will be written
* @throws SOAPException if there was a problem in externalizing this SOAP message
* @throws IOException if an I/O error occurs
*/
public void writeTo(OutputStream out) throws SOAPException, IOException {
try {
saveChanges();
OMOutputFormat format = new OMOutputFormat();
String enc = (String)getProperty(CHARACTER_SET_ENCODING);
format.setCharSetEncoding(enc != null ? enc : OMOutputFormat.DEFAULT_CHAR_SET_ENCODING);
String writeXmlDecl = (String)getProperty(WRITE_XML_DECLARATION);
if (writeXmlDecl == null || writeXmlDecl.equals("false")) {
//SAAJ default case doesn't send XML decl
format.setIgnoreXMLDeclaration(true);
}
SOAPEnvelope envelope = ((SOAPEnvelopeImpl) soapPart.getEnvelope()).getOMTarget();
if (attachmentParts.isEmpty()) {
envelope.serialize(out, format);
} else {
ContentTypeBuilder contentType = new ContentTypeBuilder(getSingleHeaderValue(HTTPConstants.HEADER_CONTENT_TYPE));
String boundary = contentType.getParameter("boundary");
if(isEmptyString(boundary)) {
boundary = UIDGenerator.generateMimeBoundary();
contentType.setParameter("boundary", boundary);
}
format.setMimeBoundary(boundary);
String rootContentId = soapPart.getContentId();
if(isEmptyString(rootContentId)) {
rootContentId = "<" + UIDGenerator.generateContentId() + ">";
soapPart.setContentId(rootContentId);
}
contentType.setParameter("start", rootContentId);
if ((rootContentId.indexOf("<") > -1) & (rootContentId.indexOf(">") > -1)) {
rootContentId = rootContentId.substring(1, (rootContentId.length() - 1));
}
format.setRootContentId(rootContentId);
format.setSOAP11(((SOAPFactory)((SOAPEnvelopeImpl) soapPart.getEnvelope()).omTarget.getOMFactory()).getSOAPVersion() == SOAP11Version.getSingleton());
//Double save the content-type in case anything is updated
mimeHeaders.setHeader(HTTPConstants.HEADER_CONTENT_TYPE, contentType.toString());
OMMultipartWriter mpw = new OMMultipartWriter(out, format);
OutputStream rootPartOutputStream = mpw.writeRootPart();
envelope.serialize(rootPartOutputStream);
rootPartOutputStream.close();
for (AttachmentPart ap : attachmentParts) {
mpw.writePart(ap.getDataHandler(), ap.getContentId());
}
mpw.complete();
}
saveRequired = true;
} catch (Exception e) {
throw new SOAPException(e);
}
}
/**
* Associates the specified value with the specified property. If there was already a value
* associated with this property, the old value is replaced.
*
* The valid property names include WRITE_XML_DECLARATION
and
* CHARACTER_SET_ENCODING
. All of these standard SAAJ properties are prefixed by
* "javax.xml.soap". Vendors may also add implementation specific properties. These properties
* must be prefixed with package names that are unique to the vendor.
*
* Setting the property WRITE_XML_DECLARATION
to "true"
will cause an
* XML Declaration to be written out at the start of the SOAP message. The default value of
* "false" suppresses this declaration.
*
* The property CHARACTER_SET_ENCODING
defaults to the value "utf-8"
* which causes the SOAP message to be encoded using UTF-8. Setting
* CHARACTER_SET_ENCODING
to "utf-16"
causes the SOAP message to be
* encoded using UTF-16.
*
* Some implementations may allow encodings in addition to UTF-8 and UTF-16. Refer to your
* vendor's documentation for details.
*
* @param property the property with which the specified value is to be associated
* @param value the value to be associated with the specified property
*/
public void setProperty(String property, Object value) {
props.put(property, value);
}
/**
* Retrieves value of the specified property.
*
* @param property the name of the property to retrieve
* @return the value of the property or null
if no such property exists
* @throws SOAPException if the property name is not recognized
*/
public Object getProperty(String property) throws SOAPException {
return props.get(property);
}
/**
* Returns an AttachmentPart object that is associated with an attachment that is referenced by
* this SOAPElement or null if no such attachment exists. References can be made via an href
* attribute as described in SOAP Messages with Attachments (http://www.w3.org/TR/SOAPattachments#SOAPReferenceToAttachements)
* , or via a single Text child node containing a URI as described in the WS-I Attachments
* Profile 1.0 for elements of schema type ref:swaRef(ref:swaRef (http://www.wsi.org/Profiles/AttachmentsProfile-1.0-2004-08-24.html")
* ). These two mechanisms must be supported. The support for references via href attribute also
* implies that this method should also be supported on an element that is an xop:Include
* element (XOP (http://www.w3.org/2000/xp/Group/3/06/Attachments/XOP.html) ). other reference
* mechanisms may be supported by individual implementations of this standard. Contact your
* vendor for details.
*
* @param element - The SOAPElement containing the reference to an Attachment
* @return the referenced AttachmentPart or null if no such AttachmentPart exists or no
* reference can be found in this SOAPElement.
* @throws SOAPException - if there is an error in the attempt to access the attachment
*/
public AttachmentPart getAttachment(SOAPElement soapelement) throws SOAPException {
//TODO read strings from constants
Iterator iterator = getAttachments();
{
AttachmentPartImpl attachmentPart;
while (iterator.hasNext()) {
attachmentPart = (AttachmentPartImpl)iterator.next();
String[] contentIds = attachmentPart.getMimeHeader("Content-Id");
//References can be made via an href attribute as described in SOAP Messages
//with Attachments or via a single Text child node containing a URI
String reference = soapelement.getAttribute("href");
if (reference == null || reference.trim().length() == 0) {
reference = soapelement.getValue();
if (reference == null || reference.trim().length() == 0) {
return null;
}
}
for (int a = 0; a < contentIds.length; a++) {
//eg: cid:gifImage scenario
String idPart = reference.substring(reference.indexOf(":") + 1);
idPart = "<" + idPart + ">";
if (idPart.equals(contentIds[a])) {
return attachmentPart;
}
}
String[] contentLocations = attachmentPart.getMimeHeader("Content-Location");
if (!(contentLocations == null)) {
//uri scenario
for (int b = 0; b < contentLocations.length; b++) {
if (reference.equals(contentLocations[b])) {
return attachmentPart;
}
}
}
}
}
return null;
}
/**
* Removes all the AttachmentPart objects that have header entries that match the specified
* headers. Note that the removed attachment could have headers in addition to those specified.
*
* @param headers - a MimeHeaders object containing the MIME headers for which to search
* @since SAAJ 1.3
*/
public void removeAttachments(MimeHeaders headers) {
Collection newAttachmentParts = new ArrayList();
for (AttachmentPart attachmentPart : attachmentParts) {
//Get all the headers
for (Iterator iterator = headers.getAllHeaders(); iterator.hasNext();) {
MimeHeader mimeHeader = (MimeHeader)iterator.next();
String[] headerValues = attachmentPart.getMimeHeader(mimeHeader.getName());
//if values for this header name, do not remove it
if (headerValues.length != 0) {
if (!(headerValues[0].equals(mimeHeader.getValue()))) {
newAttachmentParts.add(attachmentPart);
}
}
}
}
attachmentParts.clear();
this.attachmentParts = newAttachmentParts;
saveRequired = true;
}
/**
* Gets the SOAP Header contained in this SOAPMessage
object.
*
* @return the SOAPHeader
object contained by this SOAPMessage
object
* @throws javax.xml.soap.SOAPException if the SOAP Header does not exist or cannot be
* retrieved
*/
public SOAPHeader getSOAPHeader() throws SOAPException {
return this.soapPart.getEnvelope().getHeader();
}
/**
* Gets the SOAP Body contained in this SOAPMessage
object.
*
* @return the SOAPBody
object contained by this SOAPMessage
object
* @throws javax.xml.soap.SOAPException if the SOAP Body does not exist or cannot be retrieved
*/
public SOAPBody getSOAPBody() throws SOAPException {
return this.soapPart.getEnvelope().getBody();
}
/**
* Set the character encoding based on the contentType
parameter
*
* @param contentType
*/
private void initCharsetEncodingFromContentType(final String contentType) {
if (contentType != null) {
int delimiterIndex = contentType.lastIndexOf("charset");
if (delimiterIndex > 0) {
String charsetPart = contentType.substring(delimiterIndex);
int charsetIndex = charsetPart.indexOf('=');
String charset = charsetPart.substring(charsetIndex + 1).trim();
if ((charset.startsWith("\"") || charset.startsWith("\'"))) {
charset = charset.substring(1, charset.length());
}
if ((charset.endsWith("\"") || charset.endsWith("\'"))) {
charset = charset.substring(0, charset.length() - 1);
}
int index = charset.indexOf(';');
if (index != -1) {
charset = charset.substring(0, index);
}
setProperty(SOAPMessage.CHARACTER_SET_ENCODING, charset);
}
}
}
private boolean isEmptyString(String value) {
return value == null || value.length() == 0;
}
private String getSingleHeaderValue(String[] values) {
return values != null && values.length > 0 ? values[0] : null;
}
private String getSingleHeaderValue(String name) {
String[] values = mimeHeaders.getHeader(name);
if (values == null || values.length == 0) {
return null;
} else {
return values[0];
}
}
private MediaType getMediaType() throws SOAPException {
return ((SOAPFactory)((SOAPEnvelopeImpl) soapPart.getEnvelope()).omTarget.getOMFactory()).getSOAPVersion().getMediaType();
}
/**
* If the charset is configured by CHARACTER_SET_ENCODING, set it in the contentPart always.
* If it has already been configured in the contentType, leave it there.
* UTF-8 is used as the default value.
* @param contentType
* @throws SOAPException
*/
private void setCharsetParameter(ContentTypeBuilder contentType) throws SOAPException{
String charset = (String)getProperty(CHARACTER_SET_ENCODING);
if (!isEmptyString(charset)) {
contentType.setParameter("charset", charset);
} else {
charset = contentType.getParameter("charset");
if(isEmptyString(charset)) {
contentType.setParameter("charset", "UTF-8");
}
}
}
}