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