updated test cases for spring 2006 schema, and major debug of locking system
[vamsas.git] / src / org / vamsas / client / simpleclient / FileLock.java
index 8cb6808..53688ac 100644 (file)
@@ -7,41 +7,71 @@ import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+/**
+ * File based Locking mechanism to get around some bizarre limitations of JarEntry seeking.
+ * 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.
+ * Method:
+ * A lock file is created, if it doesn't already exist - the naming convention is TargetFile+suffixSeparator+_LockSuffix.
+ * A lock is obtained by locking the lock file with a native lock. The NativeLock is used for this.
+ * @author JimP
+ *
+ */
 public class FileLock extends Lock {
-  File _lock = null;
+  private File _lock = null;
   protected static String _LockSuffix="lck";
+  private NativeLock advisory=null;
+  /**
+   * ensure that the _lock file exists
+   * and create a lock
+   */
+  private boolean ensureLockFile(boolean block) {
+    if (_lock==null)
+      return false;
+    if (advisory!=null && advisory.isLocked())
+      return true;
+    try { 
+      advisory=new NativeLock(_lock, block);
+    } catch (Exception e) {
+      log.fatal("Failed to create advisory lock file "+_lock,e);
+      throw new Error("Failed to create advisory lock file "+_lock);          
+    }
+    return advisory.isLocked();
+  }
   /**
    * call to clear up a filelock file after its been made
    *
    */
   private void tidy() {
-    if (_lock!=null) {
-      _lock.delete();
+    if (_lock!=null) { 
+      if ( advisory!=null) 
+        advisory.release(true);
+      advisory=null;
       _lock=null;
     }
   }
   /**
    * @param lockfile
+   * @param block true means thread blocks until FileLock is obtained.
    */
-  public FileLock(File lockfile) {
+  public FileLock(File lockfile, boolean block) {
     super(lockfile);
     // try and get a lock.
     try {
       _lock = new File(lockfile.getParentFile(), lockfile.getName()+"."+_LockSuffix);
-      if (_lock.exists() || !_lock.createNewFile()) {
-        log.debug("Failed to get lock for "+lockfile+" using lockfile "+_lock);
-        _lock=null;
+      if (!ensureLockFile(block)) {
+        log.debug("Couldn't get lock on "+_lock);
+        tidy();
         return;
       }
-      _lock.deleteOnExit(); // safe - all locks should be removed on finalization.
       // create target file ready to be written to if necessary.
       if (!lockfile.exists())
         if (!lockfile.createNewFile()) {
           log.warn("Failed to create locked file "+lockfile);
           return;
         }
-      openRaFile();
+      //openRaFile();
     } catch (FileNotFoundException e) {
       //
       log.debug("FileLock failed with target="+lockfile+" and lockfile suffix of "+_LockSuffix);
@@ -56,18 +86,22 @@ public class FileLock extends Lock {
   private boolean openRaFile() throws IOException {
     if (target==null)
       return false;
-    if (_lock==null || !_lock.exists())
+    if (advisory==null || !advisory.isLocked())
       return false;
-    if (rafile==null)
+    if (rafile==null || rafile.getFD()==null || !rafile.getFD().valid()) {
       rafile=new RandomAccessFile(target,"rw");
+    } else {
+      if (log.isDebugEnabled())
+        log.debug("Reusing existing RandomAccessFile on "+target);
+    }
     return (rafile.getChannel()!=null) && rafile.getChannel().isOpen();
   }
   
   public boolean isLocked() {
-    if (_lock != null) {
-      if (_lock.exists())
+    if (advisory != null) {
+      if (advisory.isLocked())
         return true;
-      _lock=null;
+      advisory=null;
       if (log.isDebugEnabled())
         log.debug("Lockfile "+_lock+" unexpectedly deleted ?");
     }
@@ -79,8 +113,10 @@ public class FileLock extends Lock {
   }
   
   public void release(boolean closeChannel) {
-    if (_lock==null)
+    if (!isLocked())
       return;
+    if (log.isDebugEnabled())
+      log.debug("Releasing advisory lock on "+target);
     if (closeChannel) {
       if (rafile!=null) 
         try {
@@ -94,8 +130,10 @@ public class FileLock extends Lock {
   }
   
   public FileInputStream getFileInputStream(boolean atStart) throws IOException {
-    if (!isLocked())
+    if (!isLocked()) {
+      log.debug("Don't hold lock on "+target+" to get locked FileInputStream.");
       return null;
+    }
     openRaFile();
     if (atStart)
       rafile.seek(0);
@@ -104,27 +142,63 @@ public class FileLock extends Lock {
   
   
   public FileOutputStream getFileOutputStream(boolean clear) throws IOException {
-    if (!isLocked())
+    if (!isLocked()) {
+      log.debug("Don't hold lock on "+target+" to get locked FileOutputStream.");
       return null;
+    }
     openRaFile();
-    if (clear)
+    if (clear) {
+      rafile.seek(0);
       rafile.setLength(0);
-    else
+    } else
       rafile.seek(rafile.length());
     return new LockedFileOutputStream(rafile.getFD());
   }
   
   
   public BufferedOutputStream getBufferedOutputStream(boolean clear) throws IOException {
+    log.debug("Getting BufferedOutputStream (clear="+clear+")");
     FileOutputStream fos = getFileOutputStream(clear);
     if (fos!=null)
       return new BufferedOutputStream(fos);
     return null;
   }
   
+  /* (non-Javadoc)
+   * @see org.vamsas.client.simpleclient.Lock#getLength()
+   */
+  public long length() {
+    if (isLocked()) {
+      if (!target.exists()) {
+        try {
+          target.createNewFile();
+        } catch (Exception e) {
+          log.error("Invalid lock:Failed to create target file "+target);
+          tidy();
+          return -1;
+        }
+        return 0;
+      }
+      return target.length();
+    }
+    return -1;
+  }
   protected void finalize() throws Throwable {
     release(true); // we explicitly lose the lock here.
     super.finalize();
   }
-  
+  public RandomAccessFile getRaFile() throws IOException {
+    if (isLocked() && openRaFile()) {
+      return rafile;
+    }
+    log.debug("Failed to getRaFile on target "+target);
+    return null;
+  }
+  public FileChannel getRaChannel() throws IOException {
+    if (isLocked() && openRaFile()) {
+      return rafile.getChannel();
+    }
+    log.debug("Failed to getRaChannel on target "+target);
+    return null;
+  }
 }