Use Apache Fileupload streaming API

The Apache Commons Fileupload library offers two different APIs, a
traditional one that stores uploads in temp files before making them
available, and a streaming API that provides direct access to the
upload stream.

The streaming API has many benefits for this add-on:
* it fits better with the concept of a receiver that handles uploaded
  contents
* it makes this add-on independent from the filesystem
* it does not require cleaning up temporary files, we can get rid of
  the CleaningTracker
* it's faster because we don't need to copy streams twice

As a side effect of this change, it's not directly possible to support
the method FileDetails.getContentLength() anymore. However, returning
-1 for "unknown" is in conformance with the contract of this method.
diff --git a/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/CleaningTracker.java b/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/CleaningTracker.java
deleted file mode 100644
index 00e4fe9..0000000
--- a/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/CleaningTracker.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2013 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.addons.fileupload.internal;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.commons.io.FileCleaningTracker;
-import org.apache.commons.io.FileDeleteStrategy;
-
-/*
- * Simpler file cleaning tracker implementation due to bug
- *
- * FileCleaningTracker Reaper thread not ended
- * https://issues.apache.org/jira/browse/FILEUPLOAD-205
- */
-public class CleaningTracker extends FileCleaningTracker {
-
-  private final List<String> filesToDelete;
-  private final List<String> deleteFailures;
-
-  public CleaningTracker() {
-    filesToDelete = new ArrayList<String>();
-    deleteFailures = new ArrayList<String>();
-  }
-
-  @Override
-  public void track( File file, Object marker ) {
-    track( file, marker, null );
-  }
-
-  @Override
-  public void track( File file, Object marker, FileDeleteStrategy deleteStrategy ) {
-    if( file == null ) {
-      throw new NullPointerException( "The file must not be null" );
-    }
-    filesToDelete.add( file.getAbsolutePath() );
-  }
-
-  @Override
-  public void track( String path, Object marker ) {
-    track( path, marker, null );
-  }
-
-  @Override
-  public void track( String path, Object marker, FileDeleteStrategy deleteStrategy ) {
-    if( path == null ) {
-      throw new NullPointerException( "The path must not be null" );
-    }
-    filesToDelete.add( path );
-  }
-
-  @Override
-  public int getTrackCount() {
-    return filesToDelete.size();
-  }
-
-  @Override
-  public List<String> getDeleteFailures() {
-    return deleteFailures;
-  }
-
-  @Override
-  public void exitWhenFinished() {
-    deleteTemporaryFiles();
-  }
-
-  public void deleteTemporaryFiles() {
-    for( String path : filesToDelete ) {
-      File file = new File( path );
-      if( !file.delete() ) {
-        deleteFailures.add( path );
-      }
-    }
-    filesToDelete.clear();
-  }
-
-}
diff --git a/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor.java b/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor.java
index 10a59fd..d9f84a8 100644
--- a/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor.java
+++ b/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2013 EclipseSource and others.
+ * Copyright (c) 2011, 2014 EclipseSource and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -11,17 +11,15 @@
 package org.eclipse.rap.addons.fileupload.internal;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
+import java.io.InputStream;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import org.apache.commons.fileupload.FileItem;
 import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
-import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
 import org.apache.commons.fileupload.ProgressListener;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
 import org.apache.commons.fileupload.servlet.ServletFileUpload;
 import org.eclipse.rap.addons.fileupload.FileDetails;
 import org.eclipse.rap.addons.fileupload.FileUploadHandler;
@@ -32,68 +30,48 @@
 
   private final FileUploadHandler handler;
   private final FileUploadTracker tracker;
