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 437567 2006-08-28 06:39:07Z bayard $
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)
083: throws IOException {
084: this (fileName, append, null);
085: }
086:
087: /**
088: * Constructs a LockableFileWriter.
089: *
090: * @param fileName the file to write to, not null
091: * @param append true if content should be appended, false to overwrite
092: * @param lockDir the directory in which the lock file should be held
093: * @throws NullPointerException if the file is null
094: * @throws IOException in case of an I/O error
095: */
096: public LockableFileWriter(String fileName, boolean append,
097: String lockDir) throws IOException {
098: this (new File(fileName), append, lockDir);
099: }
100:
101: /**
102: * Constructs a LockableFileWriter.
103: * If the file exists, it is overwritten.
104: *
105: * @param file the file to write to, not null
106: * @throws NullPointerException if the file is null
107: * @throws IOException in case of an I/O error
108: */
109: public LockableFileWriter(File file) throws IOException {
110: this (file, false, null);
111: }
112:
113: /**
114: * Constructs a LockableFileWriter.
115: *
116: * @param file the file to write to, not null
117: * @param append true if content should be appended, false to overwrite
118: * @throws NullPointerException if the file is null
119: * @throws IOException in case of an I/O error
120: */
121: public LockableFileWriter(File file, boolean append)
122: throws IOException {
123: this (file, append, null);
124: }
125:
126: /**
127: * Constructs a LockableFileWriter.
128: *
129: * @param file the file to write to, not null
130: * @param append true if content should be appended, false to overwrite
131: * @param lockDir the directory in which the lock file should be held
132: * @throws NullPointerException if the file is null
133: * @throws IOException in case of an I/O error
134: */
135: public LockableFileWriter(File file, boolean append, String lockDir)
136: throws IOException {
137: this (file, null, append, lockDir);
138: }
139:
140: /**
141: * Constructs a LockableFileWriter with a file encoding.
142: *
143: * @param file the file to write to, not null
144: * @param encoding the encoding to use, null means platform default
145: * @throws NullPointerException if the file is null
146: * @throws IOException in case of an I/O error
147: */
148: public LockableFileWriter(File file, String encoding)
149: throws IOException {
150: this (file, encoding, false, null);
151: }
152:
153: /**
154: * Constructs a LockableFileWriter with a file encoding.
155: *
156: * @param file the file to write to, not null
157: * @param encoding the encoding to use, null means platform default
158: * @param append true if content should be appended, false to overwrite
159: * @param lockDir the directory in which the lock file should be held
160: * @throws NullPointerException if the file is null
161: * @throws IOException in case of an I/O error
162: */
163: public LockableFileWriter(File file, String encoding,
164: boolean append, String lockDir) throws IOException {
165: super ();
166: // init file to create/append
167: file = file.getAbsoluteFile();
168: if (file.getParentFile() != null) {
169: FileUtils.forceMkdir(file.getParentFile());
170: }
171: if (file.isDirectory()) {
172: throw new IOException("File specified is a directory");
173: }
174:
175: // init lock file
176: if (lockDir == null) {
177: lockDir = System.getProperty("java.io.tmpdir");
178: }
179: File lockDirFile = new File(lockDir);
180: FileUtils.forceMkdir(lockDirFile);
181: testLockDir(lockDirFile);
182: lockFile = new File(lockDirFile, file.getName() + LCK);
183:
184: // check if locked
185: createLock();
186:
187: // init wrapped writer
188: out = initWriter(file, encoding, append);
189: }
190:
191: //-----------------------------------------------------------------------
192: /**
193: * Tests that we can write to the lock directory.
194: *
195: * @param lockDir the File representing the lock directory
196: * @throws IOException if we cannot write to the lock directory
197: * @throws IOException if we cannot find the lock file
198: */
199: private void testLockDir(File lockDir) throws IOException {
200: if (!lockDir.exists()) {
201: throw new IOException("Could not find lockDir: "
202: + lockDir.getAbsolutePath());
203: }
204: if (!lockDir.canWrite()) {
205: throw new IOException("Could not write to lockDir: "
206: + lockDir.getAbsolutePath());
207: }
208: }
209:
210: /**
211: * Creates the lock file.
212: *
213: * @throws IOException if we cannot create the file
214: */
215: private void createLock() throws IOException {
216: synchronized (LockableFileWriter.class) {
217: if (!lockFile.createNewFile()) {
218: throw new IOException("Can't write file, lock "
219: + lockFile.getAbsolutePath() + " exists");
220: }
221: lockFile.deleteOnExit();
222: }
223: }
224:
225: /**
226: * Initialise the wrapped file writer.
227: * Ensure that a cleanup occurs if the writer creation fails.
228: *
229: * @param file the file to be accessed
230: * @param encoding the encoding to use
231: * @param append true to append
232: * @return The initialised writer
233: * @throws IOException if an error occurs
234: */
235: private Writer initWriter(File file, String encoding, boolean append)
236: throws IOException {
237: boolean fileExistedAlready = file.exists();
238: OutputStream stream = null;
239: Writer writer = null;
240: try {
241: if (encoding == null) {
242: writer = new FileWriter(file.getAbsolutePath(), append);
243: } else {
244: stream = new FileOutputStream(file.getAbsolutePath(),
245: append);
246: writer = new OutputStreamWriter(stream, encoding);
247: }
248: } catch (IOException ex) {
249: IOUtils.closeQuietly(writer);
250: IOUtils.closeQuietly(stream);
251: lockFile.delete();
252: if (fileExistedAlready == false) {
253: file.delete();
254: }
255: throw ex;
256: } catch (RuntimeException ex) {
257: IOUtils.closeQuietly(writer);
258: IOUtils.closeQuietly(stream);
259: lockFile.delete();
260: if (fileExistedAlready == false) {
261: file.delete();
262: }
263: throw ex;
264: }
265: return writer;
266: }
267:
268: //-----------------------------------------------------------------------
269: /**
270: * Closes the file writer.
271: *
272: * @throws IOException if an I/O error occurs
273: */
274: public void close() throws IOException {
275: try {
276: out.close();
277: } finally {
278: lockFile.delete();
279: }
280: }
281:
282: //-----------------------------------------------------------------------
283: /** @see java.io.Writer#write(int) */
284: public void write(int idx) throws IOException {
285: out.write(idx);
286: }
287:
288: /** @see java.io.Writer#write(char[]) */
289: public void write(char[] chr) throws IOException {
290: out.write(chr);
291: }
292:
293: /** @see java.io.Writer#write(char[], int, int) */
294: public void write(char[] chr, int st, int end) throws IOException {
295: out.write(chr, st, end);
296: }
297:
298: /** @see java.io.Writer#write(String) */
299: public void write(String str) throws IOException {
300: out.write(str);
301: }
302:
303: /** @see java.io.Writer#write(String, int, int) */
304: public void write(String str, int st, int end) throws IOException {
305: out.write(str, st, end);
306: }
307:
308: /** @see java.io.Writer#flush() */
309: public void flush() throws IOException {
310: out.flush();
311: }
312:
313: }
|