package uk.ac.vamsas.client.simpleclient; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; 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 { 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) { if (!_lock.exists()) { // advisory cannot be created. this is serious. log.fatal("Failed to create advisory lock file "+_lock,e); throw new Error("Failed to create advisory lock file "+_lock); } } return (advisory!=null) && advisory.isLocked(); } /** * call to clear up a filelock file after its been made * */ private void tidy() { if (_lock!=null) { if ( advisory!=null) { // TODO: fix occasional exceptions raised here (usually on JVM shutdown) if (advisory.target!=null) { advisory.target.deleteOnExit(); // release will null the target } advisory.release(true); } advisory=null; _lock=null; } } /** * @param lockfile * @param block true means thread blocks until FileLock is obtained. */ public FileLock(File lockfile, boolean block) { super(lockfile); // try and get a lock. try { _lock = make_lockForTarget(lockfile); if (!ensureLockFile(block)) { log.debug("Couldn't get lock on "+_lock); tidy(); return; } // 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(); } catch (FileNotFoundException e) { // log.debug("FileLock failed with target="+lockfile+" and lockfile suffix of "+_LockSuffix); //log.error("Error! Couldn't create a lockfile at " // + lockfile.getAbsolutePath(), e); } catch (IOException e) { log.error("Error! Problems with IO when creating a lock on " + lockfile.getAbsolutePath(),e); } } /** * * @param lockfile target of lock * @return file object that will be locked for lockfile */ private File make_lockForTarget(File lockfile) { return new File(lockfile.getParentFile(), lockfile.getName()+"."+_LockSuffix); } private boolean openRaFile() throws IOException { if (target==null) return false; if (advisory==null || !advisory.isLocked()) return false; 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 (advisory != null) { if (advisory.isLocked()) return true; advisory=null; if (log.isDebugEnabled()) log.debug("Lockfile "+_lock+" unexpectedly deleted ?"); } return false; } public void release() { release(true); } public void release(boolean closeChannel) { if (!isLocked()) return; if (rafile!=null) { if (closeChannel) { try { rafile.close(); } catch (Exception e) { log.debug("Unexpected exception whilst closing RandomAccessFile on "+target, e); } rafile=null; // do not hold reference to rafile anymore either } if (log.isDebugEnabled()) log.debug("Releasing advisory lock on "+target); // TODO: LATER: verify this change in closeChannel semantics really is correct - ArchiveClient works correctly } tidy(); } public FileInputStream getFileInputStream(boolean atStart) throws IOException { if (!isLocked()) { log.debug("Don't hold lock on "+target+" to get locked FileInputStream."); return null; } openRaFile(); if (atStart) rafile.seek(0); return new FileInputStream(rafile.getFD()); } public FileOutputStream getFileOutputStream(boolean clear) throws IOException { if (!isLocked()) { log.debug("Don't hold lock on "+target+" to get locked FileOutputStream."); return null; } openRaFile(); if (clear) { rafile.seek(0); rafile.setLength(0); } 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 uk.ac.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; } public boolean isTargetLockFile(File afile) { if (isLocked()) { if (target.equals(afile) || make_lockForTarget(target).equals(afile)) return true; } return false; } }