-  private final CleaningTracker cleaningTracker;
 
   FileUploadProcessor( FileUploadHandler handler ) {
     this.handler = handler;
     tracker = new FileUploadTracker( handler );
-    cleaningTracker = new CleaningTracker();
   }
 
   void handleFileUpload( HttpServletRequest request, HttpServletResponse response )
     throws IOException
   {
     try {
-      List<FileItem> fileItems = readUploadedFileItems( request );
-      if( !fileItems.isEmpty() ) {
-        FileUploadReceiver receiver = handler.getReceiver();
-        for( FileItem fileItem : fileItems ) {
-          String fileName = stripFileName( fileItem.getName() );
-          String contentType = fileItem.getContentType();
-          long contentLength = fileItem.getSize();
-          FileDetails details = new FileDetailsImpl( fileName, contentType, contentLength );
-          receiver.receive( fileItem.getInputStream(), details );
-          tracker.addFile( details );
+      ServletFileUpload upload = createUpload();
+      FileItemIterator iter = upload.getItemIterator( request );
+      while( iter.hasNext() ) {
+        FileItemStream item = iter.next();
+        if( !item.isFormField() ) {
+          receive( item );
         }
-        tracker.handleFinished();
-      } else {
+      }
+      if( tracker.isEmpty() ) {
         String errorMessage = "No file upload data found in request";
         tracker.setException( new Exception( errorMessage ) );
         tracker.handleFailed();
         response.sendError( HttpServletResponse.SC_BAD_REQUEST, errorMessage );
+      } else {
+        tracker.handleFinished();
       }
-    } catch( FileSizeLimitExceededException exception ) {
-      tracker.setException( exception );
-      tracker.handleFailed();
-      response.sendError( HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, exception.getMessage() );
     } catch( Exception exception ) {
+      Throwable cause = exception.getCause();
+      if( cause instanceof FileSizeLimitExceededException ) {
+        exception = ( Exception )cause;
+      }
       tracker.setException( exception );
       tracker.handleFailed();
-      response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, exception.getMessage() );
+      int errorCode = exception instanceof FileSizeLimitExceededException
+                    ? HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE
+                    : HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+      response.sendError( errorCode, exception.getMessage() );
     }
-    cleaningTracker.deleteTemporaryFiles();
-  }
-
-  private List<FileItem> readUploadedFileItems( HttpServletRequest request )
-    throws FileUploadException
-  {
-    List<FileItem> result = new ArrayList<FileItem>();
-    ServletFileUpload upload = createUpload();
-    List uploadedItems = upload.parseRequest( request );
-    for( Object uploadedItem : uploadedItems ) {
-      FileItem fileItem = ( FileItem )uploadedItem;
-      // Don't check for file size == 0 because this would prevent uploading empty files
-      if( !fileItem.isFormField() ) {
-        result.add( fileItem );
-      }
-    }
-    return result;
   }
 
   private ServletFileUpload createUpload() {
-    DiskFileItemFactory factory = new DiskFileItemFactory();
-    factory.setFileCleaningTracker( cleaningTracker );
-    ServletFileUpload upload = new ServletFileUpload( factory );
+    ServletFileUpload upload = new ServletFileUpload();
     upload.setFileSizeMax( handler.getMaxFileSize() );
     upload.setProgressListener( createProgressListener() );
     return upload;
@@ -117,6 +95,20 @@
     return result;
   }
 
+  private void receive( FileItemStream item ) throws IOException {
+    InputStream stream = item.openStream();
+    try {
+      String fileName = stripFileName( item.getName() );
+      String contentType = item.getContentType();
+      FileDetails details = new FileDetailsImpl( fileName, contentType, -1 );
+      FileUploadReceiver receiver = handler.getReceiver();
+      receiver.receive( stream, details );
+      tracker.addFile( details );
+    } finally {
+      stream.close();
+    }
+  }
+
   private static String stripFileName( String name ) {
     String result = name;
     int lastSlash = result.lastIndexOf( '/' );
diff --git a/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadTracker.java b/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadTracker.java
index 3745173..f7a5601 100644
--- a/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadTracker.java
+++ b/bundles/org.eclipse.rap.addons.fileupload/src/org/eclipse/rap/addons/fileupload/internal/FileUploadTracker.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2013 EclipseSource and others.
+ * Copyright (c) 2011, 2014 EclipseSource and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -35,6 +35,10 @@
     files.add( details );
   }
 
+  boolean isEmpty() {
+    return files.isEmpty();
+  }
+
   void setContentLength( long contentLength ) {
     this.contentLength = contentLength;
   }
diff --git a/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/FileUploadHandler_Test.java b/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/FileUploadHandler_Test.java
index 6218397..e8b9600 100644
--- a/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/FileUploadHandler_Test.java
+++ b/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/FileUploadHandler_Test.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011, 2013 EclipseSource and others.
+ * Copyright (c) 2011, 2014 EclipseSource and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -10,23 +10,12 @@
  ******************************************************************************/
 package org.eclipse.rap.addons.fileupload;
 
-import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 import java.io.IOException;
 import java.io.InputStream;
 
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 
-import org.eclipse.rap.addons.fileupload.FileUploadHandler;
-import org.eclipse.rap.addons.fileupload.FileUploadReceiver;
-import org.eclipse.rap.addons.fileupload.FileDetails;
 import org.eclipse.rap.addons.fileupload.internal.FileUploadHandlerStore;
 import org.eclipse.rap.addons.fileupload.internal.FileUploadServiceHandler;
 import org.eclipse.rap.addons.fileupload.test.FileUploadTestUtil;
@@ -41,6 +30,17 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
 
 @SuppressWarnings( "restriction" )
 public class FileUploadHandler_Test {
@@ -77,7 +77,7 @@
 
   @Test
   public void testInitialized() {
-    assertTrue( handler.getUploadUrl().indexOf( handler.getToken() ) != -1 );
+    assertThat( handler.getUploadUrl(), containsString( handler.getToken() ) );
     assertSame( handler, getRegisteredHandler( handler.getToken() ) );
   }
 
@@ -225,7 +225,7 @@
     serviceHandler.service( ContextProvider.getRequest(), ContextProvider.getResponse() );
 
     assertEquals( HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, getResponseErrorStatus() );
-    assertTrue( getResponseContent().indexOf( "file exceeds its maximum permitted  size" ) != -1 );
+    assertThat( getResponseContent(), containsString( "file exceeds its maximum permitted  size" ) );
   }
 
   @Test
@@ -242,7 +242,7 @@
     serviceHandler.service( ContextProvider.getRequest(), ContextProvider.getResponse() );
 
     assertEquals( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, getResponseErrorStatus() );
-    assertTrue( getResponseContent().indexOf( "the error message" ) != -1 );
+    assertThat( getResponseContent(), containsString( "the error message" ) );
   }
 
   @Test
