/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.journal.helpers;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Provides a workaround to the fact that
* {@link java.io.File#renameTo(java.io.File)} doesn't work across NFS file
* systems.
* <p>
* This code is taken from a workaround provided on the Sun Developer Network
* Bug Database (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4073756), by
* mailto:morgan.sziraki@cartesian.co.uk
* </p>
* <p>
* The code is modified to protect against a situation where:</p>
* <ul>
* <li>We need to copy the file across NFS mounted filesystems</li>
* <li>Another process is waiting for the file to be renamed</li>
* </ul><p>
* If we do a simple copy, we leave open the possibility that the second process
* will see the new file created before we have a chance to copy the contents.
* </p>
* <p>
* To avoid this, we create the new file with a modified filename (prefixed by
* an underscore '_'). When the copying is complete, we rename the file to the
* desired name. This rename is within a directory, and therefore does not
* extend across NFS filesystem boundaries, so it should work!
* </p>
* <p>
* Modify again to make sure that neither the destination file nor the temp file
* exist - throw an exception if they do. We can't rename on top of an existing
* file.
* </p>
*
* @author Jim Blake
*/
public class FileMovingUtil {
private FileMovingUtil() {
// No need to instantiate, since the only public method is static.
}
/**
* <p>
* Move a File
* </p>
* <p>
* The renameTo method does not allow action across NFS mounted filesystems.
* This method is the workaround.
* </p>
*
* @param fromFile
* The existing File
* @param toFile
* The new File
* @throws IOException
* if any problems occur
*/
public final static void move(File fromFile, File toFile)
throws IOException {
// try the simple way first.
if (fromFile.renameTo(toFile)) {
return;
}
// copy to the temp file, then rename to the desired name.
checkThatFileDoesntExist(toFile);
File tempFile = createTempFile(toFile);
checkThatFileDoesntExist(tempFile);
copy(fromFile, tempFile);
tempFile.renameTo(toFile);
// delete the old one
if (!fromFile.delete()) {
throw new IOException("Failed to delete '" + fromFile.getParent()
+ "'");
}
}
/**
* If we're trying to rename to a file that already exists, we'll throw an
* exception. Make sure that the same thing happens if the rename is
* accomplished by copying.
*
* @throws IOException
* if the file exists.
*/
private static void checkThatFileDoesntExist(File file) throws IOException {
if (file.exists()) {
throw new IOException("File '" + file.getPath()
+ "' already exists.");
}
}
/**
* Create a temporary File object. Prefix the name of the base file with an
* underscore.
*/
private static File createTempFile(File baseFile) {
File parentDirectory = baseFile.getParentFile();
String filename = baseFile.getName();
return new File(parentDirectory, '_' + filename);
}
/**
* Copy a File
*
* @param fromFile
* The existing File
* @param toFile
* The new File
* @throws IOException
* if any problems are encountered
*/
private final static void copy(File fromFile, File toFile)
throws IOException {
FileInputStream in = new FileInputStream(fromFile);
FileOutputStream out = new FileOutputStream(toFile);
BufferedInputStream inBuffer = new BufferedInputStream(in);
BufferedOutputStream outBuffer = new BufferedOutputStream(out);
int theByte = 0;
while ((theByte = inBuffer.read()) > -1) {
outBuffer.write(theByte);
}
outBuffer.close();
inBuffer.close();
out.close();
in.close();
// cleanupif files are not the same length
if (fromFile.length() != toFile.length()) {
toFile.delete();
throw new IOException("Copy failed: source file length="
+ fromFile.length() + ", target file length="
+ toFile.length());
}
}
}