Message.java |
/* * 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.jboss.axis; import org.jboss.axis.attachments.AttachmentSupport; import org.jboss.axis.attachments.Attachments; import org.jboss.axis.message.MimeHeadersImpl; import org.jboss.axis.message.SOAPEnvelopeAxisImpl; import org.jboss.axis.message.SOAPMessageAxisImpl; import org.jboss.axis.soap.SOAPConstants; import org.jboss.axis.transport.http.HTTPConstants; import org.jboss.axis.utils.Messages; import org.jboss.axis.utils.XMLUtils; import org.jboss.logging.Logger; import javax.xml.soap.AttachmentPart; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collections; import java.util.Iterator; /** * 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 SOAPMessageAxisImpl { /** * The <code>Log</code> that this class uses for logging all messages. */ private static Logger log = Logger.getLogger(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"; /** * Default Attachments Implementation class. */ public static final String DEFAULT_ATTACHMNET_IMPL = "org.jboss.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 MessagePart mSOAPPart; /** * This Message's Attachments object, which manages the attachments * contained in this Message. */ private Attachments mAttachments = null; private MimeHeadersImpl 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, MimeHeadersImpl 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); } /** * 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 charsetIndex = charsetPart.indexOf('='); String charset = charsetPart.substring(charsetIndex + 1).trim(); if ((charset.startsWith("\"") && charset.endsWith("\"")) || (charset.startsWith("'") && charset.endsWith("'"))) { charset = charset.substring(1, 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.jboss.axis.attachments.AttachmentsImpl class, // it must mean activation.jar is not present and attachments are not // supported. if (AttachmentSupport.isAttachmentSupportEnabled(getMessageContext())) { // Construct one, and cast to Attachments. // There must be exactly one constructor of AttachmentsImpl, which // must take an org.jboss.axis.Message! Class attachImpl = AttachmentSupport.getImplementationClass(); 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 = (MessagePart)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 MessagePart(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 MimeHeadersImpl() : new MimeHeadersImpl(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.jboss.axis.AxisFault if the stringification failed */ public String getSOAPPartAsString() throws org.jboss.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.jboss.axis.AxisFault if creating the byte[] failed */ public byte[] getSOAPPartAsBytes() throws org.jboss.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 SOAPEnvelopeAxisImpl 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 { int sendType = Attachments.SEND_TYPE_NOTSET; if ((msgContext != null) && (msgContext.getService() != null)) { sendType = msgContext.getService().getSendType(); } if (sendType != Attachments.SEND_TYPE_NONE) { //Force serialization if it hasn't happend it. //Rick Rineholt fix this later. //Heejune added null check. if (mSOAPPart != null) { mSOAPPart.getAsBytes(); } } // The origional logic is very simple // String ret = sc.getContentType() + "; charset="+XMLUtils.getEncoding().toLowerCase(); // The following logic is devised to utilize CHARACTER_SET_ENCODING property from SAAJ 1.2. String encoding = null; try { encoding = (String)getProperty(SOAPMessage.CHARACTER_SET_ENCODING); } catch (SOAPException ignore) { } if (encoding == null) { encoding = XMLUtils.getEncoding().toLowerCase(); } String ret = sc.getContentType(); // Support of SOAP 1.2 HTTP binding SOAPEnvelopeAxisImpl envelope = getSOAPEnvelope(); if (envelope != null) { if (envelope.getSOAPConstants() == SOAPConstants.SOAP12_CONSTANTS) { ret = HTTPConstants.HEADER_ACCEPT_APPL_SOAP + "; charset=" + encoding; } } if (mAttachments != null && 0 != mAttachments.getAttachmentCount()) { ret = mAttachments.getContentType(); } return ret; } //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.jboss.axis.AxisFault if there was a problem that prevented * the length being calculated */ public long getContentLength() throws org.jboss.axis.AxisFault { long ret = mSOAPPart.getAsBytes().length; 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/> * <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 (mAttachments == null || 0 == mAttachments.getAttachmentCount()) { try { String charEncoding = (String)getProperty(SOAPMessage.CHARACTER_SET_ENCODING); if (charEncoding == null) { charEncoding = "UTF-8"; } Writer writer = new OutputStreamWriter(os, charEncoding); writer = new BufferedWriter(writer); // write the xml declaration header String incXMLDecl = (String)getProperty(SOAPMessage.WRITE_XML_DECLARATION); if (incXMLDecl == null) { incXMLDecl = "false"; } if (incXMLDecl.equalsIgnoreCase("true")) { writer.write("<?xml version=\"1.0\" encoding=\"" + charEncoding + "\"?>"); } mSOAPPart.writeTo(writer); writer.flush(); } 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/> * <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 { 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 */ getSOAPPartAsString(); } 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/> * <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.jboss.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 (!AttachmentSupport.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(); } } }
Message.java |