You are viewing a plain text version of this content. The canonical link for it is here.
Posted to soap-dev@ws.apache.org by wc...@locus.apache.org on 2000/11/28 23:28:06 UTC

cvs commit: xml-soap/java/src/org/apache/soap/rpc SOAPContext.java

wcloeten    00/11/28 14:28:05

  Added:       java/src/org/apache/soap/rpc SOAPContext.java
  Log:
  message context encapsulating MIME MultiPart
  
  Revision  Changes    Path
  1.1                  xml-soap/java/src/org/apache/soap/rpc/SOAPContext.java
  
  Index: SOAPContext.java
  ===================================================================
  /*
   * The Apache Software License, Version 1.1
   *
   *
   * Copyright (c) 2000 The Apache Software Foundation.  All rights 
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer. 
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   *    if any, must include the following acknowledgment:  
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgment may appear in the software itself,
   *    if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "SOAP" and "Apache Software Foundation" must
   *    not be used to endorse or promote products derived from this
   *    software without prior written permission. For written 
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   *    nor may "Apache" appear in their name, without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation and was
   * originally based on software copyright (c) 2000, International
   * Business Machines, Inc., http://www.apache.org.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  package org.apache.soap.rpc;
  
  import java.io.*;
  import java.util.*;
  import org.apache.soap.util.*;
  import org.apache.soap.*;
  import org.apache.soap.encoding.*;
  import org.apache.soap.server.*;
  import org.apache.soap.util.mime.*;
  import javax.mail.*;
  import javax.mail.internet.*;
  import javax.activation.*;
  
  /**
   * SOAP context for a message. Encapsulates:
   * <ul>
   *  <li>MIME multipart
   * </ul>
   *
   * @author Wouter Cloetens (wcloeten@raleigh.ibm.com)
   */
  public class SOAPContext {
      protected MimeMultipart parts;
      protected static Hashtable contextTable = new Hashtable();
  
      /**
       * List of MIME headers to strip from JavaMail-generated header.
       */
      private static final String[] ignoreHeaders =
          {"Message-ID", "Mime-Version"};
  
      private static final String DEFAULT_BASEURI = "thismessage:/";
  
      /**
       * Get the current RPCMessage context (temp kludge).<p>
       * The structure of the code currently doesn't expose any context
       * to the [de]serializers. The Mime parts, currently embedded in
       * this calss, need to be
       * accessible to the [de]serializers. Until this situation is
       * fundamentally resolved, a kludgy mechanism is introduced here,
       * allowing any class to extract a reference to the current
       * SOAPContext using the current thread.<p>
       * This mechanism assumes that only one call or response is being
       * handled at any time, and only within the context of one thread.<p>
       * Note also that this isn't too friendly on memory since the reference
       * to the SOAPContext object does not get cleaned up.
       */
      public static SOAPContext getSOAPContext() {
          Thread me = Thread.currentThread();
          Object o = contextTable.get(me);
          if(o != null && o instanceof SOAPContext)
              return (SOAPContext)o;
          else {
              SOAPContext ctx = new SOAPContext();
              ctx.setSOAPContext();
              return ctx;
          }
      }
  
      public void setSOAPContext() {
          Thread me = Thread.currentThread();
          contextTable.put(me, this);
      }
  
      /**
       * Constructor.
       */
      public SOAPContext() {
          parts = null;
      }
  
      /**
       * Initialise MIME multipart object from a data source.
       *
       * @param ds        a DataSource object to read from
       */
      public void readMultipart(DataSource ds) throws MessagingException {
          parts = new MimeMultipart(ds);
      }
  
      /**
       * Get the specified Part.  Parts are numbered starting at 0.
       *
       * @param index     the index of the desired Part
       * @return          the Part
       * @exception       IndexOutOfBoundsException if no such Part exists
       */
      public BodyPart getBodyPart(int index) throws IndexOutOfBoundsException {
        /* Actually, this method never throws a MessagingException. In case a
         * future implementation does, catch it and throw an
         * IndexOutOfBoundsException
         */
          if (parts == null) {
              throw new IndexOutOfBoundsException();
          }
          try {
              return parts.getBodyPart(index);
          }
          catch (MessagingException me) {
              throw new IndexOutOfBoundsException(me.getMessage());
          }
      }
  
      /**
       * Get the Mimepart referred to by the given ContentID (CID).
       * Returns null if the part is not found.
       *
       * @param  CID      the ContentID of the desired part
       * @return          the Part
       */
      public BodyPart getBodyPart(String CID) {
          if (parts == null) {
              return null;
          }
          try {
              return parts.getBodyPart(CID);
          }
          catch (MessagingException me) {
              return null;
          }
          catch (NullPointerException npe) {
              return null;
          }
      }
  
      /**
       * Find the Mimepart referred to by the given URI. This can be:<ul>
       * <li>An absolute URI, in which case a part is located with a
       *     corresponding Content-Location header.
       * <li>A relative URI, in which case an absolute URI is constructed
       *     relative to the Content-Location header of the root SOAP part
       *     or the multipart and the previous rule is then applied.
       * <li>A URI of the format "cid:xxx"> in which case a part is located
       *     with a matching Content-ID header.
       * </ul>Returns null if the part is not found.
       * <p>Note: relative URIs not entirely implemented yet.
       *
       * @param  uri      the URI
       * @return          the Part or null if not found
       */
      public BodyPart findBodyPart(String uri) {
          if (parts == null || uri == null) {
              return null;
          }
          try {
              if(uri.length() > 4 &&
                 uri.substring(0, 4).equalsIgnoreCase("cid:")) {
                  // It's a Content-ID. URLDecode and lookup.
                  String cid = MimeUtils.decode(uri.substring(4));
                  // References are not supposed to be inside brackets, but be
                  // tolerant.
                  if (cid.charAt(0) != '<' || cid.charAt(cid.length()) != '>')
                      cid = '<' + cid + '>';
  
                  try {
                      return parts.getBodyPart(cid);
                  } catch(NullPointerException npe) {
                  }
              } else {
                  // It's a URI.
                  return findPartByLocation(uri);
              }
  
          } catch (MessagingException me) {
          } catch (NullPointerException npe) {
          }
          return null;
      }
  
      /**
       * Determine the document's base URI, according to the following scheme:
       * <ol>
       * <li>The Content-Location header of the root (SOAP) part.
       * <li>The Content-Location header of the multipart. (not implemented)
       * <li>"thismessage:/"
       * </ol>
       */
      public String getBaseURI() {
          String baseUri = null;
          try {
              baseUri = getRootPart().getHeader(
                  Constants.HEADER_CONTENT_LOCATION, null);
          } catch(MessagingException me) {
          }
          if(baseUri == null)
              baseUri = DEFAULT_BASEURI;
          return baseUri;
      }
  
      /**
       * Find the Mimepart referred to by the given URI. This can be:<ul>
       * <li>An absolute URI, in which case a part is located with a
       *     corresponding Content-Location header.
       * <li>A relative URI, in which case an absolute URI is constructed
       *     relative to the Content-Location header of the root SOAP part
       *     or the multipart and the previous rule is then applied.
       * </ul>Returns null if the part is not found.
       * <p>Note: relative URIs not entirely implemented yet. We also can't pick
       * up the base URI from the multipart (transport) headers yet, only from
       * the root (SOAP) part.
       *
       * @param  uri      the URI
       * @return          the Part or null if not found
       */
      public BodyPart findPartByLocation(String uri) {
          try {
              String baseUri = getBaseURI();
              uri = normalizeURI(uri, baseUri);
              if(uri == null)
                  return null;
              for(int i = 0; i < parts.getCount(); i++) {
                  MimeBodyPart part = (MimeBodyPart)getBodyPart(i);
                  if(part != null) {
                      String partUri = part.getHeader(
                          Constants.HEADER_CONTENT_LOCATION, null);
                      if(uri.equals(normalizeURI(partUri, baseUri)))
                          return part;
                  }
              }
          } catch(MessagingException me) {
          }
          return null;
      }
  
      /**
       * Normalise URI to an absolute URI according to rules in RFC2396.
       * <p>Note: resolution for relative URIs is not completely implemented
       * yet. For now, we just concatenate the relative URI to the base URI.
       */
      private String normalizeURI(String uri, String baseUri) {
          // Check if it is an absolute URI.
          int p1 = uri.indexOf(':');
          if(p1 >= 0) {
              String scheme = uri.substring(0, p1);
              if(scheme.indexOf('/') == -1 &&
                 scheme.indexOf('?') == -1 &&
                 scheme.indexOf('#') == -1)
                  // Yes, it's absolute! Return unchanged.
                  return uri;
          }
          // It's a relative URI. Normalise to an absolute URI.
          return baseUri + uri;
      }
  
      /**
       * Adds a Part.  The BodyPart is appended to
       * the list of existing Parts.
       *
       * @param  part  The Part to be appended
       * @exception    MessagingException
       * @exception    IllegalWriteException if the underlying
       *               implementation does not support modification
       *               of existing values
       */
      public void addBodyPart(BodyPart part) throws MessagingException {
          if (parts == null)
              parts = new MimeMultipart(
                  Constants.HEADERVAL_MULTIPART_CONTENT_SUBTYPE);
          parts.addBodyPart(part);
      }
  
      /**
       * Adds a BodyPart at position <code>index</code>.
       * If <code>index</code> is not the last one in the list,
       * the subsequent parts are shifted up. If <code>index</code>
       * is larger than the number of parts present, the
       * BodyPart is appended to the end.
       *
       * @param           part  The BodyPart to be inserted
       * @param           index Location where to insert the part
       * @exception       MessagingException
       * @exception       IllegalWriteException if the underlying
       *                  implementation does not support modification
       *                  of existing values
       */
      public void addBodyPart(BodyPart part, int index)
          throws MessagingException {
          if (parts == null)
              parts = new MimeMultipart(
                  Constants.HEADERVAL_MULTIPART_CONTENT_SUBTYPE);
          parts.addBodyPart(part, index);
      }
  
      /**
       * Adds the root BodyPart. Add it in the first position, create a
       * unique Content ID and set the "start" Content-Type header modifier
       * that that.<p>
       * Since there is no way to set the multipart's Content-Type in
       * the JavaMail implementation, for now, just return the string:
       * <code>start="<i>content-id</i>"</code> and let the code invoking
       * this intercept the header.
       *
       * @param           part  The BodyPart to be inserted
       * @return          "start" Content-Type header modifier
       * @exception       MessagingException
       * @exception       IllegalWriteException if the underlying
       *                  implementation does not support modification
       *                  of existing values
       */
      public String setRootPart(BodyPart part) throws MessagingException {
          if (parts == null)
              parts = new MimeMultipart(
                 Constants.HEADERVAL_MULTIPART_CONTENT_SUBTYPE);
          String rootCid = '<' + MimeUtils.getUniqueValue() + '>';
          part.setHeader(Constants.HEADER_CONTENT_ID, rootCid);
          parts.addBodyPart(part, 0);
          return "start=\"" + rootCid + '"';
      }
  
      /**
       * Find the root part. Search for a "start" Content-Type header modifier,
       * if not present or invalid, assume the first part is the root.
       *
       * @return          document root BodyPart
       */
      public MimeBodyPart getRootPart() throws MessagingException {
          BodyPart rootPart = null;
          String startCid = new ContentType(
              getContentType()).getParameter("start");
          if(startCid != null)
              rootPart = getBodyPart(MimeUtils.decode(startCid));
          if(rootPart == null)
              rootPart = getBodyPart(0);
          return (MimeBodyPart)rootPart;
      }
  
      /**
       * Set the MultiPart Mime subtype. This method should be invoked only on
       * a new MimeMultipart object created by the client. The default subtype
       * of such a multipart object is "related".<p>
       *
       * @param           subtype         Subtype
       * @exception       MessagingException
       */
      public void setSubType(String subtype) throws MessagingException {
          if (parts == null)
              parts = new MimeMultipart(subtype);
          else
              parts.setSubType(subtype);
      }
  
      /**
       * Return the number of enclosed BodyPart objects.
       *
       * @return          number of parts
       */
      public int getCount() throws MessagingException {
          if (parts == null)
              return 0;
          else
              return parts.getCount();
      }
  
      /**
       * Return the content-type
       *
       * @return          content type of the Mime multipart
       */
      public String getContentType() {
          if (parts == null)
              return null;
          else
              return parts.getContentType();
      }
  
      /**
       * Encode the whole multipart and write to an OutputStream
       *
       * @param       os        stream to write to
       * @exception   IOException
       * @exception   MessagingException
       */
      public void writeTo(OutputStream os)
          throws IOException, MessagingException {
          Session session = Session.getDefaultInstance(new Properties(), null);
          MimeMessage msg = new MimeMessage(session);
          msg.setContent(parts);
          msg.saveChanges();
          msg.writeTo(os, ignoreHeaders);
      }
  
      /**
       * String representation for debug purposes.
       */
      public String toString()  {
          StringWriter sw = new StringWriter();
          PrintWriter pw = new PrintWriter(sw);
  
          pw.print("[Parts={");
  
          if (parts != null) {
              try {
                  for (int i = 0; i < getCount(); i++) {
                      if (i > 0) {
                          pw.print(", ");
                      }
  
                      BodyPart bp = getBodyPart(i);
                      if(bp instanceof MimeBodyPart) {
                          MimeBodyPart mbp = (MimeBodyPart)bp;
                          pw.print("[cid:" + mbp.getContentID()
                                   + " type: " + mbp.getContentType()
                                   + " enc: " + mbp.getEncoding() + "]");
                      }
                      else
                          pw.print("[bodypart]");
                  }
              }
              catch(MessagingException me) {
                  me.printStackTrace();
              }
          }
  
          pw.print("}]");
  
          return sw.toString();
      }
  }