View Javadoc

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@apache.org">Scott Sanders</a>
43   * @author <a href="mailto:ms@collab.net">Michael Salmon</a>
44   * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
45   * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
46   * @author Stephen Colebourne
47   * @author Andy Lehane
48   * @version $Id: LockableFileWriter.java 437567 2006-08-28 06:39:07Z bayard $
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     /** @see java.io.Writer#write(int) */
277     public void write(int idx) throws IOException {
278         out.write(idx);
279     }
280 
281     /** @see java.io.Writer#write(char[]) */
282     public void write(char[] chr) throws IOException {
283         out.write(chr);
284     }
285 
286     /** @see java.io.Writer#write(char[], int, int) */
287     public void write(char[] chr, int st, int end) throws IOException {
288         out.write(chr, st, end);
289     }
290 
291     /** @see java.io.Writer#write(String) */
292     public void write(String str) throws IOException {
293         out.write(str);
294     }
295 
296     /** @see java.io.Writer#write(String, int, int) */
297     public void write(String str, int st, int end) throws IOException {
298         out.write(str, st, end);
299     }
300 
301     /** @see java.io.Writer#flush() */
302     public void flush() throws IOException {
303         out.flush();
304     }
305 
306 }