new method to test if a particular file is or is related to the target of a lock
[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         advisory.target.deleteOnExit(); // release will null the target
53         advisory.release(true);
54       }
55       advisory=null;
56       _lock=null;
57     }
58   }
59   /**
60    * @param lockfile
61    * @param block true means thread blocks until FileLock is obtained.
62    */
63   public FileLock(File lockfile, boolean block) {
64     super(lockfile);
65     // try and get a lock.
66     try {
67       _lock = make_lockForTarget(lockfile);
68       if (!ensureLockFile(block)) {
69         log.debug("Couldn't get lock on "+_lock);
70         tidy();
71         return;
72       }
73       // create target file ready to be written to if necessary.
74       if (!lockfile.exists())
75         if (!lockfile.createNewFile()) {
76           log.warn("Failed to create locked file "+lockfile);
77           return;
78         }
79       //openRaFile();
80     } catch (FileNotFoundException e) {
81       //
82       log.debug("FileLock failed with target="+lockfile+" and lockfile suffix of "+_LockSuffix);
83       //log.error("Error! Couldn't create a lockfile at "
84       //  + lockfile.getAbsolutePath(), e);
85     } catch (IOException e) {
86       log.error("Error! Problems with IO when creating a lock on "
87           + lockfile.getAbsolutePath(),e);
88     }
89   }
90   /**
91    * 
92    * @param lockfile target of lock
93    * @return file object that will be locked for lockfile
94    */
95   private File make_lockForTarget(File lockfile) {
96     return new File(lockfile.getParentFile(), lockfile.getName()+"."+_LockSuffix);
97   }
98   private boolean openRaFile() throws IOException {
99     if (target==null)
100       return false;
101     if (advisory==null || !advisory.isLocked())
102       return false;
103     if (rafile==null || rafile.getFD()==null || !rafile.getFD().valid()) {
104       rafile=new RandomAccessFile(target,"rw");
105     } else {
106       if (log.isDebugEnabled())
107         log.debug("Reusing existing RandomAccessFile on "+target);
108     }
109     return (rafile.getChannel()!=null) && rafile.getChannel().isOpen();
110   }
111   
112   public boolean isLocked() {
113     if (advisory != null) {
114       if (advisory.isLocked())
115         return true;
116       advisory=null;
117       if (log.isDebugEnabled())
118         log.debug("Lockfile "+_lock+" unexpectedly deleted ?");
119     }
120     return false;
121   }
122   
123   public void release() {
124     release(true);
125   }
126   
127   public void release(boolean closeChannel) {
128     if (!isLocked())
129       return;
130     if (rafile!=null) {
131       if (closeChannel) {
132         try {
133           rafile.close();
134         } catch (Exception e) {
135           log.debug("Unexpected exception whilst closing RandomAccessFile on "+target, e);
136         }
137         rafile=null; // do not hold reference to rafile anymore either
138       }
139       if (log.isDebugEnabled())
140         log.debug("Releasing advisory lock on "+target);
141       // TODO: LATER: verify this change in closeChannel semantics really is correct - ArchiveClient works correctly
142     }
143     tidy();
144   }
145   
146   public FileInputStream getFileInputStream(boolean atStart) throws IOException {
147     if (!isLocked()) {
148       log.debug("Don't hold lock on "+target+" to get locked FileInputStream.");
149       return null;
150     }
151     openRaFile();
152     if (atStart)
153       rafile.seek(0);
154     return new FileInputStream(rafile.getFD());
155   }
156   
157   
158   public FileOutputStream getFileOutputStream(boolean clear) throws IOException {
159     if (!isLocked()) {
160       log.debug("Don't hold lock on "+target+" to get locked FileOutputStream.");
161       return null;
162     }
163     openRaFile();
164     if (clear) {
165       rafile.seek(0);
166       rafile.setLength(0);
167     } else
168       rafile.seek(rafile.length());
169     return new LockedFileOutputStream(rafile.getFD());
170   }
171   
172   
173   public BufferedOutputStream getBufferedOutputStream(boolean clear) throws IOException {
174     log.debug("Getting BufferedOutputStream (clear="+clear+")");
175     FileOutputStream fos = getFileOutputStream(clear);
176     if (fos!=null)
177       return new BufferedOutputStream(fos);
178     return null;
179   }
180   
181   /* (non-Javadoc)
182    * @see uk.ac.vamsas.client.simpleclient.Lock#getLength()
183    */
184   public long length() {
185     if (isLocked()) {
186       if (!target.exists()) {
187         try {
188           target.createNewFile();
189         } catch (Exception e) {
190           log.error("Invalid lock:Failed to create target file "+target);
191           tidy();
192           return -1;
193         }
194         return 0;
195       }
196       return target.length();
197     }
198     return -1;
199   }
200   protected void finalize() throws Throwable {
201     release(true); // we explicitly lose the lock here.
202     super.finalize();
203   }
204   public RandomAccessFile getRaFile() throws IOException {
205     if (isLocked() && openRaFile()) {
206       return rafile;
207     }
208     log.debug("Failed to getRaFile on target "+target);
209     return null;
210   }
211   public FileChannel getRaChannel() throws IOException {
212     if (isLocked() && openRaFile()) {
213       return rafile.getChannel();
214     }
215     log.debug("Failed to getRaChannel on target "+target);
216     return null;
217   }
218   public boolean isTargetLockFile(File afile) {
219     if (isLocked())
220     {
221       if (target.equals(afile) || make_lockForTarget(target).equals(afile))
222         return true;
223     }
224     return false;
225   }
226 }