Java tutorial
/* * 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(); } } }