You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-dev@axis.apache.org by di...@apache.org on 2005/09/20 16:07:40 UTC

svn commit: r290456 - in /webservices/axis/trunk/java/src/org/apache/axis: attachments/ i18n/

Author: dims
Date: Tue Sep 20 07:07:35 2005
New Revision: 290456

URL: http://svn.apache.org/viewcvs?rev=290456&view=rev
Log:
Fix for AXIS-2221 - Attachment Streaming directly from HTTP Request

from Brian Husted


Added:
    webservices/axis/trunk/java/src/org/apache/axis/attachments/DimeAttachmentStreams.java
    webservices/axis/trunk/java/src/org/apache/axis/attachments/IncomingAttachmentStreams.java
    webservices/axis/trunk/java/src/org/apache/axis/attachments/MultipartAttachmentStreams.java
Modified:
    webservices/axis/trunk/java/src/org/apache/axis/attachments/Attachments.java
    webservices/axis/trunk/java/src/org/apache/axis/attachments/AttachmentsImpl.java
    webservices/axis/trunk/java/src/org/apache/axis/i18n/resource.properties

Modified: webservices/axis/trunk/java/src/org/apache/axis/attachments/Attachments.java
URL: http://svn.apache.org/viewcvs/webservices/axis/trunk/java/src/org/apache/axis/attachments/Attachments.java?rev=290456&r1=290455&r2=290456&view=diff
==============================================================================
--- webservices/axis/trunk/java/src/org/apache/axis/attachments/Attachments.java (original)
+++ webservices/axis/trunk/java/src/org/apache/axis/attachments/Attachments.java Tue Sep 20 07:07:35 2005
@@ -237,4 +237,14 @@
      */
 
     public void dispose();
+
+
+
+    /**
+     * Once this method is called, attachments can only be accessed via the InputStreams.
+     * Any other access to the attachments collection (e.g. via getAttachments()) is
+     * prohibited and will cause a ConcurrentModificationException to be thrown.
+     * @return All of the attachment streams.
+     */
+    public IncomingAttachmentStreams getIncomingAttachmentStreams();
 }

Modified: webservices/axis/trunk/java/src/org/apache/axis/attachments/AttachmentsImpl.java
URL: http://svn.apache.org/viewcvs/webservices/axis/trunk/java/src/org/apache/axis/attachments/AttachmentsImpl.java?rev=290456&r1=290455&r2=290456&view=diff
==============================================================================
--- webservices/axis/trunk/java/src/org/apache/axis/attachments/AttachmentsImpl.java (original)
+++ webservices/axis/trunk/java/src/org/apache/axis/attachments/AttachmentsImpl.java Tue Sep 20 07:07:35 2005
@@ -69,6 +69,14 @@
      */
 	private HashMap stackDataHandler = new HashMap();
 
