Author: veithen
Date: Mon Aug 22 18:44:37 2011
New Revision: 1160369

URL: http://svn.apache.org/viewvc?rev=1160369&view=rev
Log:
AXIOM-377 (step 4): Implemented DataHandlerExt#readOnce() to enable streaming 
of the MIME part content.

Modified:
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataHandler.java
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataSource.java
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartImpl.java
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/DataHandlerExt.java
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/impl/DataHandlerExtImpl.java
    
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java
    
webservices/commons/trunk/modules/axiom/modules/axiom-testutils/src/main/java/org/apache/axiom/testutils/activation/RandomDataSource.java

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataHandler.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataHandler.java?rev=1160369&r1=1160368&r2=1160369&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataHandler.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataHandler.java
 Mon Aug 22 18:44:37 2011
@@ -19,6 +19,7 @@
 package org.apache.axiom.attachments;
 
 import java.io.IOException;
+import java.io.InputStream;
 
 import javax.activation.DataHandler;
 import javax.activation.DataSource;
@@ -41,6 +42,10 @@ class PartDataHandler extends DataHandle
         return dataSource == null ? super.getDataSource() : dataSource;
     }
 
+    public InputStream readOnce() throws IOException {
+        return part.getInputStream(false);
+    }
+
     public void purgeDataSource() throws IOException {
         part.releaseContent();
     }

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataSource.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataSource.java?rev=1160369&r1=1160368&r2=1160369&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataSource.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartDataSource.java
 Mon Aug 22 18:44:37 2011
