Home | History | Annotate | Download | only in output
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 package org.apache.commons.io.output;
     18 
     19 import java.io.File;
     20 import java.io.FileOutputStream;
     21 import java.io.FileWriter;
     22 import java.io.IOException;
     23 import java.io.OutputStream;
     24 import java.io.OutputStreamWriter;
     25 import java.io.Writer;
     26 
     27 import org.apache.commons.io.FileUtils;
     28 import org.apache.commons.io.IOUtils;
     29 
     30 /**
     31  * FileWriter that will create and honor lock files to allow simple
     32  * cross thread file lock handling.
     33  * <p>
     34  * This class provides a simple alternative to <code>FileWriter</code>
     35  * that will use a lock file to prevent duplicate writes.
     36  * <p>
     37  * By default, the file will be overwritten, but this may be changed to append.
     38  * The lock directory may be specified, but defaults to the system property
     39  * <code>java.io.tmpdir</code>.
     40  * The encoding may also be specified, and defaults to the platform default.
     41  *
     42  * @author <a href="mailto:sanders (at) apache.org">Scott Sanders</a>
     43  * @author <a href="mailto:ms (at) collab.net">Michael Salmon</a>
     44  * @author <a href="mailto:jon (at) collab.net">Jon S. Stevens</a>
     45  * @author <a href="mailto:dlr (at) finemaltcoding.com">Daniel Rall</a>
     46  * @author Stephen Colebourne
     47  * @author Andy Lehane
     48  * @version $Id: LockableFileWriter.java 610010 2008-01-08 14:50:59Z niallp $
     49  */
     50 public class LockableFileWriter extends Writer {
     51     // Cannot extend ProxyWriter, as requires writer to be
     52     // known when super() is called
     53 
     54     /** The extension for the lock file. */
     55     private static final String LCK = ".lck";
     56 
     57     /** The writer to decorate. */
     58     private final Writer out;
     59     /** The lock file. */
     60     private final File lockFile;
     61 
     62     /**
     63      * Constructs a LockableFileWriter.
     64      * If the file exists, it is overwritten.
     65      *
     66      * @param fileName  the file to write to, not null
     67      * @throws NullPointerException if the file is null
     68      * @throws IOException in case of an I/O error
     69      */
     70     public LockableFileWriter(String fileName) throws IOException {
     71         this(fileName, false, null);
     72     }
     73 
     74     /**
     75      * Constructs a LockableFileWriter.
     76      *
     77      * @param fileName  file to write to, not null
     78      * @param append  true if content should be appended, false to overwrite
     79      * @throws NullPointerException if the file is null
     80      * @throws IOException in case of an I/O error
     81      */
     82     public LockableFileWriter(String fileName, boolean append) throws IOException {
     83         this(fileName, append, null);
     84     }
     85 
     86     /**
     87      * Constructs a LockableFileWriter.
     88      *
     89      * @param fileName  the file to write to, not null
     90      * @param append  true if content should be appended, false to overwrite
     91      * @param lockDir  the directory in which the lock file should be held
     92      * @throws NullPointerException if the file is null
     93      * @throws IOException in case of an I/O error
     94      */
     95     public LockableFileWriter(String fileName, boolean append, String lockDir) throws IOException {
     96         this(new File(fileName), append, lockDir);
     97     }
     98 
     99     /**
    100      * Constructs a LockableFileWriter.
    101      * If the file exists, it is overwritten.
    102      *
    103      * @param file  the file to write to, not null
    104      * @throws NullPointerException if the file is null
    105      * @throws IOException in case of an I/O error
    106      */
    107     public LockableFileWriter(File file) throws IOException {
    108         this(file, false, null);
    109     }
    110 
    111     /**
    112      * Constructs a LockableFileWriter.
    113      *
    114      * @param file  the file to write to, not null
    115      * @param append  true if content should be appended, false to overwrite
    116      * @throws NullPointerException if the file is null
    117      * @throws IOException in case of an I/O error
    118      */
    119     public LockableFileWriter(File file, boolean append) throws IOException {
    120         this(file, append, null);
    121     }
    122 
    123     /**
    124      * Constructs a LockableFileWriter.
    125      *
    126      * @param file  the file to write to, not null
    127      * @param append  true if content should be appended, false to overwrite
    128      * @param lockDir  the directory in which the lock file should be held
    129      * @throws NullPointerException if the file is null
    130      * @throws IOException in case of an I/O error
    131      */
    132     public LockableFileWriter(File file, boolean append, String lockDir) throws IOException {
    133         this(file, null, append, lockDir);
    134     }
    135 
    136     /**
    137      * Constructs a LockableFileWriter with a file encoding.
    138      *
    139      * @param file  the file to write to, not null
    140      * @param encoding  the encoding to use, null means platform default
    141      * @throws NullPointerException if the file is null
    142      * @throws IOException in case of an I/O error
    143      */
    144     public LockableFileWriter(File file, String encoding) throws IOException {
    145         this(file, encoding, false, null);
    146     }
    147 
    148     /**
    149      * Constructs a LockableFileWriter with a file encoding.
    150      *
    151      * @param file  the file to write to, not null
    152      * @param encoding  the encoding to use, null means platform default
    153      * @param append  true if content should be appended, false to overwrite
    154      * @param lockDir  the directory in which the lock file should be held
    155      * @throws NullPointerException if the file is null
    156      * @throws IOException in case of an I/O error
    157      */
    158     public LockableFileWriter(File file, String encoding, boolean append,
    159             String lockDir) throws IOException {
    160         super();
    161         // init file to create/append
    162         file = file.getAbsoluteFile();
    163         if (file.getParentFile() != null) {
    164             FileUtils.forceMkdir(file.getParentFile());
    165         }
    166         if (file.isDirectory()) {
    167             throw new IOException("File specified is a directory");
    168         }
    169 
    170         // init lock file
    171         if (lockDir == null) {
    172             lockDir = System.getProperty("java.io.tmpdir");
    173         }
    174         File lockDirFile = new File(lockDir);
    175         FileUtils.forceMkdir(lockDirFile);
    176         testLockDir(lockDirFile);
    177         lockFile = new File(lockDirFile, file.getName() + LCK);
    178 
    179         // check if locked
    180         createLock();
    181 
    182         // init wrapped writer
    183         out = initWriter(file, encoding, append);
    184     }
    185 
    186     //-----------------------------------------------------------------------
    187     /**
    188      * Tests that we can write to the lock directory.
    189      *
    190      * @param lockDir  the File representing the lock directory
    191      * @throws IOException if we cannot write to the lock directory
    192      * @throws IOException if we cannot find the lock file
    193      */
    194     private void testLockDir(File lockDir) throws IOException {
    195         if (!lockDir.exists()) {
    196             throw new IOException(
    197                     "Could not find lockDir: " + lockDir.getAbsolutePath());
    198         }
    199         if (!lockDir.canWrite()) {
    200             throw new IOException(
    201                     "Could not write to lockDir: " + lockDir.getAbsolutePath());
    202         }
    203     }
    204 
    205     /**
    206      * Creates the lock file.
    207      *
    208      * @throws IOException if we cannot create the file
    209      */
    210     private void createLock() throws IOException {
    211         synchronized (LockableFileWriter.class) {
    212             if (!lockFile.createNewFile()) {
    213                 throw new IOException("Can't write file, lock " +
    214                         lockFile.getAbsolutePath() + " exists");
    215             }
    216             lockFile.deleteOnExit();
    217         }
    218     }
    219 
    220     /**
    221      * Initialise the wrapped file writer.
    222      * Ensure that a cleanup occurs if the writer creation fails.
    223      *
    224      * @param file  the file to be accessed
    225      * @param encoding  the encoding to use
    226      * @param append  true to append
    227      * @return The initialised writer
    228      * @throws IOException if an error occurs
    229      */
    230     private Writer initWriter(File file, String encoding, boolean append) throws IOException {
    231         boolean fileExistedAlready = file.exists();
    232         OutputStream stream = null;
    233         Writer writer = null;
    234         try {
    235             if (encoding == null) {
    236                 writer = new FileWriter(file.getAbsolutePath(), append);
    237             } else {
    238                 stream = new FileOutputStream(file.getAbsolutePath(), append);
    239                 writer = new OutputStreamWriter(stream, encoding);
    240             }
    241         } catch (IOException ex) {
    242             IOUtils.closeQuietly(writer);
    243             IOUtils.closeQuietly(stream);
    244             lockFile.delete();
    245             if (fileExistedAlready == false) {
    246                 file.delete();
    247             }
    248             throw ex;
    249         } catch (RuntimeException ex) {
    250             IOUtils.closeQuietly(writer);
    251             IOUtils.closeQuietly(stream);
    252             lockFile.delete();
    253             if (fileExistedAlready == false) {
    254                 file.delete();
    255             }
    256             throw ex;
    257         }
    258         return writer;
    259     }
    260 
    261     //-----------------------------------------------------------------------
    262     /**
    263      * Closes the file writer.
    264      *
    265      * @throws IOException if an I/O error occurs
    266      */
    267     public void close() throws IOException {
    268         try {
    269             out.close();
    270         } finally {
    271             lockFile.delete();
    272         }
    273     }
    274 
    275     //-----------------------------------------------------------------------
    276     /**
    277      * Write a character.
    278      * @param idx the character to write
    279      * @throws IOException if an I/O error occurs
    280      */
    281     public void write(int idx) throws IOException {
    282         out.write(idx);
    283     }
    284 
    285     /**
    286      * Write the characters from an array.
    287      * @param chr the characters to write
    288      * @throws IOException if an I/O error occurs
    289      */
    290     public void write(char[] chr) throws IOException {
    291         out.write(chr);
    292     }
    293 
    294     /**
    295      * Write the specified characters from an array.
    296      * @param chr the characters to write
    297      * @param st The start offset
    298      * @param end The number of characters to write
    299      * @throws IOException if an I/O error occurs
    300      */
    301     public void write(char[] chr, int st, int end) throws IOException {
    302         out.write(chr, st, end);
    303     }
    304 
    305     /**
    306      * Write the characters from a string.
    307      * @param str the string to write
    308      * @throws IOException if an I/O error occurs
    309      */
    310     public void write(String str) throws IOException {
    311         out.write(str);
    312     }
    313 
    314     /**
    315      * Write the specified characters from a string.
    316      * @param str the string to write
    317      * @param st The start offset
    318      * @param end The number of characters to write
    319      * @throws IOException if an I/O error occurs
    320      */
    321     public void write(String str, int st, int end) throws IOException {
    322         out.write(str, st, end);
    323     }
    324 
    325     /**
    326      * Flush the stream.
    327      * @throws IOException if an I/O error occurs
    328      */
    329     public void flush() throws IOException {
    330         out.flush();
    331     }
    332 
    333 }
    334