applied LGPLv3 and source code formatting.
[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         // TODO: fix occasional exceptions raised here (usually on JVM shutdown)\r
81         if (advisory.target != null) {\r
82           advisory.target.deleteOnExit(); // release will null the target\r
83         }\r
84         advisory.release(true);\r
85       }\r
86       advisory = null;\r
87       _lock = null;\r
88     }\r
89   }\r
90 \r
91   /**\r
92    * @param lockfile\r
93    * @param block\r
94    *          true means thread blocks until FileLock is obtained.\r
95    */\r
96   public FileLock(File lockfile, boolean block) {\r
97     super(lockfile);\r
98     // try and get a lock.\r
99     try {\r
100       _lock = make_lockForTarget(lockfile);\r
101       if (!ensureLockFile(block)) {\r
102         log.debug("Couldn't get lock on " + _lock);\r
103         tidy();\r
104         return;\r
105       }\r
106       // create target file ready to be written to if necessary.\r
107       if (!lockfile.exists())\r
108         if (!lockfile.createNewFile()) {\r
109           log.warn("Failed to create locked file " + lockfile);\r
110           return;\r
111         }\r
112       // openRaFile();\r
113     } catch (FileNotFoundException e) {\r
114       //\r
115       log.debug("FileLock failed with target=" + lockfile\r
116           + " and lockfile suffix of " + _LockSuffix);\r
117       // log.error("Error! Couldn't create a lockfile at "\r
118       // + lockfile.getAbsolutePath(), e);\r
119     } catch (IOException e) {\r
120       log.error("Error! Problems with IO when creating a lock on "\r
121           + lockfile.getAbsolutePath(), e);\r
122     }\r
123   }\r
124 \r
125   /**\r
126    * \r
127    * @param lockfile\r
128    *          target of lock\r
129    * @return file object that will be locked for lockfile\r
130    */\r
131   private File make_lockForTarget(File lockfile) {\r
132     return new File(lockfile.getParentFile(), lockfile.getName() + "."\r
133         + _LockSuffix);\r
134   }\r
135 \r
136   private boolean openRaFile() throws IOException {\r
137     if (target == null)\r
138       return false;\r
139     if (advisory == null || !advisory.isLocked())\r
140       return false;\r
141     if (rafile == null || rafile.getFD() == null || !rafile.getFD().valid()) {\r
142       rafile = new RandomAccessFile(target, "rw");\r
143     } else {\r
144       if (log.isDebugEnabled())\r
145         log.debug("Reusing existing RandomAccessFile on " + target);\r
146     }\r
147     return (rafile.getChannel() != null) && rafile.getChannel().isOpen();\r
148   }\r
149 \r
150   public boolean isLocked() {\r
151     if (advisory != null) {\r
152       if (advisory.isLocked())\r
153         return true;\r
154       advisory = null;\r
155       if (log.isDebugEnabled())\r
156         log.debug("Lockfile " + _lock + " unexpectedly deleted ?");\r
157     }\r
158     return false;\r
159   }\r
160 \r
161   public void release() {\r
162     release(true);\r
163   }\r
164 \r
165   public void release(boolean closeChannel) {\r
166     if (!isLocked())\r
167       return;\r
168     if (rafile != null) {\r
169       if (closeChannel) {\r
170         try {\r
171           rafile.close();\r
172         } catch (Exception e) {\r
173           log.debug("Unexpected exception whilst closing RandomAccessFile on "\r
174               + target, e);\r
175         }\r
176         rafile = null; // do not hold reference to rafile anymore either\r
177       }\r
178       if (log.isDebugEnabled())\r
179         log.debug("Releasing advisory lock on " + target);\r
180       // TODO: LATER: verify this change in closeChannel semantics really is\r
181       // correct - ArchiveClient works correctly\r
182     }\r
183     tidy();\r
184   }\r
185 \r
186   public FileInputStream getFileInputStream(boolean atStart) throws IOException {\r
187     if (!isLocked()) {\r
188       log.debug("Don't hold lock on " + target\r
189           + " to get locked FileInputStream.");\r
190       return null;\r
191     }\r
192     openRaFile();\r
193     if (atStart)\r
194       rafile.seek(0);\r
195     return new FileInputStream(rafile.getFD());\r
196   }\r
197 \r
198   public FileOutputStream getFileOutputStream(boolean clear) throws IOException {\r
199     if (!isLocked()) {\r
200       log.debug("Don't hold lock on " + target\r
201           + " to get locked FileOutputStream.");\r
202       return null;\r
203     }\r
204     openRaFile();\r
205     if (clear) {\r
206       rafile.seek(0);\r
207       rafile.setLength(0);\r
208     } else\r
209       rafile.seek(rafile.length());\r
210     return new LockedFileOutputStream(rafile.getFD());\r
211   }\r
212 \r
213   public BufferedOutputStream getBufferedOutputStream(boolean clear)\r
214       throws IOException {\r
215     log.debug("Getting BufferedOutputStream (clear=" + clear + ")");\r
216     FileOutputStream fos = getFileOutputStream(clear);\r
217     if (fos != null)\r
218       return new BufferedOutputStream(fos);\r
219     return null;\r
220   }\r
221 \r
222   /*\r
223    * (non-Javadoc)\r
224    * \r
225    * @see uk.ac.vamsas.client.simpleclient.Lock#getLength()\r
226    */\r
227   public long length() {\r
228     if (isLocked()) {\r
229       if (!target.exists()) {\r
230         try {\r
231           target.createNewFile();\r
232         } catch (Exception e) {\r
233           log.error("Invalid lock:Failed to create target file " + target);\r
234           tidy();\r
235           return -1;\r
236         }\r
237         return 0;\r
238       }\r
239       return target.length();\r
240     }\r
241     return -1;\r
242   }\r
243 \r
244   protected void finalize() throws Throwable {\r
245     release(true); // we explicitly lose the lock here.\r
246     super.finalize();\r
247   }\r
248 \r
249   public RandomAccessFile getRaFile() throws IOException {\r
250     if (isLocked() && openRaFile()) {\r
251       return rafile;\r
252     }\r
253     log.debug("Failed to getRaFile on target " + target);\r
254     return null;\r
255   }\r
256 \r
257   public FileChannel getRaChannel() throws IOException {\r
258     if (isLocked() && openRaFile()) {\r
259       return rafile.getChannel();\r
260     }\r
261     log.debug("Failed to getRaChannel on target " + target);\r
262     return null;\r
263   }\r
264 \r
265   public boolean isTargetLockFile(File afile) {\r
266     if (isLocked()) {\r
267       if (target.equals(afile) || make_lockForTarget(target).equals(afile))\r
268         return true;\r
269     }\r
270     return false;\r
271   }\r
272 }\r