@@ -37,7 +37,7 @@ class PartDataSource implements DataSour
     }
 
     public InputStream getInputStream() throws IOException {
-        return part.getInputStream();
+        return part.getInputStream(true);
     }
 
     public String getName() {

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartImpl.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartImpl.java?rev=1160369&r1=1160368&r2=1160369&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartImpl.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/PartImpl.java
 Mon Aug 22 18:44:37 2011
@@ -42,6 +42,28 @@ import java.util.Hashtable;
  * Actual implementation of the {@link Part} interface.
  */
 final class PartImpl implements Part {
+    /**
+     * The part has not been read yet. In this case the parser is in state
+     * {@link EntityState#T_BODY}.
+     */
+    private static final int STATE_UNREAD = 0;
+    
+    /**
+     * The part has been read into a memory or file based buffer.
+     */
+    private static final int STATE_BUFFERED = 1;
+    
+    /**
+     * The part content is being streamed, i.a. the application code consumes 
the part content
+     * without buffering.
+     */
+    private static final int STATE_STREAMING = 2;
+    
+    /**
+     * The part content has been discarded and can no longer be read. This 
state is reached either
+     * when the content has been streamed or when it is discarded explicitly 
after being buffered.
+     */
+    private static final int STATE_DISCARDED = 3;
 
     private static Log log = LogFactory.getLog(PartImpl.class);
     
@@ -52,14 +74,16 @@ final class PartImpl implements Part {
     // Value is a javax.mail.Header object
     private Hashtable headers;
     
+    private int state = STATE_UNREAD;
+    
     /**
-     * The MIME parser from which the content of this part is read. This is 
only set if the content
-     * has not been read yet. In this case the parser is in state {@link 
EntityState#T_BODY}.
+     * The MIME parser from which the content of this part is read. This is 
only set if the state is
+     * {@link #STATE_UNREAD} or {@link #STATE_STREAMING}.
      */
     private MimeTokenStream parser;
     
     /**
-     * The content of this part. This is only set if the content of the part 
is buffered.
+     * The content of this part. This is only set if the state is {@link 
#STATE_BUFFERED}.
      */
     private ContentStore content;
     
@@ -155,14 +179,15 @@ final class PartImpl implements Part {
     }
 
     private ContentStore getContent() {
-        if (content == null) {
-            if (parser == null) {
-                throw new IllegalStateException("The content of the MIME part 
has already been consumed");
-            } else {
+        switch (state) {
+            case STATE_UNREAD:
                 fetch();
-            }
+                // Fall through
+            case STATE_BUFFERED:
+                return content;
+            default:
+                throw new IllegalStateException("The content of the MIME part 
has already been consumed");
         }
-        return content;
     }
     
     private static void checkParserState(EntityState state, EntityState 
expected) throws IllegalStateException {
@@ -178,36 +203,53 @@ final class PartImpl implements Part {
      * in the stream.
      */
     void fetch() {
-        if (content == null && parser != null) {
-            checkParserState(parser.getState(), EntityState.T_BODY);
-            
-            // The PartFactory will determine which Part implementation is 
most appropriate.
-            content = 
ContentStoreFactory.createContentStore(message.getLifecycleManager(),
-                                          parser.getDecodedInputStream(), 
-                                          isSOAPPart, 
-                                          message.getThreshold(),
-                                          message.getAttachmentRepoDir(),
-                                          message.getContentLengthIfKnown());  
// content-length for the whole message
-            try {
-                checkParserState(parser.next(), EntityState.T_END_BODYPART);
-                EntityState state = parser.next();
-                if (state == EntityState.T_EPILOGUE) {
-                    while (parser.next() != EntityState.T_END_MULTIPART) {
-                        // Just loop
-                    }
-                } else if (state != EntityState.T_START_BODYPART && state != 
EntityState.T_END_MULTIPART) {
-                    throw new IllegalStateException("Internal error: 
unexpected parser state " + state);
+        switch (state) {
+            case STATE_UNREAD:
+                checkParserState(parser.getState(), EntityState.T_BODY);
+                
+                // The PartFactory will determine which Part implementation is 
most appropriate.
+                content = 
ContentStoreFactory.createContentStore(message.getLifecycleManager(),
+                                              parser.getDecodedInputStream(), 
+                                              isSOAPPart, 
+                                              message.getThreshold(),
+                                              message.getAttachmentRepoDir(),
+                                              
message.getContentLengthIfKnown());  // content-length for the whole message
+                moveToNextPart();
+                state = STATE_BUFFERED;
+                break;
+            case STATE_STREAMING:
+                moveToNextPart();
+                state = STATE_DISCARDED;
+        }
+    }
+    
+    private void moveToNextPart() {
+        try {
+            checkParserState(parser.next(), EntityState.T_END_BODYPART);
+            EntityState state = parser.next();
+            if (state == EntityState.T_EPILOGUE) {
+                while (parser.next() != EntityState.T_END_MULTIPART) {
+                    // Just loop
                 }
-            } catch (IOException ex) {
-                throw new OMException(ex);
-            } catch (MimeException ex) {
-                throw new OMException(ex);
+            } else if (state != EntityState.T_START_BODYPART && state != 
EntityState.T_END_MULTIPART) {
+                throw new IllegalStateException("Internal error: unexpected 
parser state " + state);
             }
+        } catch (IOException ex) {
+            throw new OMException(ex);
+        } catch (MimeException ex) {
+            throw new OMException(ex);
         }
+        parser = null;
     }
     
-    InputStream getInputStream() throws IOException {
-        return getContent().getInputStream();
+    InputStream getInputStream(boolean preserve) throws IOException {
+        if (!preserve && state == STATE_UNREAD) {
+            checkParserState(parser.getState(), EntityState.T_BODY);
+            state = STATE_STREAMING;
+            return parser.getDecodedInputStream();
+        } else {
+            return getContent().getInputStream();
+        }
     }
     
     DataSource getDataSource() {
@@ -219,17 +261,20 @@ final class PartImpl implements Part {
     }
 
     void releaseContent() throws IOException {
-        if (content != null) {
-            content.destroy();
-        } else if (parser != null) {
-            try {
-                EntityState state;
-                do {
-                    state = parser.next();
-                } while (state != EntityState.T_START_BODYPART && state != 
EntityState.T_END_MULTIPART);
-            } catch (MimeException ex) {
-                throw new OMException(ex);
-            }
+        switch (state) {
+            case STATE_UNREAD:
+                try {
+                    EntityState state;
+                    do {
+                        state = parser.next();
+                    } while (state != EntityState.T_START_BODYPART && state != 
EntityState.T_END_MULTIPART);
+                } catch (MimeException ex) {
+                    throw new OMException(ex);
+                }
+                state = STATE_DISCARDED;
+                break;
+            case STATE_BUFFERED:
+                content.destroy();
         }
     }
 }

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/DataHandlerExt.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/DataHandlerExt.java?rev=1160369&r1=1160368&r2=1160369&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/DataHandlerExt.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/DataHandlerExt.java
 Mon Aug 22 18:44:37 2011
@@ -20,8 +20,22 @@
 package org.apache.axiom.attachments.lifecycle;
 
 import java.io.IOException;
+import java.io.InputStream;
+
+import javax.activation.DataHandler;
 
 public interface DataHandlerExt {
+    /**
+     * Get an {@link InputStream} that consumes the content of this data 
handler. This method is
+     * similar to {@link DataHandler#getInputStream()} except that it can be 
invoked only once. If
+     * the content has not been buffered yet, then the implementation may 
choose to enable streaming
+     * of the content.
+     * 
+     * @return the stream representing the content; never <code>null</code>
+     * @throws IOException
+     *             if an error occurs
+     */
+    InputStream readOnce() throws IOException;
        
        /**
         * This method will give users an option to trigger a purge

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/impl/DataHandlerExtImpl.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/impl/DataHandlerExtImpl.java?rev=1160369&r1=1160368&r2=1160369&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/impl/DataHandlerExtImpl.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/main/java/org/apache/axiom/attachments/lifecycle/impl/DataHandlerExtImpl.java
 Mon Aug 22 18:44:37 2011
@@ -21,6 +21,7 @@ package org.apache.axiom.attachments.lif
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.Observable;
 import java.util.Observer;
 
@@ -47,7 +48,13 @@ public class DataHandlerExtImpl extends 
                this.manager = manager;
        }
 
-       public void deleteWhenReadOnce() throws IOException {
+       public InputStream readOnce() throws IOException {
+           // Instances of DataHandlerExtImpl are only created by legacy code, 
so it's OK to
+           // throw an UnsupportedOperationException here.
+        throw new UnsupportedOperationException();
+    }
+
+    public void deleteWhenReadOnce() throws IOException {
                deleteOnreadOnce = true;
                FileAccessor fa =manager.getFileAccessor(getName());
                if(fa==null){

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java?rev=1160369&r1=1160368&r2=1160369&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-api/src/test/java/org/apache/axiom/attachments/AttachmentsTest.java
 Mon Aug 22 18:44:37 2011
@@ -25,6 +25,8 @@ import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -32,6 +34,7 @@ import java.util.Random;
 import java.util.Set;
 
 import javax.activation.DataHandler;
+import javax.activation.DataSource;
 import javax.mail.Session;
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
@@ -42,6 +45,7 @@ import org.apache.axiom.attachments.life
 import org.apache.axiom.om.AbstractTestCase;
 import org.apache.axiom.om.OMException;
 import org.apache.axiom.om.TestConstants;
+import org.apache.axiom.testutils.activation.RandomDataSource;
 import org.apache.axiom.testutils.io.IOTestUtils;
 import org.apache.axiom.util.UIDGenerator;
 import org.apache.commons.io.IOUtils;
@@ -516,4 +520,67 @@ public class AttachmentsTest extends Abs
         
         in.close();
     }
+
+    /**
+     * Tests that a call to {@link DataHandlerExt#readOnce()} on a {@link 
DataHandler} returned by
+     * the {@link Attachments} object streams the content of the MIME part.
+     * 
+     * @throws Exception
+     */
+    public void testDataHandlerStreaming() throws Exception {
+        // Note: We are only interested in the MimeMultipart, but we need to 
create a
+        //       MimeMessage to be able to calculate the correct content type
+        MimeMessage message = new MimeMessage((Session)null);
+        final MimeMultipart mp = new MimeMultipart("related");
+        
+        // Prepare the "SOAP" part
+        MimeBodyPart bp1 = new MimeBodyPart();
+        // Obviously this is not SOAP, but this is irrelevant for this test
+        bp1.setText("<root/>", "utf-8", "xml");
+        bp1.addHeader("Content-Transfer-Encoding", "binary");
+        bp1.addHeader("Content-ID", "[email protected]");
+        mp.addBodyPart(bp1);
+        
+        // Create an attachment that is larger than the maximum heap
+        DataSource dataSource = new 
RandomDataSource((int)Math.min(Runtime.getRuntime().maxMemory(), 
Integer.MAX_VALUE));
+        MimeBodyPart bp2 = new MimeBodyPart();
+        bp2.setDataHandler(new DataHandler(dataSource));
+        bp2.addHeader("Content-Transfer-Encoding", "binary");
+        bp2.addHeader("Content-ID", "[email protected]");
+        mp.addBodyPart(bp2);
+        
+        message.setContent(mp);
+        // Compute the correct content type
+        message.saveChanges();
+        
+        // We use a pipe (with a producer running in a separate thread) 
because obviously we can't
+        // store the multipart in memory.
+        final PipedOutputStream pipeOut = new PipedOutputStream();
+        PipedInputStream pipeIn = new PipedInputStream(pipeOut);
+        
+        Thread producerThread = new Thread(new Runnable() {
+            public void run() {
+                try {
+                    try {
+                        mp.writeTo(pipeOut);
+                    } finally {
+                        pipeOut.close();
+                    }
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                }
+            }
+        });
+        producerThread.start();
+        
+        try {
+            // We configure Attachments to buffer MIME parts in memory. If the 
part content is not
+            // streamed, then this will result in an OOM error.
+            Attachments attachments = new Attachments(pipeIn, 
message.getContentType());
+            DataHandlerExt dh = 
(DataHandlerExt)attachments.getDataHandler("[email protected]");
+            IOTestUtils.compareStreams(dataSource.getInputStream(), 
dh.readOnce());
+        } finally {
+            pipeIn.close();
+        }
+    }
 }

Modified: 
webservices/commons/trunk/modules/axiom/modules/axiom-testutils/src/main/java/org/apache/axiom/testutils/activation/RandomDataSource.java
URL: 
http://svn.apache.org/viewvc/webservices/commons/trunk/modules/axiom/modules/axiom-testutils/src/main/java/org/apache/axiom/testutils/activation/RandomDataSource.java?rev=1160369&r1=1160368&r2=1160369&view=diff
==============================================================================
--- 
webservices/commons/trunk/modules/axiom/modules/axiom-testutils/src/main/java/org/apache/axiom/testutils/activation/RandomDataSource.java
 (original)
+++ 
webservices/commons/trunk/modules/axiom/modules/axiom-testutils/src/main/java/org/apache/axiom/testutils/activation/RandomDataSource.java
 Mon Aug 22 18:44:37 2011
@@ -52,7 +52,7 @@ public class RandomDataSource implements
     }
     
     public String getContentType() {
-        return null;
+        return "application/octet-stream";
     }
     
     public InputStream getInputStream() throws IOException {


Reply via email to