From d4199f5cfea3a90baeb956c4beaa2f3e8c0597c6 Mon Sep 17 00:00:00 2001 From: jprocter Date: Wed, 24 May 2006 13:32:33 +0000 Subject: [PATCH] introducing Lock File based locking (for portability) - part implemented. git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@230 be28352e-c001-0410-b1a7-c7978e42abec --- .../vamsas/client/simpleclient/ClientDocument.java | 2 +- .../vamsas/client/simpleclient/ClientsFile.java | 3 + src/org/vamsas/client/simpleclient/FileLock.java | 130 ++++++++++++++++++++ .../vamsas/client/simpleclient/FileWatcher.java | 2 +- src/org/vamsas/client/simpleclient/Lock.java | 124 +++++-------------- .../vamsas/client/simpleclient/LockFactory.java | 34 +++++ src/org/vamsas/client/simpleclient/NativeLock.java | 129 +++++++++++++++++++ .../vamsas/client/simpleclient/SessionFile.java | 15 ++- .../vamsas/client/simpleclient/SimpleClient.java | 5 +- .../vamsas/test/simpleclient/ArchiveClient.java | 4 +- .../vamsas/test/simpleclient/ArchiveWriter.java | 21 ++-- src/org/vamsas/test/simpleclient/ClientDoc.java | 7 +- .../vamsas/test/simpleclient/VamsasArchive.java | 2 +- 13 files changed, 360 insertions(+), 118 deletions(-) create mode 100644 src/org/vamsas/client/simpleclient/FileLock.java create mode 100644 src/org/vamsas/client/simpleclient/LockFactory.java create mode 100644 src/org/vamsas/client/simpleclient/NativeLock.java diff --git a/src/org/vamsas/client/simpleclient/ClientDocument.java b/src/org/vamsas/client/simpleclient/ClientDocument.java index d360085..631c068 100644 --- a/src/org/vamsas/client/simpleclient/ClientDocument.java +++ b/src/org/vamsas/client/simpleclient/ClientDocument.java @@ -424,7 +424,7 @@ public class ClientDocument extends org.vamsas.client.ClientDocument implements // first write for this application - add a new section in document ApplicationData appd = scappd.appsGlobal = new ApplicationData(); appd.setName(client.getClientName()); - appd.setUrn(client.getClientUrn()); + // appd.setUrn(client.getClientUrn()); appd.setVersion(client.getVersion()); doc.addApplicationData(appd); // embed or jarEntry ? - for now only jarEntry's are dealt with. diff --git a/src/org/vamsas/client/simpleclient/ClientsFile.java b/src/org/vamsas/client/simpleclient/ClientsFile.java index d8abd2b..cab03cd 100644 --- a/src/org/vamsas/client/simpleclient/ClientsFile.java +++ b/src/org/vamsas/client/simpleclient/ClientsFile.java @@ -179,6 +179,9 @@ public class ClientsFile extends ListFile { */ protected int addClient(ClientHandle me, boolean disambiguate) { int newclient = 0; + int tries=5; + while (tries-->0 && !lockFile()) + try { Thread.sleep(1); } catch (Exception e){}; if (lockFile()) { ClientHandle[] clients = retrieveClientHandles(); if (me.getClientUrn()==null) { diff --git a/src/org/vamsas/client/simpleclient/FileLock.java b/src/org/vamsas/client/simpleclient/FileLock.java new file mode 100644 index 0000000..8cb6808 --- /dev/null +++ b/src/org/vamsas/client/simpleclient/FileLock.java @@ -0,0 +1,130 @@ +package org.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; + +public class FileLock extends Lock { + File _lock = null; + protected static String _LockSuffix="lck"; + /** + * call to clear up a filelock file after its been made + * + */ + private void tidy() { + if (_lock!=null) { + _lock.delete(); + _lock=null; + } + } + /** + * @param lockfile + */ + public FileLock(File lockfile) { + 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; + 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(); + } 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); + } + } + + private boolean openRaFile() throws IOException { + if (target==null) + return false; + if (_lock==null || !_lock.exists()) + return false; + if (rafile==null) + rafile=new RandomAccessFile(target,"rw"); + return (rafile.getChannel()!=null) && rafile.getChannel().isOpen(); + } + + public boolean isLocked() { + if (_lock != null) { + if (_lock.exists()) + return true; + _lock=null; + if (log.isDebugEnabled()) + log.debug("Lockfile "+_lock+" unexpectedly deleted ?"); + } + return false; + } + + public void release() { + release(true); + } + + public void release(boolean closeChannel) { + if (_lock==null) + return; + if (closeChannel) { + if (rafile!=null) + try { + rafile.close(); + } catch (Exception e) { + log.debug("Unexpected exception whilst closing RandomAccessFile on "+target, e); + } + rafile=null; + } + tidy(); + } + + public FileInputStream getFileInputStream(boolean atStart) throws IOException { + if (!isLocked()) + return null; + openRaFile(); + if (atStart) + rafile.seek(0); + return new FileInputStream(rafile.getFD()); + } + + + public FileOutputStream getFileOutputStream(boolean clear) throws IOException { + if (!isLocked()) + return null; + openRaFile(); + if (clear) + rafile.setLength(0); + else + rafile.seek(rafile.length()); + return new LockedFileOutputStream(rafile.getFD()); + } + + + public BufferedOutputStream getBufferedOutputStream(boolean clear) throws IOException { + FileOutputStream fos = getFileOutputStream(clear); + if (fos!=null) + return new BufferedOutputStream(fos); + return null; + } + + protected void finalize() throws Throwable { + release(true); // we explicitly lose the lock here. + super.finalize(); + } + +} diff --git a/src/org/vamsas/client/simpleclient/FileWatcher.java b/src/org/vamsas/client/simpleclient/FileWatcher.java index 7854b48..29d5979 100644 --- a/src/org/vamsas/client/simpleclient/FileWatcher.java +++ b/src/org/vamsas/client/simpleclient/FileWatcher.java @@ -42,7 +42,7 @@ public class FileWatcher { if (subjectLock!=null) { subjectLock.release(); } - subjectLock = new Lock(subject); + subjectLock = LockFactory.getLock(subject); if (subjectLock.isLocked()) { return false; } diff --git a/src/org/vamsas/client/simpleclient/Lock.java b/src/org/vamsas/client/simpleclient/Lock.java index 903214d..9dd472f 100644 --- a/src/org/vamsas/client/simpleclient/Lock.java +++ b/src/org/vamsas/client/simpleclient/Lock.java @@ -2,101 +2,57 @@ package org.vamsas.client.simpleclient; import java.io.BufferedInputStream; 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.FileLock; import org.apache.commons.logging.LogFactory; /** * transient object representing a file lock * This lock should hold for all processes interacting in a session. - * TODO: currently implemented for local filesystem style locking - need a fallback mechanism for systems without file locks. * @author jimp - * */ -public class Lock { - org.apache.commons.logging.Log log = LogFactory.getLog(Lock.class); - FileLock lock = null; - RandomAccessFile rafile=null; +public abstract class Lock { + protected org.apache.commons.logging.Log log = LogFactory.getLog(Lock.class); + File target = null; // The file that is being locked + protected RandomAccessFile rafile=null; + /** * creates a valid Lock (test with isLocked) * if a lock could be obtained for lockfile * @param lockfile */ - public Lock(java.io.File lockfile) { - // try and get a lock. - lock = null; - - try { - if (!lockfile.exists()) - if (!lockfile.createNewFile()) { - log.warn("Failed to create locked file "+lockfile); - return; - } - - lock = (rafile=new RandomAccessFile(lockfile,"rw")).getChannel().tryLock(); - if (lock==null || !lock.isValid()) { - // failed to get lock. Close the file channel - log.debug("failed to get lock for "+lockfile); - rafile.getChannel().close(); - lock=null; - } - } catch (FileNotFoundException e) { - // - log.debug("Lock failed - normal behaviour for windows locking."); - //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); - } - } - - boolean isLocked() { - if (lock != null && lock.isValid()) { - return true; - } - return false; + protected Lock(java.io.File lockfile) { + target = lockfile; } - public void release() { - release(true); - } - public void release(boolean closeChannel) { - try { - // channel.close should be called before release() for rigourous locking. - if (rafile!=null && rafile.getChannel()!=null) { - if (rafile.getChannel().isOpen()) { - if (closeChannel && rafile.getChannel().isOpen()) - rafile.getChannel().close(); - if (lock!=null && lock.isValid()) - lock.release(); - } - } - } catch (IOException e) { - log.warn("Whilst releasing lock",e); - } - lock=null; - rafile=null; - } - + /** + * + * @return true if lock is held on the target + */ + public abstract boolean isLocked(); + /** + * release lock and close all managed channels to file + * + */ + public abstract void release(); + /** + * optionally close the open random access channel on the file when releasing lock + * @param closeChannel + */ + public abstract void release(boolean closeChannel); + /** * gets Locked Stream for reading from * @param atStart true to start reading at beginning of file. * @return null if file not locked * @throws IOException */ - public FileInputStream getFileInputStream(boolean atStart) throws IOException { - if (!isLocked()) - return null; - if (atStart) - rafile.seek(0); - return new FileInputStream(rafile.getFD()); - } + public abstract FileInputStream getFileInputStream(boolean atStart) throws IOException; + /** * gets Locked stream to write to * FileInput always starts at the *end* of the file (after any truncation) @@ -104,26 +60,17 @@ public class Lock { * @return null if file is not locked * @throws IOException */ - public FileOutputStream getFileOutputStream(boolean clear) throws IOException { - if (!isLocked()) - return null; - if (clear) - rafile.setLength(0); - rafile.seek(rafile.length()); - return new LockedFileOutputStream(rafile.getFD()); - } + public abstract FileOutputStream getFileOutputStream(boolean clear) throws IOException; /** * return buffered output stream to locked file. * @param clear - true means file is truncated to 0 length before writing * @return */ - public BufferedOutputStream getBufferedOutputStream(boolean clear) throws IOException { - FileOutputStream fos = getFileOutputStream(clear); - if (fos!=null) - return new BufferedOutputStream(fos); - return null; + public abstract BufferedOutputStream getBufferedOutputStream(boolean clear) throws IOException; + + protected void finalize() throws Throwable { + target=null; } - /** * return buffered input stream for locked file. * @param atStart - true means read from begining of file @@ -135,13 +82,4 @@ public class Lock { return new BufferedInputStream(fis); return null; } - /* Explicitly release lock (probably don't need to do this!) - * @see java.lang.Object#finalize() - */ - protected void finalize() throws Throwable { - release(true); // we explicitly lose the lock here. - // log.debug("lock closing through garbage collection ?"); - super.finalize(); - } - } diff --git a/src/org/vamsas/client/simpleclient/LockFactory.java b/src/org/vamsas/client/simpleclient/LockFactory.java new file mode 100644 index 0000000..72cbe66 --- /dev/null +++ b/src/org/vamsas/client/simpleclient/LockFactory.java @@ -0,0 +1,34 @@ +package org.vamsas.client.simpleclient; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class LockFactory { + protected static Log log = LogFactory.getLog(LockFactory.class); + public static int locktype=0; // use file lock by default + public static String[] locktypes = {"file","native"}; + { + String lockt = System.getProperty("vamsas.locktype"); + if (lockt!=null) { + int i,j; + for (i=0, j=locktypes.length; i=j) { + String lt = "'"+locktypes[0]+"'"; + for (i=1; i0 && !fileLock.isLocked()); if (!fileLock.isLocked()) log.error("Failed to get lock for "+sessionFile); @@ -134,8 +135,10 @@ public class SessionFile { if (newData.sessionFile==null) throw new IOException("Null SessionFile in newData."); - lockFile(extantLock); - newData.lockFile(); + if (!lockFile(extantLock)) + log.error("Failed to get write lock for "+sessionFile); + if (!newData.lockFile()) + log.warn("Failed to get lock for updateFrom "+newData.sessionFile); fileLock.rafile.getChannel().transferFrom(newData.fileLock.rafile.getChannel(), 0, newData.fileLock.rafile.length()); } diff --git a/src/org/vamsas/client/simpleclient/SimpleClient.java b/src/org/vamsas/client/simpleclient/SimpleClient.java index e824549..e21c359 100644 --- a/src/org/vamsas/client/simpleclient/SimpleClient.java +++ b/src/org/vamsas/client/simpleclient/SimpleClient.java @@ -152,7 +152,7 @@ public class SimpleClient implements IClient { * @return user field for a provenance entry */ protected String getProvenanceUser() { - return new String(user.getFullName()+" ["+client.getClientUrn()+"]"); + return new String(user.getFullName()); } /** * construct a provenance entry for this client with the specified action string. @@ -160,8 +160,7 @@ public class SimpleClient implements IClient { * @return properly completed provenance entry */ protected Entry getProvenanceEntry(String action) { - // VAMSAS: modify schema to allow referencing of user field (plus other issues, ClientUrn field, machine readable action, input parameters, additional data generated notes - Entry prov = ProvenanceStuff.newProvenanceEntry(getProvenanceUser(), action); + Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action); return prov; } private Hashtable handlers = initHandlers(); diff --git a/src/org/vamsas/test/simpleclient/ArchiveClient.java b/src/org/vamsas/test/simpleclient/ArchiveClient.java index 0ba0a93..d72b43e 100644 --- a/src/org/vamsas/test/simpleclient/ArchiveClient.java +++ b/src/org/vamsas/test/simpleclient/ArchiveClient.java @@ -153,7 +153,7 @@ public class ArchiveClient extends IdFactory { d = docb.getVamsasDocument(vreader); if (d!=null) { - ClientDoc creader = new ClientDoc(d, null, vreader, getProvenanceUser(), getVorbaIdHash()); + ClientDoc creader = new ClientDoc(d, null, vreader, getClientHandle().getClientUrn(), getProvenanceUser(), getVorbaIdHash()); return creader; } } @@ -180,7 +180,7 @@ public class ArchiveClient extends IdFactory { varc.cancelArchive(); return null; } - ClientDoc cdoc = new ClientDoc(d, varc, varc.getOriginalArchiveReader(), getProvenanceUser(), getVorbaIdHash()); + ClientDoc cdoc = new ClientDoc(d, varc, varc.getOriginalArchiveReader(), getClientHandle().getClientUrn(), getProvenanceUser(), getVorbaIdHash()); return cdoc; // do appHandle? } catch (Exception e) { diff --git a/src/org/vamsas/test/simpleclient/ArchiveWriter.java b/src/org/vamsas/test/simpleclient/ArchiveWriter.java index 1a9bae9..d6f8ee9 100644 --- a/src/org/vamsas/test/simpleclient/ArchiveWriter.java +++ b/src/org/vamsas/test/simpleclient/ArchiveWriter.java @@ -13,8 +13,10 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.vamsas.client.simpleclient.VamsasArchive; import org.vamsas.client.simpleclient.VamsasArchiveReader; +import org.vamsas.objects.core.Alignment; import org.vamsas.objects.core.ApplicationData; import org.vamsas.objects.core.Entry; +import org.vamsas.objects.core.Instance; import org.vamsas.objects.core.Provenance; import org.vamsas.objects.core.VAMSAS; import org.vamsas.objects.core.VamsasDocument; @@ -38,8 +40,9 @@ public class ArchiveWriter { } // Merge appDataReferences require transfer of jar entries, perhaps with a renaming of the entry. // Merge appDatas require eventually unique URNS + // TODO: merging global appdata from different documents where same app has written them causes conflict - public static Hashtable hashOfAppDatas(Hashtable ht, ApplicationData[] appdatas) { + public static Hashtable hashOfAppDatas(Hashtable ht, Instance[] appdatas) { if (ht==null) ht = new Hashtable(); for (int i=0, j=appdatas.length; i