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 * <b>N.B.</b> the lock file is deleted when {@link #close()} is called 038 * - or if the main file cannot be opened initially. 039 * In the (unlikely) event that the lockfile cannot be deleted, 040 * this is not reported, and subsequent requests using 041 * the same lockfile will fail. 042 * <p> 043 * By default, the file will be overwritten, but this may be changed to append. 044 * The lock directory may be specified, but defaults to the system property 045 * <code>java.io.tmpdir</code>. 046 * The encoding may also be specified, and defaults to the platform default. 047 * 048 * @author <a href="mailto:sanders@apache.org">Scott Sanders</a> 049 * @author <a href="mailto:ms@collab.net">Michael Salmon</a> 050 * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a> 051 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a> 052 * @author Stephen Colebourne 053 * @author Andy Lehane 054 * @version $Id: LockableFileWriter.java 1002182 2010-09-28 14:39:19Z sebb $ 055 */ 056 public class LockableFileWriter extends Writer { 057 // Cannot extend ProxyWriter, as requires writer to be 058 // known when super() is called 059 060 /** The extension for the lock file. */ 061 private static final String LCK = ".lck"; 062 063 /** The writer to decorate. */ 064 private final Writer out; 065 /** The lock file. */ 066 private final File lockFile; 067 068 /** 069 * Constructs a LockableFileWriter. 070 * If the file exists, it is overwritten. 071 * 072 * @param fileName the file to write to, not null 073 * @throws NullPointerException if the file is null 074 * @throws IOException in case of an I/O error 075 */ 076 public LockableFileWriter(String fileName) throws IOException { 077 this(fileName, false, null); 078 } 079 080 /** 081 * Constructs a LockableFileWriter. 082 * 083 * @param fileName file to write to, not null 084 * @param append true if content should be appended, false to overwrite 085 * @throws NullPointerException if the file is null 086 * @throws IOException in case of an I/O error 087 */ 088 public LockableFileWriter(String fileName, boolean append) throws IOException { 089 this(fileName, append, null); 090 } 091 092 /** 093 * Constructs a LockableFileWriter. 094 * 095 * @param fileName the file to write to, not null 096 * @param append true if content should be appended, false to overwrite 097 * @param lockDir the directory in which the lock file should be held 098 * @throws NullPointerException if the file is null 099 * @throws IOException in case of an I/O error 100 */ 101 public LockableFileWriter(String fileName, boolean append, String lockDir) throws IOException { 102 this(new File(fileName), append, lockDir); 103 } 104 105 /** 106 * Constructs a LockableFileWriter. 107 * If the file exists, it is overwritten. 108 * 109 * @param file the file to write to, not null 110 * @throws NullPointerException if the file is null 111 * @throws IOException in case of an I/O error 112 */ 113 public LockableFileWriter(File file) throws IOException { 114 this(file, false, null); 115 } 116 117 /** 118 * Constructs a LockableFileWriter. 119 * 120 * @param file the file to write to, not null 121 * @param append true if content should be appended, false to overwrite 122 * @throws NullPointerException if the file is null 123 * @throws IOException in case of an I/O error 124 */ 125 public LockableFileWriter(File file, boolean append) throws IOException { 126 this(file, append, null); 127 } 128 129 /** 130 * Constructs a LockableFileWriter. 131 * 132 * @param file the file to write to, not null 133 * @param append true if content should be appended, false to overwrite 134 * @param lockDir the directory in which the lock file should be held 135 * @throws NullPointerException if the file is null 136 * @throws IOException in case of an I/O error 137 */ 138 public LockableFileWriter(File file, boolean append, String lockDir) throws IOException { 139 this(file, null, append, lockDir); 140 } 141 142 /** 143 * Constructs a LockableFileWriter with a file encoding. 144 * 145 * @param file the file to write to, not null 146 * @param encoding the encoding to use, null means platform default 147 * @throws NullPointerException if the file is null 148 * @throws IOException in case of an I/O error 149 */ 150 public LockableFileWriter(File file, String encoding) throws IOException { 151 this(file, encoding, false, null); 152 } 153 154 /** 155 * Constructs a LockableFileWriter with a file encoding. 156 * 157 * @param file the file to write to, not null 158 * @param encoding the encoding to use, null means platform default 159 * @param append true if content should be appended, false to overwrite 160 * @param lockDir the directory in which the lock file should be held 161 * @throws NullPointerException if the file is null 162 * @throws IOException in case of an I/O error 163 */ 164 public LockableFileWriter(File file, String encoding, boolean append, 165 String lockDir) throws IOException { 166 super(); 167 // init file to create/append 168 file = file.getAbsoluteFile(); 169 if (file.getParentFile() != null) { 170 FileUtils.forceMkdir(file.getParentFile()); 171 } 172 if (file.isDirectory()) { 173 throw new IOException("File specified is a directory"); 174 } 175 176 // init lock file 177 if (lockDir == null) { 178 lockDir = System.getProperty("java.io.tmpdir"); 179 } 180 File lockDirFile = new File(lockDir); 181 FileUtils.forceMkdir(lockDirFile); 182 testLockDir(lockDirFile); 183 lockFile = new File(lockDirFile, file.getName() + LCK); 184 185 // check if locked 186 createLock(); 187 188 // init wrapped writer 189 out = initWriter(file, encoding, append); 190 } 191 192 //----------------------------------------------------------------------- 193 /** 194 * Tests that we can write to the lock directory. 195 * 196 * @param lockDir the File representing the lock directory 197 * @throws IOException if we cannot write to the lock directory 198 * @throws IOException if we cannot find the lock file 199 */ 200 private void testLockDir(File lockDir) throws IOException { 201 if (!lockDir.exists()) { 202 throw new IOException( 203 "Could not find lockDir: " + lockDir.getAbsolutePath()); 204 } 205 if (!lockDir.canWrite()) { 206 throw new IOException( 207 "Could not write to lockDir: " + lockDir.getAbsolutePath()); 208 } 209 } 210 211 /** 212 * Creates the lock file. 213 * 214 * @throws IOException if we cannot create the file 215 */ 216 private void createLock() throws IOException { 217 synchronized (LockableFileWriter.class) { 218 if (!lockFile.createNewFile()) { 219 throw new IOException("Can't write file, lock " + 220 lockFile.getAbsolutePath() + " exists"); 221 } 222 lockFile.deleteOnExit(); 223 } 224 } 225 226 /** 227 * Initialise the wrapped file writer. 228 * Ensure that a cleanup occurs if the writer creation fails. 229 * 230 * @param file the file to be accessed 231 * @param encoding the encoding to use 232 * @param append true to append 233 * @return The initialised writer 234 * @throws IOException if an error occurs 235 */ 236 private Writer initWriter(File file, String encoding, boolean append) 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(), append); 245 writer = new OutputStreamWriter(stream, encoding); 246 } 247 } catch (IOException ex) { 248 IOUtils.closeQuietly(writer); 249 IOUtils.closeQuietly(stream); 250 FileUtils.deleteQuietly(lockFile); 251 if (fileExistedAlready == false) { 252 FileUtils.deleteQuietly(file); 253 } 254 throw ex; 255 } catch (RuntimeException ex) { 256 IOUtils.closeQuietly(writer); 257 IOUtils.closeQuietly(stream); 258 FileUtils.deleteQuietly(lockFile); 259 if (fileExistedAlready == false) { 260 FileUtils.deleteQuietly(file); 261 } 262 throw ex; 263 } 264 return writer; 265 } 266 267 //----------------------------------------------------------------------- 268 /** 269 * Closes the file writer and deletes the lockfile (if possible). 270 * 271 * @throws IOException if an I/O error occurs 272 */ 273 @Override 274 public void close() throws IOException { 275 try { 276 out.close(); 277 } finally { 278 lockFile.delete(); 279 } 280 } 281 282 //----------------------------------------------------------------------- 283 /** 284 * Write a character. 285 * @param idx the character to write 286 * @throws IOException if an I/O error occurs 287 */ 288 @Override 289 public void write(int idx) throws IOException { 290 out.write(idx); 291 } 292 293 /** 294 * Write the characters from an array. 295 * @param chr the characters to write 296 * @throws IOException if an I/O error occurs 297 */ 298 @Override 299 public void write(char[] chr) throws IOException { 300 out.write(chr); 301 } 302 303 /** 304 * Write the specified characters from an array. 305 * @param chr the characters to write 306 * @param st The start offset 307 * @param end The number of characters to write 308 * @throws IOException if an I/O error occurs 309 */ 310 @Override 311 public void write(char[] chr, int st, int end) throws IOException { 312 out.write(chr, st, end); 313 } 314 315 /** 316 * Write the characters from a string. 317 * @param str the string to write 318 * @throws IOException if an I/O error occurs 319 */ 320 @Override 321 public void write(String str) throws IOException { 322 out.write(str); 323 } 324 325 /** 326 * Write the specified characters from a string. 327 * @param str the string to write 328 * @param st The start offset 329 * @param end The number of characters to write 330 * @throws IOException if an I/O error occurs 331 */ 332 @Override 333 public void write(String str, int st, int end) throws IOException { 334 out.write(str, st, end); 335 } 336 337 /** 338 * Flush the stream. 339 * @throws IOException if an I/O error occurs 340 */ 341 @Override 342 public void flush() throws IOException { 343 out.flush(); 344 } 345 346 }