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 }