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