blob: 667c33db807fac6e96d5c301f80c380e8ad03225 [file] [log] [blame]
/*
* Copyright 2001-2004 The Apache Software Foundation.
*
* Licensed 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.axis;
import org.apache.axis.attachments.Attachments;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.message.SOAPEnvelope;
import org.apache.axis.message.MimeHeaders;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.utils.ClassUtils;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.XMLUtils;
import org.apache.commons.logging.Log;
import javax.xml.soap.AttachmentPart;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPBody;
import javax.xml.soap.SOAPHeader;
import javax.xml.soap.SOAPMessage;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Collections;
/**
* A complete SOAP (and/or XML-RPC, eventually) message.
* Includes both the root part (as a SOAPPart), and zero or
* more MIME attachments (as AttachmentParts).
* <p>
* Eventually should be refactored to generalize SOAPPart
* for multiple protocols (XML-RPC?).
*
* @author Rob Jellinghaus (robj@unrealities.com)
* @author Doug Davis (dug@us.ibm.com)
* @author Glen Daniels (gdaniels@allaire.com)
* @author Rick Rineholt
* @author Heejune Ahn (cityboy@tmax.co.kr)
*/
public class Message extends javax.xml.soap.SOAPMessage
implements java.io.Serializable {
/**
* The <code>Log</code> that this class uses for logging all messages.
*/
protected static Log log =
LogFactory.getLog(Message.class.getName());
/** Message is a request. */
public static final String REQUEST = "request";
/** Message is a a response. */
public static final String RESPONSE = "response";
/** MIME parts defined for messages. */
public static final String MIME_MULTIPART_RELATED = "multipart/related";
/** DIME parts defined for messages. */
public static final String MIME_APPLICATION_DIME = "application/dime";
/** Content Type for MTOM/XOP */
public static final String CONTENT_TYPE_MTOM = "application/xop+xml";
/** Default Attachments Implementation class. */
public static final String DEFAULT_ATTACHMNET_IMPL="org.apache.axis.attachments.AttachmentsImpl";
/** Current Attachment implementation. */
private static String mAttachmentsImplClassName=DEFAULT_ATTACHMNET_IMPL;
/** Look at the input stream to find the headers to decide the mime type. */
public static final String MIME_UNKNOWN = " ";
// fixme: is this constrained to two values - request/response (e.g.
// REQUEST and RESPONSE)? If so, this needs documenting in the get/set
// methods and/or converting into a type-safe e-num. Potentially get/set
// methods should check these values & throw IllegalArgumentException
/**
* The messageType indicates whether this is request or response.
*/
private String messageType;
/**
* This Message's SOAPPart. Will always be here.
*/
private SOAPPart mSOAPPart;
/**
* This Message's Attachments object, which manages the attachments
* contained in this Message.
*/
private Attachments mAttachments = null;
private MimeHeaders headers;
private boolean saveRequired = true;
/**
* Returns the name of the class prividing Attachment Implementation.
*
* @return class name
*/
public static String getAttachmentImplClassName(){
return mAttachmentsImplClassName;
}
private MessageContext msgContext;
/**
* Get the message type.
*
* @return the message type <code>String</code>
*/
public String getMessageType() {
return messageType;
}
/**
* Set the message type.
*
* @param messageType the message type <code>String</code>
*/
public void setMessageType(String messageType) {
this.messageType = messageType;
}
/**
* Get the context associated with this message.
*
* @return the message context for this message
*/
public MessageContext getMessageContext() {
return msgContext;
}
/**
* Set the context associated with this message.
*
* @param msgContext the message context for this message
*/
public void setMessageContext(MessageContext msgContext) {
this.msgContext = msgContext;
}
/**
* Construct a Message, using the provided initialContents as the
* contents of the Message's SOAPPart.
* <p>
* Eventually, genericize this to
* return the RootPart instead, which will have some kind of
* EnvelopeFactory to enable support for things other than SOAP.
* But that all will come later, with lots of additional refactoring.
*
* @param initialContents may be String, byte[], InputStream, SOAPEnvelope,
* or AxisFault.
* @param bodyInStream is true if initialContents is an InputStream
* containing just the SOAP body (no SOAP-ENV).
*/
public Message(Object initialContents, boolean bodyInStream) {
setup(initialContents, bodyInStream, null, null, null);
}
/**
* Construct a Message, using the provided initialContents as the
* contents of the Message's SOAPPart.
* <p>
* Eventually, genericize this to
* return the RootPart instead, which will have some kind of
* EnvelopeFactory to enable support for things other than SOAP.
* But that all will come later, with lots of additional refactoring.
*
* @param initialContents may be String, byte[], InputStream, SOAPEnvelope,
* or AxisFault.
* @param bodyInStream is true if initialContents is an InputStream
* containing just the SOAP body (no SOAP-ENV).
* @param headers Mime Headers.
*/
public Message(Object initialContents, boolean bodyInStream, javax.xml.soap.MimeHeaders headers) {
setup(initialContents, bodyInStream, null, null, headers);
}
/**
* Construct a Message, using the provided initialContents as the
* contents of the Message's SOAPPart.
* <p>
* Eventually, genericize this to
* return the RootPart instead, which will have some kind of
* EnvelopeFactory to enable support for things other than SOAP.
* But that all will come later, with lots of additional refactoring.
*
* @param initialContents may be String, byte[], InputStream, SOAPEnvelope,
* or AxisFault.
* @param headers Mime Headers.
*/
public Message(Object initialContents, MimeHeaders headers) {
setup(initialContents, true, null, null, headers);
}
/**
* Construct a Message, using the provided initialContents as the
* contents of the Message's SOAPPart.
* <p>
* Eventually, genericize this to
* return the RootPart instead, which will have some kind of
* EnvelopeFactory to enable support for things other than SOAP.
* But that all will come later, with lots of additional refactoring.
*
* @param initialContents may be String, byte[], InputStream, SOAPEnvelope,
* or AxisFault
* @param bodyInStream is true if initialContents is an InputStream
* containing just the SOAP body (no SOAP-ENV)
* @param contentType this if the contentType has been already determined
* (as in the case of servlets)
* @param contentLocation the location of the content
*/
public Message(Object initialContents,
boolean bodyInStream,
String contentType,
String contentLocation) {
setup(initialContents, bodyInStream, contentType, contentLocation, null);
}
/**
* Construct a Message. An overload of Message(Object, boolean),
* defaulting bodyInStream to false.
*
* @param initialContents may be String, byte[], InputStream, SOAPEnvelope,
* or AxisFault
*/
public Message(Object initialContents) {
setup(initialContents, false, null, null, null);
}
private static Class attachImpl = null;
//aviod testing and possibly failing everytime.
private static boolean checkForAttachmentSupport = true;
private static boolean attachmentSupportEnabled = false;
private static synchronized boolean isAttachmentSupportEnabled(MessageContext mc) {
if (checkForAttachmentSupport) {
//aviod testing and possibly failing everytime.
checkForAttachmentSupport = false;
try {
// Get the default setting from AxisProperties
String attachImpName = AxisProperties.getProperty(AxisEngine.PROP_ATTACHMENT_IMPLEMENTATION,
AxisEngine.DEFAULT_ATTACHMENT_IMPL);
// override the default with any changes specified in the engine configuration
if (null != mc) {
AxisEngine ae = mc.getAxisEngine();
if (null != ae) {
attachImpName = (String) ae.getOption(
AxisEngine.PROP_ATTACHMENT_IMPLEMENTATION);
}
}
/**
* Attempt to resolve class name, verify that these are present...
*/
ClassUtils.forName("javax.activation.DataHandler");
ClassUtils.forName("javax.mail.internet.MimeMultipart");
attachImpl = ClassUtils.forName(attachImpName);
attachmentSupportEnabled = true;
} catch (ClassNotFoundException ex) {
// no support for it, leave mAttachments null.
} catch (java.lang.NoClassDefFoundError ex) {
// no support for it, leave mAttachments null.
} catch (java.lang.SecurityException ex) {
// no support for it, leave mAttachments null.
}
log.debug(Messages.getMessage("attachEnabled") + " " +
attachmentSupportEnabled);
}
return attachmentSupportEnabled;
}
/**
* Do the work of construction.
*
* @param initialContents may be String, byte[], InputStream, SOAPEnvelope,
* or AxisFault
* @param bodyInStream is true if initialContents is an InputStream
* containing just the SOAP body (no SOAP-ENV)
* @param contentType this if the contentType has been already determined
* (as in the case of servlets)
* @param contentLocation the location of the content
* @param mimeHeaders mime headers for attachments
*/
private void setup(Object initialContents, boolean bodyInStream,
String contentType, String contentLocation,
javax.xml.soap.MimeHeaders mimeHeaders) {
if(contentType == null && mimeHeaders != null) {
String contentTypes[] = mimeHeaders.getHeader("Content-Type");
contentType = (contentTypes != null)? contentTypes[0] : null;
}
if(contentLocation == null && mimeHeaders != null) {
String contentLocations[] = mimeHeaders.getHeader("Content-Location");
contentLocation = (contentLocations != null)? contentLocations[0] : null;
}
if (contentType != null) {
int delimiterIndex = contentType.lastIndexOf("charset");
if (delimiterIndex > 0) {
String charsetPart = contentType.substring(delimiterIndex);
int delimiterIndex2 = charsetPart.indexOf(';');
if(delimiterIndex2 != -1){
charsetPart = charsetPart.substring(0, delimiterIndex2);
}
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);
}
try {
setProperty(SOAPMessage.CHARACTER_SET_ENCODING, charset);
} catch (SOAPException e) {
}
}
}
// Try to construct an AttachmentsImpl object for attachment
// functionality.
// If there is no org.apache.axis.attachments.AttachmentsImpl class,
// it must mean activation.jar is not present and attachments are not
// supported.
if (isAttachmentSupportEnabled(getMessageContext())) {
// Construct one, and cast to Attachments.
// There must be exactly one constructor of AttachmentsImpl, which
// must take an org.apache.axis.Message!
Constructor attachImplConstr = attachImpl.getConstructors()[0];
try {
mAttachments = (Attachments) attachImplConstr.newInstance(
new Object[] { initialContents,
contentType, contentLocation});
//If it can't support it, it wont have a root part.
mSOAPPart = (SOAPPart) mAttachments.getRootPart();
} catch (InvocationTargetException ex) {
log.fatal(Messages.getMessage("invocationTargetException00"),
ex);
throw new RuntimeException(ex.getMessage());
} catch (InstantiationException ex) {
log.fatal(Messages.getMessage("instantiationException00"),
ex);
throw new RuntimeException(ex.getMessage());
} catch (IllegalAccessException ex) {
log.fatal(Messages.getMessage("illegalAccessException00"),
ex);
throw new RuntimeException(ex.getMessage());
}
} else if (contentType != null && contentType.startsWith("multipart")){
throw new RuntimeException(Messages.getMessage("noAttachments"));
}
// text/xml
if (null == mSOAPPart) {
mSOAPPart = new SOAPPart(this, initialContents, bodyInStream);
}
else
mSOAPPart.setMessage(this);
// The stream was not determined by a more complex type so default to
if(mAttachments!=null) mAttachments.setRootPart(mSOAPPart);
headers = (mimeHeaders == null) ? new MimeHeaders() : new MimeHeaders(mimeHeaders);
}
/**
* Get this message's SOAPPart.
* <p>
* Eventually, this should be generalized beyond just SOAP,
* but it's hard to know how to do that without necessitating
* a lot of casts in client code. Refactoring keeps getting
* easier anyhow.
*
* @return the soap part of this message
*/
public javax.xml.soap.SOAPPart getSOAPPart() {
return mSOAPPart;
}
// fixme: do we realy need this? Can client code not just call
// getSOAPPart().getAsString() or is there some future optimization that
// could be hooked in here?
/**
* Get a string representation of this message's SOAPPart.
*
* @return the soap part of this message as a <code>String</code>
* @throws org.apache.axis.AxisFault if the stringification failed
*/
public String getSOAPPartAsString() throws org.apache.axis.AxisFault {
return mSOAPPart.getAsString();
}
// fixme: do we realy need this? Can client code not just call
// getSOAPPart().getAsBytes() or is there some future optimization that
// could be hooked in here?
/**
* Get a byte array representation of this message's SOAPPart.
*
* @return the soap part of this message as a <code>byte[]</code>
* @throws org.apache.axis.AxisFault if creating the byte[] failed
*/
public byte[] getSOAPPartAsBytes() throws org.apache.axis.AxisFault {
return mSOAPPart.getAsBytes();
}
/**
* Get this message's SOAPPart as a SOAPEnvelope.
*
* @return a SOAPEnvelope containing this message's SOAPPart
* @throws AxisFault if this failed
*/
public SOAPEnvelope getSOAPEnvelope() throws AxisFault {
return mSOAPPart.getAsSOAPEnvelope();
}
/**
* Get the Attachments of this Message.
* <p>
* If this returns null, then NO ATTACHMENT SUPPORT EXISTS in this
* configuration of Axis, and no attachment operations may be
* performed.
*
* @return the <code>Attachments</code> if attachments are supported, null
* otherwise
*/
public Attachments getAttachmentsImpl() {
return mAttachments;
}
/**
* Get the content type of the attachments.
*
* @param sc provides the default content type
* @return a <code>String</code> giving the content type of the
* attachment
* @throws AxisFault if there was an error deducing the content type from
* this message
*/
public String getContentType(SOAPConstants sc) throws AxisFault {
boolean soap12 = false;
if(sc != null) {
if(sc == SOAPConstants.SOAP12_CONSTANTS) {
soap12 = true;
}
} else {
// Support of SOAP 1.2 HTTP binding
SOAPEnvelope envelope = getSOAPEnvelope();
if (envelope != null) {
if (envelope.getSOAPConstants() == SOAPConstants.SOAP12_CONSTANTS) {
soap12 = true;
}
}
}
String encoding = XMLUtils.getEncoding(this, msgContext);;
String ret = sc.getContentType() + "; charset=" + encoding.toLowerCase();
// Support of SOAP 1.2 HTTP binding
if (soap12) {
ret = HTTPConstants.HEADER_ACCEPT_APPL_SOAP +"; charset=" + encoding;
}
if (getSendType() != Attachments.SEND_TYPE_NONE && mAttachments != null &&
0 != mAttachments.getAttachmentCount()) {
ret = mAttachments.getContentType();
}
return ret;
}
private int getSendType() {
int sendType = Attachments.SEND_TYPE_NOTSET;
if ((msgContext != null) && (msgContext.getService() != null)) {
sendType = msgContext.getService().getSendType();
}
return sendType;
}
//This will have to give way someday to HTTP Chunking but for now kludge.
/**
* Get the content length, including both soap and any attachments.
*
* @return the total length of this message in bytes
* @throws org.apache.axis.AxisFault if there was a problem that prevented
* the length being calculated
*/
public long getContentLength() throws org.apache.axis.AxisFault {
long ret = mSOAPPart.getContentLength();
if (mAttachments != null && 0 < mAttachments.getAttachmentCount()) {
ret = mAttachments.getContentLength();
}
return ret;
}
/**
* Writes this <CODE>SOAPMessage</CODE> object to the given
* output stream. The externalization format is as defined by
* the SOAP 1.1 with Attachments specification.
*
* <P>If there are no attachments, just an XML stream is
* written out. For those messages that have attachments,
* <CODE>writeTo</CODE> writes a MIME-encoded byte stream.</P>
* @param os the <CODE>OutputStream</CODE>
* object to which this <CODE>SOAPMessage</CODE> 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(java.io.OutputStream os) throws SOAPException, IOException {
//Do it the old fashion way.
if (getSendType() == Attachments.SEND_TYPE_NONE || mAttachments == null || 0 == mAttachments.getAttachmentCount()) {
try {
String charEncoding = XMLUtils.getEncoding(this, msgContext);;
mSOAPPart.setEncoding(charEncoding);
mSOAPPart.writeTo(os);
} catch (java.io.IOException e) {
log.error(Messages.getMessage("javaIOException00"), e);
}
} else {
try {
mAttachments.writeContentToStream(os);
} catch (java.lang.Exception e) {
log.error(Messages.getMessage("exception00"), e);
}
}
}
private java.util.Hashtable mProps = new java.util.Hashtable();
public SOAPBody getSOAPBody() throws SOAPException {
return mSOAPPart.getEnvelope().getBody();
}
public SOAPHeader getSOAPHeader() throws SOAPException {
return mSOAPPart.getEnvelope().getHeader();
}
public void setProperty(String property, Object value) throws SOAPException {
mProps.put(property, value);
}
public Object getProperty(String property) throws SOAPException {
return mProps.get(property);
}
/**
* Retrieves a description of this <CODE>SOAPMessage</CODE>
* object's content.
* @return a <CODE>String</CODE> describing the content of this
* message or <CODE>null</CODE> if no description has been
* set
* @see #setContentDescription(java.lang.String) setContentDescription(java.lang.String)
*/
public String getContentDescription() {
String values[] = headers.getHeader(HTTPConstants.HEADER_CONTENT_DESCRIPTION);
if(values != null && values.length > 0)
return values[0];
return null;
}
/**
* Sets the description of this <CODE>SOAPMessage</CODE>
* object's content with the given description.
* @param description a <CODE>String</CODE>
* describing the content of this message
* @see #getContentDescription() getContentDescription()
*/
public void setContentDescription(String description) {
headers.setHeader(HTTPConstants.HEADER_CONTENT_DESCRIPTION, description);
}
/**
* Updates this <CODE>SOAPMessage</CODE> 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 <CODE>ProviderConnection.send</CODE>, <CODE>
* SOAPConnection.call</CODE>, or <CODE>
* SOAPMessage.writeTo</CODE>. However, if changes are made to
* a message that was received or to one that has already been
* sent, the method <CODE>saveChanges</CODE> needs to be
* called explicitly in order to save the changes. The method
* <CODE>saveChanges</CODE> 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 <CODE>saveChanges</CODE> has been
* called.
*
* <P>In addition, this method marks the point at which the
* data from all constituent <CODE>AttachmentPart</CODE>
* objects are pulled into the message.</P>
* @throws SOAPException if there
* was a problem saving changes to this message.
*/
public void saveChanges() throws SOAPException {
headers.removeHeader("Content-Length");
if (mAttachments != null && 0 < mAttachments.getAttachmentCount()) {
try {
headers.setHeader("Content-Type",mAttachments.getContentType());
} catch (AxisFault af){
log.error(Messages.getMessage("exception00"), af);
}
}
saveRequired = false;
try {
/* Fix for Bug 16418 - Start from scratch */
mSOAPPart.saveChanges();
} catch (AxisFault axisFault) {
log.error(Messages.getMessage("exception00"), axisFault);
}
}
/**
* Indicates whether this <CODE>SOAPMessage</CODE> object
* has had the method <CODE>saveChanges</CODE> called on
* it.
* @return <CODE>true</CODE> if <CODE>saveChanges</CODE> has
* been called on this message at least once; <CODE>
* false</CODE> otherwise.
*/
public boolean saveRequired() {
return saveRequired;
}
/**
* Returns all the transport-specific MIME headers for this
* <CODE>SOAPMessage</CODE> object in a transport-independent
* fashion.
* @return a <CODE>MimeHeaders</CODE> object containing the
* <CODE>MimeHeader</CODE> objects
*/
public javax.xml.soap.MimeHeaders getMimeHeaders() {
return headers;
}
/**
* Removes all <CODE>AttachmentPart</CODE> objects that have
* been added to this <CODE>SOAPMessage</CODE> object.
*
* <P>This method does not touch the SOAP part.</P>
*/
public void removeAllAttachments(){
mAttachments.removeAllAttachments();
}
/**
* Gets a count of the number of attachments in this
* message. This count does not include the SOAP part.
* @return the number of <CODE>AttachmentPart</CODE> objects
* that are part of this <CODE>SOAPMessage</CODE>
* object
*/
public int countAttachments(){
return mAttachments == null ? 0 : mAttachments.getAttachmentCount();
}
/**
* Retrieves all the <CODE>AttachmentPart</CODE> objects
* that are part of this <CODE>SOAPMessage</CODE> object.
* @return an iterator over all the attachments in this
* message
*/
public Iterator getAttachments(){
try {
if (mAttachments != null && 0 != mAttachments.getAttachmentCount()) {
return mAttachments.getAttachments().iterator();
}
} catch (AxisFault af){
log.error(Messages.getMessage("exception00"), af);
}
return Collections.EMPTY_LIST.iterator();
}
/**
* Retrieves all the <CODE>AttachmentPart</CODE> 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 <CODE>MimeHeaders</CODE>
* object containing the MIME headers for which to
* search
* @return an iterator over all attachments that have a header
* that matches one of the given headers
*/
public Iterator getAttachments(javax.xml.soap.MimeHeaders headers){
return mAttachments.getAttachments(headers);
}
/**
* Adds the given <CODE>AttachmentPart</CODE> object to this
* <CODE>SOAPMessage</CODE> object. An <CODE>
* AttachmentPart</CODE> object must be created before it can be
* added to a message.
* @param attachmentpart an <CODE>
* AttachmentPart</CODE> object that is to become part of
* this <CODE>SOAPMessage</CODE> object
* @throws java.lang.IllegalArgumentException
*/
public void addAttachmentPart(AttachmentPart attachmentpart){
try {
mAttachments.addAttachmentPart((org.apache.axis.Part)attachmentpart);
} catch (AxisFault af){
log.error(Messages.getMessage("exception00"), af);
}
}
/**
* Creates a new empty <CODE>AttachmentPart</CODE> object.
* Note that the method <CODE>addAttachmentPart</CODE> must be
* called with this new <CODE>AttachmentPart</CODE> object as
* the parameter in order for it to become an attachment to this
* <CODE>SOAPMessage</CODE> object.
* @return a new <CODE>AttachmentPart</CODE> object that can be
* populated and added to this <CODE>SOAPMessage</CODE>
* object
*/
public AttachmentPart createAttachmentPart() {
if (!isAttachmentSupportEnabled(getMessageContext())) {
throw new RuntimeException(Messages.getMessage("noAttachments"));
}
try {
return (AttachmentPart) mAttachments.createAttachmentPart();
} catch (AxisFault af){
log.error(Messages.getMessage("exception00"), af);
}
return null;
}
/**
* Dispose of attachments.
*/
public void dispose() {
if(mAttachments!=null) {
mAttachments.dispose();
}
}
}