@@ -278,4 +278,5 @@
     TestResponse response = ( TestResponse )ContextProvider.getResponse();
     return response.getContent();
   }
+
 }
diff --git a/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/internal/CleaningTracker_Test.java b/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/internal/CleaningTracker_Test.java
deleted file mode 100644
index 878df6b..0000000
--- a/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/internal/CleaningTracker_Test.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2013 EclipseSource and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-package org.eclipse.rap.addons.fileupload.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-
-import java.io.File;
-import java.io.IOException;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-
-public class CleaningTracker_Test {
-
-  private File tempFile;
-  private CleaningTracker cleaningTracker;
-
-  @Before
-  public void setUp() throws IOException {
-    tempFile = File.createTempFile( "temp-", null );
-    cleaningTracker = new CleaningTracker();
-  }
-
-  @After
-  public void tearDown() {
-    tempFile.delete();
-  }
-
-  @Test( expected = NullPointerException.class )
-  public void testTrack_nullFile() {
-    cleaningTracker.track( ( File )null, null );
-  }
-
-  @Test( expected = NullPointerException.class )
-  public void testTrack_nullPath() {
-    cleaningTracker.track( ( String )null, null );
-  }
-
-  @Test
-  public void testDeleteTemporaryFiles_trackedByFile_1() {
-    cleaningTracker.track( tempFile, null );
-
-    cleaningTracker.deleteTemporaryFiles();
-
-    assertFalse( tempFile.exists() );
-    assertEquals( 0, cleaningTracker.getDeleteFailures().size() );
-  }
-
-  @Test
-  public void testDeleteTemporaryFiles_trackedByFile_2() {
-    cleaningTracker.track( tempFile, null, null );
-
-    cleaningTracker.deleteTemporaryFiles();
-
-    assertFalse( tempFile.exists() );
-    assertEquals( 0, cleaningTracker.getDeleteFailures().size() );
-  }
-
-  @Test
-  public void testDeleteTemporaryFiles_trackedByPath_1() {
-    cleaningTracker.track( tempFile.getAbsolutePath(), null );
-
-    cleaningTracker.deleteTemporaryFiles();
-
-    assertFalse( tempFile.exists() );
-    assertEquals( 0, cleaningTracker.getDeleteFailures().size() );
-  }
-
-  @Test
-  public void testDeleteTemporaryFiles_trackedByPath_2() {
-    cleaningTracker.track( tempFile.getAbsolutePath(), null, null );
-
-    cleaningTracker.deleteTemporaryFiles();
-
-    assertFalse( tempFile.exists() );
-    assertEquals( 0, cleaningTracker.getDeleteFailures().size() );
-  }
-
-  @Test
-  public void testExitWhenFinished_deletesTemporaryFiles() {
-    cleaningTracker.track( tempFile, null );
-
-    cleaningTracker.exitWhenFinished();
-
-    assertFalse( tempFile.exists() );
-    assertEquals( 0, cleaningTracker.getDeleteFailures().size() );
-  }
-
-  @Test
-  public void testGetTrackCount() {
-    cleaningTracker.track( tempFile, null );
-
-    assertEquals( 1, cleaningTracker.getTrackCount() );
-  }
-
-  @Test
-  public void testGetDeleteFailures() {
-    String path = tempFile.getAbsolutePath();
-    tempFile.delete();
-    tempFile = mock( File.class );
-    doReturn( Boolean.FALSE ).when( tempFile ).delete();
-    doReturn( path ).when( tempFile ).getAbsolutePath();
-    cleaningTracker.track( tempFile, null );
-
-    cleaningTracker.deleteTemporaryFiles();
-
-    assertEquals( 1, cleaningTracker.getDeleteFailures().size() );
-  }
-
-}
diff --git a/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor_Test.java b/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor_Test.java
index fbdb181..1158483 100644
--- a/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor_Test.java
+++ b/tests/org.eclipse.rap.addons.fileupload.test/src/org/eclipse/rap/addons/fileupload/internal/FileUploadProcessor_Test.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013 EclipseSource and others.
+ * Copyright (c) 2013, 2014 EclipseSource and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -13,6 +13,8 @@
 import static org.eclipse.rap.addons.fileupload.test.FileUploadTestUtil.fakeUploadRequest;
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Matchers.any;
+
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -30,6 +32,7 @@
 import org.eclipse.rap.addons.fileupload.FileUploadReceiver;
 import org.eclipse.rap.addons.fileupload.test.FileUploadTestUtil.FileData;
 import org.eclipse.rap.addons.fileupload.test.TestFileUploadListener;