+	 /**
+     * Used to distribute attachment streams without caching them.
+     */
+	 private IncomingAttachmentStreams _streams = null;
+
+	 private boolean _askedForAttachments = false;
+	 private boolean _askedForStreams = false;
+
     /**
      * Construct one of these on a parent Message.
      * Should only ever be called by Message constructor!
@@ -131,6 +139,8 @@
                         soapPart = new org.apache.axis.SOAPPart(null,
                                 mpartStream,
                                 false);
+                        MultiPartRelatedInputStream specificType = (MultiPartRelatedInputStream) mpartStream;
+                        _streams = new MultipartAttachmentStreams(specificType.boundaryDelimitedStream, specificType.orderedParts);
                      } else if (token.equalsIgnoreCase(org.apache.axis.Message.MIME_APPLICATION_DIME)) {
                          try{
                             mpartStream=
@@ -138,6 +148,8 @@
                              soapPart = new org.apache.axis.SOAPPart(null, mpartStream, false);
                          }catch(Exception e){ throw org.apache.axis.AxisFault.makeFault(e);}
                          sendtype=  SEND_TYPE_DIME;
+                         MultiPartDimeInputStream specificType = (MultiPartDimeInputStream) mpartStream;
+                         _streams = new DimeAttachmentStreams(specificType.dimeDelimitedStream);
                     } else if (token.indexOf(org.apache.axis.Message.CONTENT_TYPE_MTOM)!=-1){
                         sendtype = SEND_TYPE_MTOM;
                     }
@@ -179,6 +191,9 @@
      */
     public Part removeAttachmentPart(String reference)
             throws org.apache.axis.AxisFault {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
 
         multipart = null;
 
@@ -207,7 +222,9 @@
      */
     public Part addAttachmentPart(Part newPart)
             throws org.apache.axis.AxisFault {
-
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
 
         multipart = null;
         dimemultipart = null;
@@ -272,6 +289,9 @@
      */
     public void setAttachmentParts(java.util.Collection parts)
             throws org.apache.axis.AxisFault {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
 
         removeAllAttachments();
 
@@ -304,6 +324,9 @@
      */
     public Part getAttachmentByReference(String reference)
             throws org.apache.axis.AxisFault {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
 
         if (null == reference) {
             return null;
@@ -360,6 +383,9 @@
      */
     public java.util.Collection getAttachments()
             throws org.apache.axis.AxisFault {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
 
         mergeinAttachments();
 
@@ -399,6 +425,9 @@
      * @throws org.apache.axis.AxisFault
      */
     public long getContentLength() throws org.apache.axis.AxisFault {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
 
         mergeinAttachments();
 
@@ -513,6 +542,9 @@
      * @return the number of attachments
      */
     public int getAttachmentCount() {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
 
         try {
             mergeinAttachments();
@@ -548,6 +580,9 @@
      *   <P>This method does not touch the SOAP part.</P>
      */
     public void removeAllAttachments() {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
         try {
             multipart = null;
             dimemultipart = null;
@@ -573,6 +608,9 @@
      */
     public java.util.Iterator getAttachments(
             javax.xml.soap.MimeHeaders headers) {
+        if (_askedForStreams) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
         java.util.Vector vecParts = new java.util.Vector();
         java.util.Iterator iterator = GetAttachmentsIterator();
         while(iterator.hasNext()){
@@ -676,5 +714,21 @@
             return "NONE";
         }
         return null;
+    }
+
+    /**
+     * Once this method is called, attachments can only be accessed via the InputStreams.
+     * Any other access to the attachments collection (e.g. via getAttachments()) is
+     * prohibited and will cause a IllegalStateException to be thrown.
+     *
+     * @return All of the attachment streams.
+     */
+    public IncomingAttachmentStreams getIncomingAttachmentStreams() {
+        if (_askedForAttachments) {
+            throw new IllegalStateException(Messages.getMessage("concurrentModificationOfStream"));
+        }
+        _askedForStreams = true;
+        mpartStream = null; // todo: comment
+        return _streams;
     }
 }

Added: webservices/axis/trunk/java/src/org/apache/axis/attachments/DimeAttachmentStreams.java
URL: http://svn.apache.org/viewcvs/webservices/axis/trunk/java/src/org/apache/axis/attachments/DimeAttachmentStreams.java?rev=290456&view=auto
==============================================================================
--- webservices/axis/trunk/java/src/org/apache/axis/attachments/DimeAttachmentStreams.java (added)
+++ webservices/axis/trunk/java/src/org/apache/axis/attachments/DimeAttachmentStreams.java Tue Sep 20 07:07:35 2005
@@ -0,0 +1,75 @@
+
+package org.apache.axis.attachments;
+
+import java.io.IOException;
+
+import org.apache.axis.AxisFault;
+import org.apache.axis.transport.http.HTTPConstants;
+import org.apache.axis.utils.Messages;
+
+/**
+ * 
+ * This is the concrete implementation of the IncomingAttachmentStreams class
+ * and is used to parse data that is in the DIME format. This class will make
+ * use of Axis’ DimeDelimitedInputStream to parse the data in the HTTP stream
+ * which will give this class the capability of creating
+ * IncomingAttachmentInputStream objects at each marker within the HTTP stream.
+ * 
+ * @author David Wong
+ * @author Brian Husted
+ *
+ */
+public final class DimeAttachmentStreams extends IncomingAttachmentStreams
+{
+   private DimeDelimitedInputStream _delimitedStream = null;
+   
+   public DimeAttachmentStreams(DimeDelimitedInputStream stream)
+      throws AxisFault
+   {
+      if (stream == null)
+      {
+         throw new AxisFault(Messages.getMessage("nullDelimitedStream"));
+      }
+      _delimitedStream = stream;
+   }
+   
+   /* (non-Javadoc)
+    * @see org.apache.axis.attachments.IncomingAttachmentStreams#getNextStream()
+    */
+   public IncomingAttachmentInputStream getNextStream() throws AxisFault
+   {
+      IncomingAttachmentInputStream stream = null;
+      
+      if (!isReadyToGetNextStream())
+      {
+         throw new IllegalStateException(Messages.getMessage("nextStreamNotReady"));
+      }
+      try
+      {
+         _delimitedStream = _delimitedStream.getNextStream();
+         if (_delimitedStream == null)
+         {
+            return null;
+         }
+         stream = new IncomingAttachmentInputStream(_delimitedStream);
+      }
+      catch (IOException e)
+      {
+         throw new AxisFault(Messages.getMessage("failedToGetDelimitedAttachmentStream"), e);
+      }
+
+      String value = _delimitedStream.getContentId();
+      if (value != null && value.length() > 0)
+      {
+         stream.addHeader(HTTPConstants.HEADER_CONTENT_ID, value);
+      }
+      value = _delimitedStream.getType();
+      if (value != null && value.length() > 0)
+      {
+         stream.addHeader(HTTPConstants.HEADER_CONTENT_TYPE, value);
+      }
+      setReadyToGetNextStream(false);
+      return stream;
+   }
+
+}

Added: webservices/axis/trunk/java/src/org/apache/axis/attachments/IncomingAttachmentStreams.java
URL: http://svn.apache.org/viewcvs/webservices/axis/trunk/java/src/org/apache/axis/attachments/IncomingAttachmentStreams.java?rev=290456&view=auto
==============================================================================
--- webservices/axis/trunk/java/src/org/apache/axis/attachments/IncomingAttachmentStreams.java (added)
+++ webservices/axis/trunk/java/src/org/apache/axis/attachments/IncomingAttachmentStreams.java Tue Sep 20 07:07:35 2005
@@ -0,0 +1,163 @@
+
+package org.apache.axis.attachments;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.axis.AxisFault;
+import org.apache.axis.transport.http.HTTPConstants;
+import org.apache.axis.utils.Messages;
+
+/**
+ * Similiar in concept to an iterator over the delimited streams inside
+ * of the HTTP stream. One difference between this class and a full fledge
+ * iterator is that the class is unable to tell if there are more streams until
+ * the last one has been fully read. It will however, return null when the end
+ * of the HTTP stream has been reached. Since the HTTP stream can contain data
+ * in different formats (e.g. DIME or SwA), the IncomingAttachmentStreams class
+ * will be an abstract class letting its derivatives handle the specifics to
+ * parsing out the HTTP stream. However, the class will implement methods that
+ * keep track of when each of the delimited streams are completely read. This is
+ * necessary since the next stream cannot be created until the previous stream
+ * has been fully read due to the fact that we are actually dealing with a
+ * single stream delimited by markers.
+ * 
+ * @author David Wong
+ * @author Brian Husted
+ */
+public abstract class IncomingAttachmentStreams {
+    private boolean _readyToGetNextStream = true;
+
+    /**
+     * @return The next delimited stream or null if no additional streams are
+     *         left.
+     */
+    public abstract IncomingAttachmentInputStream getNextStream()
+            throws AxisFault;
+
+    /**
+     * @return True if the next stream can be read, false otherwise.
+     */
+    public final boolean isReadyToGetNextStream() {
+        return _readyToGetNextStream;
+    }
+
+    /**
+     * Set the ready flag. Intended for the inner class to use.
+     * 
+     * @param ready
+     */
+    protected final void setReadyToGetNextStream(boolean ready) {
+        _readyToGetNextStream = ready;
+    }
+
+    public final class IncomingAttachmentInputStream extends InputStream {
+        private HashMap _headers = null;
+
+        private InputStream _stream = null;
+
+        /**
+         * @param in
+         */
+        public IncomingAttachmentInputStream(InputStream in) {
+            _stream = in;
+        }
+
+        /**
+         * @return MIME headers for this attachment. May be null if no headers
+         *         were set.
+         */
+        public Map getHeaders() {
+            return _headers;
+        }
+
+        /**
+         * Add a header.
+         * 
+         * @param name
+         * @param value
+         */
+        public void addHeader(String name, String value) {
+            if (_headers == null) {
+                _headers = new HashMap();
+            }
+            _headers.put(name, value);
+        }
+
+        /**
+         * Get a header value.
+         * 
+         * @param name
+         * @return The header found or null if not found.
+         */
+        public String getHeader(String name) {
+            Object header = null;
+            if (_headers == null || (header = _headers.get(name)) == null) {
+                return null;
+            }
+            return header.toString();
+        }
+
+        /**
+         * @return The header with HTTPConstants.HEADER_CONTENT_ID as the key.
+         */
+        public String getContentId() {
+            return getHeader(HTTPConstants.HEADER_CONTENT_ID);
+        }
+
+        /**
+         * @return The header with HTTPConstants.HEADER_CONTENT_LOCATION as the
+         *         key.
+         */
+        public String getContentLocation() {
+            return getHeader(HTTPConstants.HEADER_CONTENT_LOCATION);
+        }
+
+        /**
+         * @return The header with HTTPConstants.HEADER_CONTENT_TYPE as the key.
+         */
+        public String getContentType() {
+            return getHeader(HTTPConstants.HEADER_CONTENT_TYPE);
+        }
+
+        /**
+         * Don't want to support mark and reset since this may get us into
+         * concurrency problem when different pieces of software may have a
+         * handle to the underlying InputStream.
+         */
+        public boolean markSupported() {
+            return false;
+        }
+
+        public void reset() throws IOException {
+            throw new IOException(Messages.getMessage("markNotSupported"));
+        }
+
+        public void mark(int readLimit) {
+            // do nothing
+        }
+
+        public int read() throws IOException {
+            int retval = _stream.read();
+            IncomingAttachmentStreams.this
+                    .setReadyToGetNextStream(retval == -1);
+            return retval;
+        }
+
+        public int read(byte[] b) throws IOException {
+            int retval = _stream.read(b);
+            IncomingAttachmentStreams.this
+                    .setReadyToGetNextStream(retval == -1);
+            return retval;
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+            int retval = _stream.read(b, off, len);
+            IncomingAttachmentStreams.this
+                    .setReadyToGetNextStream(retval == -1);
+            return retval;
+        }
+    }
+}
\ No newline at end of file

Added: webservices/axis/trunk/java/src/org/apache/axis/attachments/MultipartAttachmentStreams.java
URL: http://svn.apache.org/viewcvs/webservices/axis/trunk/java/src/org/apache/axis/attachments/MultipartAttachmentStreams.java?rev=290456&view=auto
==============================================================================
--- webservices/axis/trunk/java/src/org/apache/axis/attachments/MultipartAttachmentStreams.java (added)
+++ webservices/axis/trunk/java/src/org/apache/axis/attachments/MultipartAttachmentStreams.java Tue Sep 20 07:07:35 2005
@@ -0,0 +1,146 @@
+package org.apache.axis.attachments;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetHeaders;
+import javax.mail.internet.MimeUtility;
+import javax.xml.soap.SOAPException;
+
+import org.apache.axis.AxisFault;
+import org.apache.axis.transport.http.HTTPConstants;
+import org.apache.axis.utils.Messages;
+
+/**
+ * The MultipartAttachmentStreams class is used to create
+ * IncomingAttachmentInputStream objects when the HTTP stream shows a marked
+ * separation between the SOAP and each attachment parts. Unlike the DIME
+ * version, this class will use the BoundaryDelimitedStream to parse data in the
+ * SwA format. Another difference between the two is that the
+ * MultipartAttachmentStreams class must also provide a way to hold attachment
+ * parts parsed prior to where the SOAP part appears in the HTTP stream (i.e.
+ * the root part of the multipart-related message). Our DIME counterpart didn’t
+ * have to worry about this since the SOAP part is guaranteed to be the first in
+ * the stream. But since SwA has no such guarantee, we must fall back to caching
+ * these first parts. Afterwards, we can stream the rest of the attachments that
+ * are after the SOAP part of the request message.
+ * 
+ * @author David Wong
+ * @author Brian Husted
+ *  
+ */
+public final class MultipartAttachmentStreams extends IncomingAttachmentStreams {
+    private BoundaryDelimitedStream _delimitedStream = null;
+
+    private Iterator _attachmentParts = null;
+
+    public MultipartAttachmentStreams(BoundaryDelimitedStream delimitedStream)
+            throws AxisFault {
+        this(delimitedStream, null);
+    }
+
+    public MultipartAttachmentStreams(BoundaryDelimitedStream delimitedStream,
+            Collection priorParts) throws AxisFault {
+        if (delimitedStream == null) {
+            throw new AxisFault(Messages.getMessage("nullDelimitedStream"));
+        }
+        _delimitedStream = delimitedStream;
+        if (priorParts != null) {
+            setAttachmentsPriorToSoapPart(priorParts.iterator());
+        }
+    }
+
+    public void setAttachmentsPriorToSoapPart(Iterator iterator) {
+        _attachmentParts = iterator;
+    }
+
+    /**
+     * 
+     * @see org.apache.axis.attachments.IncomingAttachmentStreams#getNextStream()
+     */
+    public IncomingAttachmentInputStream getNextStream() throws AxisFault {
+        IncomingAttachmentInputStream stream = null;
+        if (!isReadyToGetNextStream()) {
+            throw new IllegalStateException(Messages
+                    .getMessage("nextStreamNotReady"));
+        }
+        if (_attachmentParts != null && _attachmentParts.hasNext()) {
+            AttachmentPart part = (AttachmentPart) _attachmentParts.next();
+
+            try {
+                stream = new IncomingAttachmentInputStream(part
+                        .getDataHandler().getInputStream());
+            } catch (IOException e) {
+                throw new AxisFault(Messages
+                        .getMessage("failedToGetAttachmentPartStream"), e);
+            } catch (SOAPException e) {
+                throw new AxisFault(Messages
+                        .getMessage("failedToGetAttachmentPartStream"), e);
+            }
+            stream.addHeader(HTTPConstants.HEADER_CONTENT_ID, part
+                    .getContentId());
+            stream.addHeader(HTTPConstants.HEADER_CONTENT_LOCATION, part
+                    .getContentLocation());
+            stream.addHeader(HTTPConstants.HEADER_CONTENT_TYPE, part
+                    .getContentType());
+        } else {
+            InternetHeaders headers = null;
+
+            try {
+                _delimitedStream = _delimitedStream.getNextStream();
+                if (_delimitedStream == null) {
+                    return null;
+                }
+                headers = new InternetHeaders(_delimitedStream);
+                String delimiter = null; // null for the first header
+                String encoding = headers.getHeader(
+                        HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING,
+                        delimiter);
+                if (encoding != null && encoding.length() > 0) {
+                    encoding = encoding.trim();
+                    stream = new IncomingAttachmentInputStream(MimeUtility
+                            .decode(_delimitedStream, encoding));
+                    stream.addHeader(
+                            HTTPConstants.HEADER_CONTENT_TRANSFER_ENCODING,
+                            encoding);
+                } else {
+                    stream = new IncomingAttachmentInputStream(_delimitedStream);
+                }
+            } catch (IOException e) {
+                throw new AxisFault(Messages
+                        .getMessage("failedToGetDelimitedAttachmentStream"), e);
+            } catch (MessagingException e) {
+                throw new AxisFault(Messages
+                        .getMessage("failedToGetDelimitedAttachmentStream"), e);
+            }
+            Header header = null;
+            Enumeration enum = headers.getAllHeaders();
+            String name = null;
+            String value = null;
+            while (enum != null && enum.hasMoreElements()) {
+                header = (Header) enum.nextElement();
+                name = header.getName();
+                value = header.getValue();
+                if (HTTPConstants.HEADER_CONTENT_ID.equals(name)
+                        || HTTPConstants.HEADER_CONTENT_TYPE.equals(name)
+                        || HTTPConstants.HEADER_CONTENT_LOCATION.equals(name)) {
+                    value = value.trim();
+                    if ((HTTPConstants.HEADER_CONTENT_ID.equals(name) || HTTPConstants.HEADER_CONTENT_LOCATION
+                            .equals(name))
+                            && (name.indexOf('>') > 0 || name.indexOf('<') > 0)) {
+                        value = new StringTokenizer(value, "<>").nextToken();
+                    }
+                }
+                stream.addHeader(name, value);
+            }
+        }
+        setReadyToGetNextStream(false);
+        return stream;
+    }
+
+}
\ No newline at end of file

Modified: webservices/axis/trunk/java/src/org/apache/axis/i18n/resource.properties
URL: http://svn.apache.org/viewcvs/webservices/axis/trunk/java/src/org/apache/axis/i18n/resource.properties?rev=290456&r1=290455&r2=290456&view=diff
==============================================================================
--- webservices/axis/trunk/java/src/org/apache/axis/i18n/resource.properties (original)
+++ webservices/axis/trunk/java/src/org/apache/axis/i18n/resource.properties Tue Sep 20 07:07:35 2005
@@ -1078,6 +1078,12 @@
 
 cannotDoWrappedMode00=Warning: Element {0} has no type declaration, hence it is not a wrapper element. Switching off wrapped mode.
 
+nextStreamNotReady=Asked for the next delimited stream before the previous delimited stream is fully read
+failedToGetAttachmentPartStream=Exception occured when asking AttachmentPart.DataHandler for InputStream.
+nullDelimitedStream=The delimited stream used to initialize an IncomingAttachmentStreams object cannot be null.
+failedToGetDelimitedAttachmentStream=Exception occured when asking the delimited stream for the next stream.
+markNotSupported=Mark and reset features are not supported by this InputStream.
+concurrentModificationOfStream=The attachments stream can only be accessed once; either by using the IncomingAttachmentStreams class or by getting a collection of AttachmentPart objects.  They cannot both be called within the life time of the same service request.
 #                                                                    #
 # In-use keys                                                        #
 ######################################################################