FileSafeWriter with example
authorGustavo Martin Morcuende <gu.martinm@gmail.com>
Thu, 5 Nov 2015 12:40:21 +0000 (13:40 +0100)
committerGustavo Martin Morcuende <gu.martinm@gmail.com>
Thu, 5 Nov 2015 12:41:43 +0000 (13:41 +0100)
Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriter.java [new file with mode: 0644]
Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriterMain.java [new file with mode: 0644]

diff --git a/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriter.java b/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriter.java
new file mode 100644 (file)
index 0000000..81a33bb
--- /dev/null
@@ -0,0 +1,136 @@
+package de.example.fsync;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * If you want to make sure what you are writing to disk is not going to get
+ * lost you should use this class.
+ * 
+ * <p>Hopefully you are already closing your output streams in a finally block
+ * (if you aren't, you don't deserve my respect) and in that case you could be
+ * wondering what this class is for. <b>From Linux's close(2)</b>:
+ * 
+ * <p><i>A successful close does not guarantee that the data has been successfully
+ * saved to disk, as the kernel defers writes. It is not common for a file system
+ * to flush the buffers when the stream is closed. If you need to be sure that
+ * the data is physically stored use fsync(2). (It will depend on the disk hardware
+ * at this point.)</i>
+ * 
+ * <p> See also: {@link http://thunk.org/tytso/blog/2009/03/15/dont-fear-the-fsync/}
+ *
+ */
+public class FileSafeWriter {
+       private final SafeWriter safeWriter;
+
+       /**
+        * This interface must be used to implement the user's methods in charge of
+        * writing to and closing output stream.
+        */
+       public interface SafeWriter extends Closeable {
+
+               public void doWrite(final OutputStream out) throws IOException;
+       }
+       
+       /**
+        * Creates object with the user's implementation in charge of writing to
+        * output stream.
+        * 
+        * @param safeWriter user's implementation in charge of writing to output
+        * stream
+        */
+       public FileSafeWriter(final SafeWriter safeWriter) {
+               this.safeWriter = safeWriter;
+       }
+
+       /**
+        * Writes data to file in a safe way.
+        * 
+        * <p> This write is atomic (as long as you are not using FAT16, FAT32 or NFS)
+        * in the sense that it is first written to a temporary file which is then
+        * renamed to the final name.
+        * 
+        * @param file destination file
+        * @throws FileNotFoundException if the file exists but is a directory rather
+        * than a regular file, does not exist but cannot be created, or cannot be
+        * opened for any other reason
+        * @throws IOException if an I/O error occurs
+        */
+       public void writeFile(final File file)
+                       throws FileNotFoundException, IOException {
+
+               final File tempFile = this.writeTempFile(file);
+
+               this.renameFile(tempFile, file);
+       }
+
+       /**
+        * Writes data to temporary file.
+        * 
+        * <p>
+        * Note that the name for the temporary file is constructed by appending
+        * random characters to the name (just its name, not the whole path) of
+        * file.
+        * </p>
+        * 
+        * @param file destination file
+        * @return temporary file with the temporarily stored data
+        * @throws FileNotFoundException if the file exists but is a directory rather
+        * than a regular file, does not exist but cannot be created, or cannot be
+        * opened for any other reason
+        * @throws IOException if an I/O error occurs
+        */
+       private File writeTempFile(final File file)
+                       throws FileNotFoundException, IOException {
+
+               final File tempFile = File.createTempFile(file.getName(), ".tmp", null);
+               final FileOutputStream tempFileStream = new FileOutputStream(tempFile, false);
+               try {
+                       safeWriter.doWrite(tempFileStream);
+
+                       /* We want to sync the newly written file to ensure the data is on disk
+                        * when we rename over the destination. Otherwise if we get a system
+                        * crash we can lose both the new and the old file on some filesystems.
+                        * (I.E. those that don't guarantee the data is written to the disk
+                        * before the metadata.)
+                        */
+                       tempFileStream.flush();
+                       tempFileStream.getFD().sync();
+               } finally {
+                       // Override exception (if there is one) Hopefully it will not be a problem.
+                       try {
+                               safeWriter.close();
+                       } finally {
+                               tempFileStream.close();
+                       }
+               }
+
+               return tempFile;
+       }
+
+       /**
+        * This method renames file and tries to clear everything in case of error.
+        * 
+        * <p>Rename file should be atomic (if you are not using FAT16, FAT32 or NFS)
+        * 
+        * <p> See also: {@link http://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html}
+        * 
+        * @param fromFile destination file
+        * @param toFile target file
+        * @throws IOException if there was some error
+        */
+    private void renameFile(final File fromFile, final File toFile) throws IOException {
+        if (!fromFile.renameTo(toFile)) {
+            if (!fromFile.delete()) {
+               System.out.println("[FileSafeWriter] Could not remove temporary file: "
+                                               + fromFile.getAbsolutePath());
+            }
+            throw new IOException("[FileSafeWriter] Could not move from "
+                                       + fromFile.getAbsolutePath() + " to " + toFile.getAbsolutePath());
+        }
+    }
+}
diff --git a/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriterMain.java b/Allgemeines/FileSafeWriter/src/de/example/fsync/FileSafeWriterMain.java
new file mode 100644 (file)
index 0000000..c6144e2
--- /dev/null
@@ -0,0 +1,81 @@
+package de.example.fsync;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+
+
+public class FileSafeWriterMain {
+       private static final File exampleFile = new File("/tmp/fileSafeWriterExample");
+
+       public static void main(String[] args) throws FileNotFoundException, IOException {
+               
+               final FileSafeWriter fileSafeWriter = new FileSafeWriter(new ExampleSafeWriter(9999));
+               fileSafeWriter.writeFile(exampleFile);
+        
+        System.out.println("Result: " + getPersistedData());
+       }
+       
+    private static Integer getPersistedData()
+            throws FileNotFoundException, UnsupportedEncodingException, IOException {
+        InputStream inputStream = null;
+        Reader readerStream = null;
+        BufferedReader reader = null;
+        try {
+            inputStream = new FileInputStream(exampleFile);
+            readerStream = new InputStreamReader(inputStream, "UTF-8");
+            reader = new BufferedReader(readerStream);
+            return Integer.valueOf(reader.readLine());
+        } finally {
+            if (reader != null) {
+                reader.close();
+            }
+            if (readerStream != null) {
+                readerStream.close();
+            }
+            if (readerStream != null) {
+                readerStream.close();
+            }
+        }
+    }
+
+       
+    private static class ExampleSafeWriter implements FileSafeWriter.SafeWriter {
+        private final Integer number;
+        private Writer writerStream = null;
+        private BufferedWriter buffer = null;
+
+        private ExampleSafeWriter(final Integer number) {
+            this.number = number;
+        }
+
+        @Override
+        public void doWrite(final OutputStream out) throws IOException {
+            writerStream = new OutputStreamWriter(out, "UTF-8");
+            buffer = new BufferedWriter(writerStream);
+            buffer.write(String.valueOf(number)); buffer.newLine();
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (buffer != null) {
+                buffer.close();
+            }
+
+            if (writerStream != null) {
+                writerStream.close();
+            }
+        }
+    }
+
+}