supress occasional null pointer exceptions (unverified fix)
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / FileLock.java
1 /*\r
2  * This file is part of the Vamsas Client version 0.1. \r
3  * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, \r
4  *  Andrew Waterhouse and Dominik Lindner.\r
5  * \r
6  * Earlier versions have also been incorporated into Jalview version 2.4 \r
7  * since 2008, and TOPALi version 2 since 2007.\r
8  * \r
9  * The Vamsas Client is free software: you can redistribute it and/or modify\r
10  * it under the terms of the GNU Lesser General Public License as published by\r
11  * the Free Software Foundation, either version 3 of the License, or\r
12  * (at your option) any later version.\r
13  *  \r
14  * The Vamsas Client is distributed in the hope that it will be useful,\r
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
17  * GNU Lesser General Public License for more details.\r
18  * \r
19  * You should have received a copy of the GNU Lesser General Public License\r
20  * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.\r
21  */\r
22 package uk.ac.vamsas.client.simpleclient;\r
23 \r
24 import java.io.BufferedOutputStream;\r
25 import java.io.File;\r
26 import java.io.FileInputStream;\r
27 import java.io.FileNotFoundException;\r
28 import java.io.FileOutputStream;\r
29 import java.io.IOException;\r
30 import java.io.RandomAccessFile;\r
31 import java.nio.channels.FileChannel;\r
32 import java.nio.channels.ReadableByteChannel;\r
33 \r
34 /**\r
35  * File based Locking mechanism to get around some bizarre limitations of\r
36  * JarEntry seeking. Abstract locks have a target file, to which access is\r
37  * controlled when a lock is held. Native locks on WindowsXP seem to conflict\r
38  * with Jar seek operations, so a file lock creates an advisory lock. Method: A\r
39  * lock file is created, if it doesn't already exist - the naming convention is\r
40  * TargetFile+suffixSeparator+_LockSuffix. A lock is obtained by locking the\r
41  * lock file with a native lock. The NativeLock is used for this.\r
42  * \r
43  * @author JimP\r
44  * \r
45  */\r
46 public class FileLock extends Lock {\r
47   private File _lock = null;\r
48 \r
49   protected static String _LockSuffix = "lck";\r
50 \r
51   private NativeLock advisory = null;\r
52 \r
53   /**\r
54    * ensure that the _lock file exists and create a lock\r
55    */\r
56   private boolean ensureLockFile(boolean block) {\r
57     if (_lock == null)\r
58       return false;\r
59     if (advisory != null && advisory.isLocked())\r
60       return true;\r
61     try {\r
62       advisory = new NativeLock(_lock, block);\r
63     } catch (Exception e) {\r
64       if (!_lock.exists()) {\r
65         // advisory cannot be created. this is serious.\r
66         log.fatal("Failed to create advisory lock file " + _lock, e);\r
67         throw new Error("Failed to create advisory lock file " + _lock);\r
68       }\r
69     }\r
70     return (advisory != null) && advisory.isLocked();\r
71   }\r
72 \r
73   /**\r
74    * call to clear up a filelock file after its been made\r
75    * \r
76    */\r
77   private void tidy() {\r
78     if (_lock != null) {\r
79       if (advisory != null) {\r
80         File tgt = advisory.target;\r
81         // TODO: fix occasional exceptions raised here (usually on JVM shutdown)\r
82         if (tgt != null) {\r
83           try {\r
84             tgt.deleteOnExit(); // release will null the target\r
85           } catch (NullPointerException e)\r
86           {\r
87             // ignore - TODO: fix nulls \r
88           }\r
89         }\r
90         advisory.release(true);\r
91       }\r
92       advisory = null;\r
93       _lock = null;\r
94     }\r
95   }\r
96 \r
97   /**\r
98    * @param lockfile\r
99    * @param block\r
100    *          true means thread blocks until FileLock is obtained.\r
101    */\r
102   public FileLock(File lockfile, boolean block) {\r
103     super(lockfile);\r
104     // try and get a lock.\r
105     try {\r
106       _lock = make_lockForTarget(lockfile);\r
107       if (!ensureLockFile(block)) {\r
108         log.debug("Couldn't get lock on " + _lock);\r
109         tidy();\r
110         return;\r
111       }\r
112       // create target file ready to be written to if necessary.\r
113       if (!lockfile.exists())\r
114         if (!lockfile.createNewFile()) {\r
115           log.warn("Failed to create locked file " + lockfile);\r
116           return;\r
117         }\r
118       // openRaFile();\r
119     } catch (FileNotFoundException e) {\r
120       //\r
121       log.debug("FileLock failed with target=" + lockfile\r
122           + " and lockfile suffix of " + _LockSuffix);\r
123       // log.error("Error! Couldn't create a lockfile at "\r
124       // + lockfile.getAbsolutePath(), e);\r
125     } catch (IOException e) {\r
126       log.error("Error! Problems with IO when creating a lock on "\r
127           + lockfile.getAbsolutePath(), e);\r
128     }\r
129   }\r
130 \r
131   /**\r
132    * \r
133    * @param lockfile\r
134    *          target of lock\r
135    * @return file object that will be locked for lockfile\r
136    */\r
137   private File make_lockForTarget(File lockfile) {\r
138     return new File(lockfile.getParentFile(), lockfile.getName() + "."\r
139         + _LockSuffix);\r
140   }\r
141 \r
142   private boolean openRaFile() throws IOException {\r
143     if (target == null)\r
144       return false;\r
145     if (advisory == null || !advisory.isLocked())\r
146       return false;\r
147     if (rafile == null || rafile.getFD() == null || !rafile.getFD().valid()) {\r
148       rafile = new RandomAccessFile(target, "rw");\r
149     } else {\r
150       if (log.isDebugEnabled())\r
151         log.debug("Reusing existing RandomAccessFile on " + target);\r
152     }\r
153     return (rafile.getChannel() != null) && rafile.getChannel().isOpen();\r
154   }\r
155 \r
156   public boolean isLocked() {\r
157     if (advisory != null) {\r
158       if (advisory.isLocked())\r
159         return true;\r
160       advisory = null;\r
161       if (log.isDebugEnabled())\r
162         log.debug("Lockfile " + _lock + " unexpectedly deleted ?");\r
163     }\r
164     return false;\r
165   }\r
166 \r
167   public void release() {\r
168     release(true);\r
169   }\r
170 \r
171   public void release(boolean closeChannel) {\r
172     if (!isLocked())\r
173       return;\r
174     if (rafile != null) {\r
175       if (closeChannel) {\r
176         try {\r
177           rafile.close();\r
178         } catch (Exception e) {\r
179           log.debug("Unexpected exception whilst closing RandomAccessFile on "\r
180               + target, e);\r
181         }\r
182         rafile = null; // do not hold reference to rafile anymore either\r
183       }\r
184       if (log.isDebugEnabled())\r
185         log.debug("Releasing advisory lock on " + target);\r
186       // TODO: LATER: verify this change in closeChannel semantics really is\r
187       // correct - ArchiveClient works correctly\r
188     }\r
189     tidy();\r
190   }\r
191 \r
192   public FileInputStream getFileInputStream(boolean atStart) throws IOException {\r
193     if (!isLocked()) {\r
194       log.debug("Don't hold lock on " + target\r
195           + " to get locked FileInputStream.");\r
196       return null;\r
197     }\r
198     openRaFile();\r
199     if (atStart)\r
200       rafile.seek(0);\r
201     return new FileInputStream(rafile.getFD());\r
202   }\r
203 \r
204   public FileOutputStream getFileOutputStream(boolean clear) throws IOException {\r
205     if (!isLocked()) {\r
206       log.debug("Don't hold lock on " + target\r
207           + " to get locked FileOutputStream.");\r
208       return null;\r
209     }\r
210     openRaFile();\r
211     if (clear) {\r
212       rafile.seek(0);\r
213       rafile.setLength(0);\r
214     } else\r
215       rafile.seek(rafile.length());\r
216     return new LockedFileOutputStream(rafile.getFD());\r
217   }\r
218 \r
219   public BufferedOutputStream getBufferedOutputStream(boolean clear)\r
220       throws IOException {\r
221     log.debug("Getting BufferedOutputStream (clear=" + clear + ")");\r
222     FileOutputStream fos = getFileOutputStream(clear);\r
223     if (fos != null)\r
224       return new BufferedOutputStream(fos);\r
225     return null;\r
226   }\r
227 \r
228   /*\r
229    * (non-Javadoc)\r
230    * \r
231    * @see uk.ac.vamsas.client.simpleclient.Lock#getLength()\r
232    */\r
233   public long length() {\r
234     if (isLocked()) {\r
235       if (!target.exists()) {\r
236         try {\r
237           target.createNewFile();\r
238         } catch (Exception e) {\r
239           log.error("Invalid lock:Failed to create target file " + target);\r
240           tidy();\r
241           return -1;\r
242         }\r
243         return 0;\r
244       }\r
245       return target.length();\r
246     }\r
247     return -1;\r
248   }\r
249 \r
250   protected void finalize() throws Throwable {\r
251     release(true); // we explicitly lose the lock here.\r
252     super.finalize();\r
253   }\r
254 \r
255   public RandomAccessFile getRaFile() throws IOException {\r
256     if (isLocked() && openRaFile()) {\r
257       return rafile;\r
258     }\r
259     log.debug("Failed to getRaFile on target " + target);\r
260     return null;\r
261   }\r
262 \r
263   public FileChannel getRaChannel() throws IOException {\r
264     if (isLocked() && openRaFile()) {\r
265       return rafile.getChannel();\r
266     }\r
267     log.debug("Failed to getRaChannel on target " + target);\r
268     return null;\r
269   }\r
270 \r
271   public boolean isTargetLockFile(File afile) {\r
272     if (isLocked()) {\r
273       if (target.equals(afile) || make_lockForTarget(target).equals(afile))\r
274         return true;\r
275     }\r
276     return false;\r
277   }\r
278 }\r