+import org.eclipse.rap.addons.fileupload.test.TestFileUploadReceiver;
 import org.eclipse.rap.rwt.RWT;
 import org.eclipse.rap.rwt.testfixture.Fixture;
 import org.eclipse.rap.rwt.testfixture.TestResponse;
@@ -37,6 +40,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 
 public class FileUploadProcessor_Test {
@@ -82,7 +87,7 @@
     FileDetails[] fileDetails = event.getFileDetails();
     assertEquals( "foo.txt", fileDetails[ 0 ].getFileName() );
     assertEquals( "text/plain", fileDetails[ 0 ].getContentType() );
-    assertEquals( 3, fileDetails[ 0 ].getContentLength() );
+    assertEquals( -1, fileDetails[ 0 ].getContentLength() );
     assertEquals( event.getContentLength(), event.getBytesRead() );
   }
 
@@ -94,7 +99,7 @@
     ArgumentCaptor<FileDetails> captor = ArgumentCaptor.forClass( FileDetails.class );
     verify( receiver ).receive( any( InputStream.class ), captor.capture() );
     FileDetails uploadDetails = captor.getValue();
-    assertEquals( 3, uploadDetails.getContentLength() );
+    assertEquals( -1, uploadDetails.getContentLength() );
     assertEquals( "text/plain", uploadDetails.getContentType() );
     assertEquals( "foo.txt", uploadDetails.getFileName() );
   }
@@ -124,10 +129,10 @@
     FileDetails[] fileDetails = event.getFileDetails();
     assertEquals( "foo.txt", fileDetails[ 0 ].getFileName() );
     assertEquals( "text/plain", fileDetails[ 0 ].getContentType() );
-    assertEquals( 3, fileDetails[ 0 ].getContentLength() );
+    assertEquals( -1, fileDetails[ 0 ].getContentLength() );
     assertEquals( "bar.png", fileDetails[ 1 ].getFileName() );
     assertEquals( "image/png", fileDetails[ 1 ].getContentType() );
-    assertEquals( 3, fileDetails[ 1 ].getContentLength() );
+    assertEquals( -1, fileDetails[ 1 ].getContentLength() );
     assertEquals( event.getContentLength(), event.getBytesRead() );
   }
 
@@ -141,10 +146,10 @@
     ArgumentCaptor<FileDetails> captor = ArgumentCaptor.forClass( FileDetails.class );
     verify( receiver, times( 2 ) ).receive( any( InputStream.class ), captor.capture() );
     List<FileDetails> values = captor.getAllValues();
-    assertEquals( 3, values.get( 0 ).getContentLength() );
+    assertEquals( -1, values.get( 0 ).getContentLength() );
     assertEquals( "text/plain", values.get( 0 ).getContentType() );
     assertEquals( "foo.txt", values.get( 0 ).getFileName() );
-    assertEquals( 3, values.get( 1 ).getContentLength() );
+    assertEquals( -1, values.get( 1 ).getContentLength() );
     assertEquals( "image/png", values.get( 1 ).getContentType() );
     assertEquals( "bar.png", values.get( 1 ).getFileName() );
   }
@@ -164,6 +169,7 @@
   public void testHandleFileUpload_fileExceedsMaxSize() throws IOException {
     uploadHandler.setMaxFileSize( 5 );
     uploadHandler.addUploadListener( testListener );
+    stubReceiveMethod( receiver );
 
     FileData file1 = new FileData( "foo", "text/plain", "foo.txt" );
     FileData file2 = new FileData( "bar bar", "image/png", "bar.png" );
@@ -187,6 +193,16 @@
     assertEquals( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, getResponseErrorStatus() );
   }
 
+  private static void stubReceiveMethod( FileUploadReceiver receiver ) throws IOException {
+    Answer answer = new Answer() {
+      public Object answer( InvocationOnMock invocation ) throws Throwable {
+        new TestFileUploadReceiver().receive( ( InputStream )invocation.getArguments()[0], null );
+        return null;
+      }
+    };
+    doAnswer( answer ).when( receiver ).receive( any( InputStream.class ), any( FileDetails.class ) );
+  }
+
   private static int getResponseErrorStatus() {
     TestResponse response = ( TestResponse )RWT.getResponse();
     return response.getErrorStatus();