Revision: 411
          http://svn.sourceforge.net/stripes/?rev=411&view=rev
Author:   tfenne
Date:     2006-09-25 17:49:22 -0700 (Mon, 25 Sep 2006)

Log Message:
-----------
Fix for STS-270: FileBean.save() fails when target file is on a different file 
system.

Modified Paths:
--------------
    trunk/stripes/src/net/sourceforge/stripes/action/FileBean.java

Added Paths:
-----------
    trunk/tests/src/net/sourceforge/stripes/action/
    trunk/tests/src/net/sourceforge/stripes/action/FileBeanTests.java

Modified: trunk/stripes/src/net/sourceforge/stripes/action/FileBean.java
===================================================================
--- trunk/stripes/src/net/sourceforge/stripes/action/FileBean.java      
2006-09-06 11:43:16 UTC (rev 410)
+++ trunk/stripes/src/net/sourceforge/stripes/action/FileBean.java      
2006-09-26 00:49:22 UTC (rev 411)
@@ -14,10 +14,7 @@
  */
 package net.sourceforge.stripes.action;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.FileInputStream;
+import java.io.*;
 
 /**
  * <p>Represents a file that was submitted as part of an HTTP POST request.  
Provides methods for
@@ -97,8 +94,10 @@
     }
 
     /**
-     * Saves the uploaded file to the location on disk represented by File.  
This is currently
-     * implemented as a simple rename of the underlying file that was created 
during upload.
+     * Saves the uploaded file to the location on disk represented by File.  
First attemps a
+     * simple rename of the underlying file that was created during upload as 
this is the
+     * most efficient route. If the rename fails an attempt is made to copy 
the file bit
+     * by bit to the new File and then the temporary file is removed.
      *
      * @param toFile a File object representing a location
      * @throws IOException if the save will fail for a reason that we can 
detect up front, for
@@ -119,22 +118,50 @@
                     + this.file.getAbsolutePath() + " - writability is 
required to move the file.");
         }
 
+        File parent = toFile.getAbsoluteFile().getParentFile();
         if (toFile.exists() && !toFile.canWrite()) {
             throw new IOException("Cannot overwrite existing file at "+ 
toFile.getAbsolutePath());
         }
-        else if (!toFile.exists() && 
!toFile.getAbsoluteFile().getParentFile().canWrite()) {
+        else if (!parent.exists() && !parent.mkdirs()) {
+            throw new IOException("Parent directory of specified file does not 
exist and cannot " +
+                " be created. File location supplied: " + 
toFile.getAbsolutePath());
+        }
+        else if (!toFile.exists() && !parent.canWrite()) {
             throw new IOException("Cannot create new file at location: " + 
toFile.getAbsolutePath());
         }
 
         this.saved = this.file.renameTo(toFile);
 
+        // If the rename didn't work, try copying the darn thing bit by bit
         if (this.saved == false) {
-            throw new IOException("Tried to save file [" + 
this.file.getAbsolutePath() + "] to file ["
-            + toFile.getAbsolutePath() + "but got a false from 
File.renameTo(File).");
+            saveViaCopy(toFile);
         }
     }
 
     /**
+     * Attempts to save the uploaded file to the specified file by performing 
a stream
+     * based copy. This is only used when a rename cannot be executed, e.g. 
because the
+     * target file is on a different file system than the temporary file.
+     *
+     * @param toFile the file to save to
+     */
+    protected void saveViaCopy(File toFile) throws IOException {
+        BufferedOutputStream out = new BufferedOutputStream(new 
FileOutputStream(toFile));
+        BufferedInputStream   in = new BufferedInputStream(new 
FileInputStream(this.file));
+
+        int b;
+        while ((b = in.read()) != -1) {
+            out.write(b);
+        }
+
+        in.close();
+        out.close();
+
+        this.file.delete();
+        this.saved = true;
+    }
+
+    /**
      * Deletes the temporary file associated with this file upload if one 
still exists.  If save()
      * has already been called then there is no temporary file any more, and 
this is a no-op.
      *

Added: trunk/tests/src/net/sourceforge/stripes/action/FileBeanTests.java
===================================================================
--- trunk/tests/src/net/sourceforge/stripes/action/FileBeanTests.java           
                (rev 0)
+++ trunk/tests/src/net/sourceforge/stripes/action/FileBeanTests.java   
2006-09-26 00:49:22 UTC (rev 411)
@@ -0,0 +1,105 @@
+package net.sourceforge.stripes.action;
+
+import org.testng.annotations.Test;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.AfterMethod;
+import org.testng.Assert;
+
+import java.io.*;
+
+/**
+ * Basic set of tests for the FileBean object.
+ *
+ * @author Tim Fennell
+ */
+public class FileBeanTests {
+    public static final String[] LINES = {"Hello World!", "How have you 
been?"};
+    File from, to;
+
+    @BeforeMethod(alwaysRun=true)
+    public void setupFiles() throws IOException {
+        // The from file
+        this.from = File.createTempFile("foo", "bar");
+        FileWriter out = new FileWriter(this.from);
+        out.write(LINES[0]);
+        out.write('\n');
+        out.write(LINES[1]);
+        out.write('\n');
+        out.close();
+
+        // A to file
+        this.to = new File(System.getProperty("java.io.tmpdir"), "foo-" + 
System.currentTimeMillis());
+    }
+
+    @AfterMethod(alwaysRun=true)
+    public void cleanupFiles() {
+        if (this.from != null && this.from.exists()) this.from.delete();
+        if (this.to != null && this.to.exists()) this.to.delete();
+    }
+
+    /** Helper method to assert contents of post-copy file. */
+    private void assertContents(File toFile) throws IOException {
+        BufferedReader in = new BufferedReader(new FileReader(toFile));
+        Assert.assertEquals(in.readLine(), LINES[0]);
+        Assert.assertEquals(in.readLine(), LINES[1]);
+        Assert.assertNull(in.readLine());
+    }
+
+    @Test(groups="fast")
+    public void testBasicSave() throws Exception {
+        FileBean bean = new FileBean(from, "text/plain", "somefile.txt");
+
+        bean.save(this.to);
+        Assert.assertTrue(this.to.exists());
+        Assert.assertFalse(this.from.exists());
+        assertContents(this.to);
+    }
+
+    @Test(groups="fast")
+    public void testSaveByCopy() throws Exception {
+        FileBean bean = new FileBean(from, "text/plain", "somefile.txt");
+
+        bean.saveViaCopy(this.to);
+        Assert.assertTrue(this.to.exists());
+        Assert.assertFalse(this.from.exists());
+        assertContents(this.to);
+    }
+
+    @Test(groups="fast")
+    public void testSaveOverExistingFile() throws Exception {
+        FileBean bean = new FileBean(from, "text/plain", "somefile.txt");
+
+        Assert.assertTrue(this.to.createNewFile());
+        bean.save(this.to);
+        Assert.assertTrue(this.to.exists());
+        Assert.assertFalse(this.from.exists());
+        assertContents(this.to);
+    }
+
+    @Test(groups="fast")
+    public void testSaveOverExistingFileWithContents() throws Exception {
+        FileBean bean = new FileBean(from, "text/plain", "somefile.txt");
+
+        Assert.assertTrue(this.to.createNewFile());
+        BufferedWriter out = new BufferedWriter(new FileWriter(this.to));
+        out.write("This is not what we should read back after the save!\n");
+        out.write("If we get this text back we're in trouble!\n");
+        out.close();
+
+        bean.save(this.to);
+        Assert.assertTrue(this.to.exists());
+        Assert.assertFalse(this.from.exists());
+        assertContents(this.to);
+    }
+
+    @Test(groups="fast")
+    public void testIntoDirectoryThatDoesNotExistYet() throws Exception {
+        FileBean bean = new FileBean(from, "text/plain", "somefile.txt");
+        this.to = new File(this.to, "somechild.txt");
+
+        bean.save(this.to);
+        Assert.assertTrue(this.to.exists());
+        Assert.assertFalse(this.from.exists());
+        assertContents(this.to);
+    }
+}


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
Stripes-development mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/stripes-development

Reply via email to