note on occasional null pointer exceptions raised on JVM shutdown
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / FileLock.java
1 package uk.ac.vamsas.client.simpleclient;
2
3 import java.io.BufferedOutputStream;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileNotFoundException;
7 import java.io.FileOutputStream;
8 import java.io.IOException;
9 import java.io.RandomAccessFile;
10 import java.nio.channels.FileChannel;
11 import java.nio.channels.ReadableByteChannel;
12 /**
13  * File based Locking mechanism to get around some bizarre limitations of JarEntry seeking.
14  * Abstract locks have a target file, to which access is controlled when a lock is held. Native locks on WindowsXP seem to conflict with Jar seek operations, so a file lock creates an advisory lock.
15  * Method:
16  * A lock file is created, if it doesn't already exist - the naming convention is TargetFile+suffixSeparator+_LockSuffix.
17  * A lock is obtained by locking the lock file with a native lock. The NativeLock is used for this.
18  * @author JimP
19  *
20  */
21 public class FileLock extends Lock {
22   private File _lock = null;
23   protected static String _LockSuffix="lck";
24   private NativeLock advisory=null;
25   /**
26    * ensure that the _lock file exists
27    * and create a lock
28    */
29   private boolean ensureLockFile(boolean block) {
30     if (_lock==null)
31       return false;
32     if (advisory!=null && advisory.isLocked())
33       return true;
34     try { 
35       advisory=new NativeLock(_lock, block);
36     } catch (Exception e) {
37       if (!_lock.exists()) {
38         // advisory cannot be created. this is serious.
39         log.fatal("Failed to create advisory lock file "+_lock,e);
40         throw new Error("Failed to create advisory lock file "+_lock);
41       }
42     }
43     return (advisory!=null) && advisory.isLocked();
44   }
45   /**
46    * call to clear up a filelock file after its been made
47    *
48    */
49   private void tidy() {
50     if (_lock!=null) { 
51       if ( advisory!=null) {
52         // TODO: fix occasional exceptions raised here (usually on JVM shutdown)
53         if (advisory.target!=null) {
54            advisory.target.deleteOnExit(); // release will null the target
55         }
56         advisory.release(true);
57       }
58       advisory=null;
59       _lock=null;
60     }
61   }
62   /**
63    * @param lockfile
64    * @param block true means thread blocks until FileLock is obtained.
65    */
66   public FileLock(File lockfile, boolean block) {
67     super(lockfile);
68     // try and get a lock.
69     try {
70       _lock = make_lockForTarget(lockfile);
71       if (!ensureLockFile(block)) {
72         log.debug("Couldn't get lock on "+_lock);
73         tidy();
74         return;
75       }
76       // create target file ready to be written to if necessary.
77       if (!lockfile.exists())
78         if (!lockfile.createNewFile()) {
79           log.warn("Failed to create locked file "+lockfile);
80           return;
81         }
82       //openRaFile();
83     } catch (FileNotFoundException e) {
84       //
85       log.debug("FileLock failed with target="+lockfile+" and lockfile suffix of "+_LockSuffix);
86       //log.error("Error! Couldn't create a lockfile at "
87       //  + lockfile.getAbsolutePath(), e);
88     } catch (IOException e) {
89       log.error("Error! Problems with IO when creating a lock on "
90           + lockfile.getAbsolutePath(),e);
91     }
92   }
93   /**
94    * 
95    * @param lockfile target of lock
96    * @return file object that will be locked for lockfile
97    */
98   private File make_lockForTarget(File lockfile) {
99     return new File(lockfile.getParentFile(), lockfile.getName()+"."+_LockSuffix);
100   }
101   private boolean openRaFile() throws IOException {
102     if (target==null)
103       return false;
104     if (advisory==null || !advisory.isLocked())
105       return false;
106     if (rafile==null || rafile.getFD()==null || !rafile.getFD().valid()) {
107       rafile=new RandomAccessFile(target,"rw");
108     } else {
109       if (log.isDebugEnabled())
110         log.debug("Reusing existing RandomAccessFile on "+target);
111     }
112     return (rafile.getChannel()!=null) && rafile.getChannel().isOpen();
113   }
114   
115   public boolean isLocked() {
116     if (advisory != null) {
117       if (advisory.isLocked())
118         return true;
119       advisory=null;
120       if (log.isDebugEnabled())
121         log.debug("Lockfile "+_lock+" unexpectedly deleted ?");
122     }
123     return false;
124   }
125   
126   public void release() {
127     release(true);
128   }
129   
130   public void release(boolean closeChannel) {
131     if (!isLocked())
132       return;
133     if (rafile!=null) {
134       if (closeChannel) {
135         try {
136           rafile.close();
137         } catch (Exception e) {
138           log.debug("Unexpected exception whilst closing RandomAccessFile on "+target, e);
139         }
140         rafile=null; // do not hold reference to rafile anymore either
141       }
142       if (log.isDebugEnabled())
143         log.debug("Releasing advisory lock on "+target);
144       // TODO: LATER: verify this change in closeChannel semantics really is correct - ArchiveClient works correctly
145     }
146     tidy();
147   }
148   
149   public FileInputStream getFileInputStream(boolean atStart) throws IOException {
150     if (!isLocked()) {
151       log.debug("Don't hold lock on "+target+" to get locked FileInputStream.");
152       return null;
153     }
154     openRaFile();
155     if (atStart)
156       rafile.seek(0);
157     return new FileInputStream(rafile.getFD());
158   }
159   
160   
161   public FileOutputStream getFileOutputStream(boolean clear) throws IOException {
162     if (!isLocked()) {
163       log.debug("Don't hold lock on "+target+" to get locked FileOutputStream.");
164       return null;
165     }
166     openRaFile();
167     if (clear) {
168       rafile.seek(0);
169       rafile.setLength(0);
170     } else
171       rafile.seek(rafile.length());
172     return new LockedFileOutputStream(rafile.getFD());
173   }
174   
175   
176   public BufferedOutputStream getBufferedOutputStream(boolean clear) throws IOException {
177     log.debug("Getting BufferedOutputStream (clear="+clear+")");
178     FileOutputStream fos = getFileOutputStream(clear);
179     if (fos!=null)
180       return new BufferedOutputStream(fos);
181     return null;
182   }
183   
184   /* (non-Javadoc)
185    * @see uk.ac.vamsas.client.simpleclient.Lock#getLength()
186    */
187   public long length() {
188     if (isLocked()) {
189       if (!target.exists()) {
190         try {
191           target.createNewFile();
192         } catch (Exception e) {
193           log.error("Invalid lock:Failed to create target file "+target);
194           tidy();
195           return -1;
196         }
197         return 0;
198       }
199       return target.length();
200     }
201     return -1;
202   }
203   protected void finalize() throws Throwable {
204     release(true); // we explicitly lose the lock here.
205     super.finalize();
206   }
207   public RandomAccessFile getRaFile() throws IOException {
208     if (isLocked() && openRaFile()) {
209       return rafile;
210     }
211     log.debug("Failed to getRaFile on target "+target);
212     return null;
213   }
214   public FileChannel getRaChannel() throws IOException {
215     if (isLocked() && openRaFile()) {
216       return rafile.getChannel();
217     }
218     log.debug("Failed to getRaChannel on target "+target);
219     return null;
220   }
221   public boolean isTargetLockFile(File afile) {
222     if (isLocked())
223     {
224       if (target.equals(afile) || make_lockForTarget(target).equals(afile))
225         return true;
226     }
227     return false;
228   }
229 }