AtomicFileOutputStream extends FilterOutputStream { @Override public void close() throws IOException { boolean triedToClose = false, success = false; try { flush(); ((FileOutputStream)out).getChannel().force(true); triedToClose = true; super.close(); success = true; } finally { if (success) { boolean renamed = tmpFile.renameTo(origFile); if (!renamed) { if (origFile.exists() && !origFile.delete()) { throw new IOException("Could not delete original file " + origFile); } try { NativeIO.renameTo(tmpFile, origFile); } catch (NativeIOException e) { throw new IOException("Could not rename temporary file " + tmpFile + " to " + origFile + " due to failure in native rename. " + e.toString()); } } } else { if (!triedToClose) { IOUtils.closeStream(out); } if (!tmpFile.delete()) { LOG.warn("Unable to delete tmp file " + tmpFile); } } } } AtomicFileOutputStream(File f); @Override void close(); void abort();  }
@Test public void testWriteNewFile() throws IOException { OutputStream fos = new AtomicFileOutputStream(DST_FILE); assertFalse(DST_FILE.exists()); fos.write(TEST_STRING.getBytes()); fos.flush(); assertFalse(DST_FILE.exists()); fos.close(); assertTrue(DST_FILE.exists()); String readBackData = DFSTestUtil.readFile(DST_FILE); assertEquals(TEST_STRING, readBackData); }
@Test public void testOverwriteFile() throws IOException { assertTrue("Creating empty dst file", DST_FILE.createNewFile()); OutputStream fos = new AtomicFileOutputStream(DST_FILE); assertTrue("Empty file still exists", DST_FILE.exists()); fos.write(TEST_STRING.getBytes()); fos.flush(); assertEquals("", DFSTestUtil.readFile(DST_FILE)); fos.close(); String readBackData = DFSTestUtil.readFile(DST_FILE); assertEquals(TEST_STRING, readBackData); }
@Test public void testFailToFlush() throws IOException { FileOutputStream fos = new FileOutputStream(DST_FILE); fos.write(TEST_STRING_2.getBytes()); fos.close(); OutputStream failingStream = createFailingStream(); failingStream.write(TEST_STRING.getBytes()); try { failingStream.close(); fail("Close didn't throw exception"); } catch (IOException ioe) { } assertEquals(TEST_STRING_2, DFSTestUtil.readFile(DST_FILE)); assertEquals("Temporary file should have been cleaned up", DST_FILE.getName(), Joiner.on(",").join(TEST_DIR.list())); }
@Test public void testFailToRename() throws IOException { assumeTrue(Shell.WINDOWS); OutputStream fos = null; try { fos = new AtomicFileOutputStream(DST_FILE); fos.write(TEST_STRING.getBytes()); FileUtil.setWritable(TEST_DIR, false); exception.expect(IOException.class); exception.expectMessage("failure in native rename"); try { fos.close(); } finally { fos = null; } } finally { IOUtils.cleanup(null, fos); FileUtil.setWritable(TEST_DIR, true); } }