001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.io.output;
018
019 import java.io.File;
020 import java.io.FileOutputStream;
021 import java.io.FileWriter;
022 import java.io.IOException;
023 import java.io.OutputStream;
024 import java.io.OutputStreamWriter;
025 import java.io.Writer;
026
027 import org.apache.commons.io.FileUtils;
028 import org.apache.commons.io.IOUtils;
029
030 /**
031 * FileWriter that will create and honor lock files to allow simple
032 * cross thread file lock handling.
033 * <p>
034 * This class provides a simple alternative to <code>FileWriter</code>
035 * that will use a lock file to prevent duplicate writes.
036 * <p>
037 * By default, the file will be overwritten, but this may be changed to append.
038 * The lock directory may be specified, but defaults to the system property
039 * <code>java.io.tmpdir</code>.
040 * The encoding may also be specified, and defaults to the platform default.
041 *
042 * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
043 * @author <a href="mailto:ms@collab.net">Michael Salmon</a>
044 * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
045 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
046 * @author Stephen Colebourne
047 * @author Andy Lehane
048 * @version $Id: LockableFileWriter.java 610010 2008-01-08 14:50:59Z niallp $
049 */
050 public class LockableFileWriter extends Writer {
051 // Cannot extend ProxyWriter, as requires writer to be
052 // known when super() is called
053
054 /** The extension for the lock file. */
055 private static final String LCK = ".lck";
056
057 /** The writer to decorate. */
058 private final Writer out;
059 /** The lock file. */
060 private final File lockFile;
061
062 /**
063 * Constructs a LockableFileWriter.
064 * If the file exists, it is overwritten.
065 *
066 * @param fileName the file to write to, not null
067 * @throws NullPointerException if the file is null
068 * @throws IOException in case of an I/O error
069 */
070 public LockableFileWriter(String fileName) throws IOException {
071 this(fileName, false, null);
072 }
073
074 /**
075 * Constructs a LockableFileWriter.
076 *
077 * @param fileName file to write to, not null
078 * @param append true if content should be appended, false to overwrite
079 * @throws NullPointerException if the file is null
080 * @throws IOException in case of an I/O error
081 */
082 public LockableFileWriter(String fileName, boolean append) throws IOException {
083 this(fileName, append, null);
084 }
085
086 /**
087 * Constructs a LockableFileWriter.
088 *
089 * @param fileName the file to write to, not null
090 * @param append true if content should be appended, false to overwrite
091 * @param lockDir the directory in which the lock file should be held
092 * @throws NullPointerException if the file is null
093 * @throws IOException in case of an I/O error
094 */
095 public LockableFileWriter(String fileName, boolean append, String lockDir) throws IOException {
096 this(new File(fileName), append, lockDir);
097 }
098
099 /**
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 }