From 5156a824b13ecd091af9f84870f41d83eb59bd71 Mon Sep 17 00:00:00 2001 From: jprocter Date: Thu, 14 Dec 2006 17:52:15 +0000 Subject: [PATCH] refactored org to uk git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@269 be28352e-c001-0410-b1a7-c7978e42abec --- .../client/simpleclient/AppDataInputStream.java | 53 ++ .../client/simpleclient/AppDataOutputStream.java | 86 +++ .../ac/vamsas/client/simpleclient/ArchiveUrn.java | 34 + .../vamsas/client/simpleclient/ClientDocument.java | 510 ++++++++++++ .../ac/vamsas/client/simpleclient/ClientsFile.java | 295 +++++++ .../client/simpleclient/EventGeneratorThread.java | 277 +++++++ src/uk/ac/vamsas/client/simpleclient/FileLock.java | 205 +++++ .../ac/vamsas/client/simpleclient/FileWatcher.java | 168 ++++ .../ac/vamsas/client/simpleclient/IdFactory.java | 159 ++++ src/uk/ac/vamsas/client/simpleclient/ListFile.java | 25 + src/uk/ac/vamsas/client/simpleclient/Lock.java | 96 +++ .../ac/vamsas/client/simpleclient/LockFactory.java | 52 ++ .../client/simpleclient/LockTimeoutException.java | 29 + .../simpleclient/LockedFileOutputStream.java | 159 ++++ .../ac/vamsas/client/simpleclient/NativeLock.java | 173 +++++ .../ac/vamsas/client/simpleclient/SessionFile.java | 215 ++++++ .../client/simpleclient/SessionFlagFile.java | 85 ++ .../ac/vamsas/client/simpleclient/SessionUrn.java | 40 + .../vamsas/client/simpleclient/SessionsFile.java | 342 +++++++++ .../vamsas/client/simpleclient/SimpleClient.java | 416 ++++++++++ .../client/simpleclient/SimpleClientAppdata.java | 543 +++++++++++++ .../client/simpleclient/SimpleClientFactory.java | 262 +++++++ .../client/simpleclient/SimpleDocBinding.java | 138 ++++ .../vamsas/client/simpleclient/SimpleDocument.java | 28 + .../vamsas/client/simpleclient/VamsasArchive.java | 809 ++++++++++++++++++++ .../client/simpleclient/VamsasArchiveReader.java | 313 ++++++++ .../ac/vamsas/client/simpleclient/VamsasFile.java | 100 +++ .../vamsas/client/simpleclient/VamsasSession.java | 312 ++++++++ 28 files changed, 5924 insertions(+) create mode 100644 src/uk/ac/vamsas/client/simpleclient/AppDataInputStream.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/AppDataOutputStream.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/ArchiveUrn.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/ClientDocument.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/ClientsFile.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/EventGeneratorThread.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/FileLock.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/FileWatcher.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/IdFactory.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/ListFile.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/Lock.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/LockFactory.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/LockTimeoutException.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/LockedFileOutputStream.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/NativeLock.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SessionFile.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SessionFlagFile.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SessionUrn.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SessionsFile.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SimpleClient.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SimpleDocBinding.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/SimpleDocument.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/VamsasArchive.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/VamsasArchiveReader.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/VamsasFile.java create mode 100644 src/uk/ac/vamsas/client/simpleclient/VamsasSession.java diff --git a/src/uk/ac/vamsas/client/simpleclient/AppDataInputStream.java b/src/uk/ac/vamsas/client/simpleclient/AppDataInputStream.java new file mode 100644 index 0000000..c16d0cf --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/AppDataInputStream.java @@ -0,0 +1,53 @@ +/** + * + */ +package uk.ac.vamsas.client.simpleclient; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarInputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author jimp + * LATER: this may not be a necessary or useful class to return from IClientAppdata get*InputStream() methods + */ +public class AppDataInputStream extends DataInputStream implements DataInput { + private Log log = LogFactory.getLog(AppDataInputStream.class); + private boolean isOpen = false; + /** + * Wrapper for writing to/from AppData Entries in a Vamsas Document. + */ + public AppDataInputStream(InputStream inputstream) { + super(inputstream); + isOpen=true; + } + + /* (non-Javadoc) + * @see java.io.FilterInputStream#close() + */ + public void close() throws IOException { + if (!isOpen) { + log.debug("close() called on closed AppDataInputStream."); + // throw new IOException("Attempt to close an already closed AppDataInputStream"); + } else { + isOpen=false; + } + } + + /** + * Will return zero if stream has been closed. + * @see java.io.FilterInputStream#available() + */ + public int available() throws IOException { + if (isOpen) + return super.available(); + else + return 0; + } + +} diff --git a/src/uk/ac/vamsas/client/simpleclient/AppDataOutputStream.java b/src/uk/ac/vamsas/client/simpleclient/AppDataOutputStream.java new file mode 100644 index 0000000..339ec0d --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/AppDataOutputStream.java @@ -0,0 +1,86 @@ +/** + * + */ +package uk.ac.vamsas.client.simpleclient; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author jimp + * + */ +public class AppDataOutputStream extends DataOutputStream { + private Log log = LogFactory.getLog(AppDataOutputStream.class); + private boolean isOpen=true; + /** + * @param out + */ + public AppDataOutputStream(OutputStream out) { + super(out); + isOpen=true; + } + /* (non-Javadoc) + * @see java.io.DataOutputStream#flush() + */ + public void flush() throws IOException { + if (isOpen) + super.flush(); + else + log.warn("flush() called on closed AppDataOutputStream"); + } + /* (non-Javadoc) + * @see java.io.DataOutputStream#write(byte[], int, int) + */ + public synchronized void write(byte[] b, int off, int len) throws IOException { + if (isOpen) { + super.write(b, off, len); + } else { + log.debug("write(b,off,len) called on closed AppDataOutputStream"); + throw new IOException("Attempt to write to closed AppDataOutputStream"); + } + } + /* (non-Javadoc) + * @see java.io.DataOutputStream#write(int) + */ + public synchronized void write(int b) throws IOException { + if (isOpen) { + super.write(b); + } else { + log.debug("write(b) called on closed AppDataOutputStream"); + throw new IOException("Attempt to write to closed AppDataOutputStream"); + } + } + /** + * Sets an internal flag preventing further write operations + * to the AppData output stream and flushes any pending writes. + * @see java.io.FilterOutputStream#close() + */ + public void close() throws IOException { + isOpen=false; + super.flush(); + log.debug("AppDataOutputStream was closed."); + } + /* (non-Javadoc) + * @see java.io.FilterOutputStream#write(byte[]) + */ + public void write(byte[] b) throws IOException { + if (isOpen) { + super.write(b); + } else { + log.debug("write(b[]) called on closed AppDataOutputStream"); + throw new IOException("Attempt to write to closed AppDataOutputStream"); + } + } + /** + * @return true if stream is still Open. + */ + public boolean isOpen() { + return isOpen; + } + +} diff --git a/src/uk/ac/vamsas/client/simpleclient/ArchiveUrn.java b/src/uk/ac/vamsas/client/simpleclient/ArchiveUrn.java new file mode 100644 index 0000000..e3cdd3d --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/ArchiveUrn.java @@ -0,0 +1,34 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; +import java.net.MalformedURLException; + +/** + * Vamsas Document URN for files understood by ArchiveReader and + * written by VamsasArchive. + * vdoc://{Absolute path to file} + * @author jimp + * + */ +public class ArchiveUrn extends org.vamsas.client.SessionUrn { + /** + * a simple vamsas document urn prefix + */ + public static String VAMSASDOCUMENT="vdoc"; + static { + TYPES.put(ArchiveUrn.VAMSASDOCUMENT, ArchiveUrn.class); + } + + public ArchiveUrn(File docLocation) throws MalformedURLException { + super(VAMSASDOCUMENT, docLocation.getAbsoluteFile().toURL()); + } + + /** + * TODO: LATER: think about this again. + * @return File(urn.getPath()) + */ + public File asFile() { + return new File(urn.getPath()); + } + // TODO: add abstract 'handler' methods for resolving the URN to a particular class +} diff --git a/src/uk/ac/vamsas/client/simpleclient/ClientDocument.java b/src/uk/ac/vamsas/client/simpleclient/ClientDocument.java new file mode 100644 index 0000000..dde9632 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/ClientDocument.java @@ -0,0 +1,510 @@ +/* + * + */ +package uk.ac.vamsas.client.simpleclient; + +import java.io.IOException; +import java.util.Vector; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.client.ClientHandle; +import org.vamsas.client.IClientAppdata; +import org.vamsas.client.IClientDocument; +import org.vamsas.client.UserHandle; +import org.vamsas.client.Vobject; +import org.vamsas.client.VorbaId; +import org.vamsas.objects.core.ApplicationData; +import org.vamsas.objects.core.User; +import org.vamsas.objects.core.VAMSAS; +import org.vamsas.objects.core.VamsasDocument; +import org.vamsas.objects.utils.AppDataReference; +import org.vamsas.test.objects.Core; + +/** + * Maintains a collection of vamsas objects, appdatas and states, and provides api for a SimpleClient's client. + * @author jimp + */ +public class ClientDocument extends org.vamsas.client.ClientDocument implements IClientDocument { + private static Log log = LogFactory.getLog(ClientDocument.class); + private VamsasDocument doc; + protected SimpleClient sclient; + protected VamsasArchive archive = null; + /** + * indicate if new data has been incorporated + */ + private boolean isModified = false; + /** + * Public method for internal use by SimpleClient. + * @return true if document has been modified. + */ + public boolean isModified() { + return isModified; + } + private Vector updatedObjects=null; + /** + * + * prepare Application-side dataset from the vamsas Document archive + * @param doc - the dataset + * @param docHandler - the sessionFile IO handler + * @param Factory - the source of current and new vorbaIds + * @param sclient - the simpleclient instance + */ + protected ClientDocument(VamsasDocument doc, VamsasArchive docHandler, IdFactory Factory, SimpleClient sclient) { + super(Factory.getVorbaIdHash(), Factory); + + + /** + * prepare Application-side dataset from the vamsas Document archive + */ + this.sclient = sclient; + archive = docHandler; + this.doc = doc; + updatedObjects=null; /// TODO: correct this line + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClientDocument#getObject(org.vamsas.client.VorbaId) + */ + public Vobject getObject(VorbaId id) { + if (vamsasObjects==null) { + log.debug("getObject called on null objrefs list."); + return null; + } + if (vamsasObjects.containsKey(id)) + return (Vobject) vamsasObjects.get(id); + log.debug("Returning null Vobject reference for id "+id.getId()); + return null; + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClientDocument#getObjects(org.vamsas.client.VorbaId[]) + */ + public Vobject[] getObjects(VorbaId[] ids) { + if (vamsasObjects==null) { + log.debug("getObject[] called on null vamsasObjects list."); + return null; + } + Vobject[] vo = new Vobject[ids.length]; + for (int i=0,j=ids.length; i-1) { + if (isValidUpdate(newr[k], original[i])) { + modified=true; + rts.add(newr[k]); + newr[k]=null; + } else { + // LATER: try harder to merge ducument roots. + log.warn("Couldn't merge new VAMSAS root "+newr[k].getId()); + newr[k] = null; // LATER: this means we ignore mangled roots. NOT GOOD + } + } else { + // add in order. + rts.add(original[i]); + } + } + // add remaining (new) roots + for (int i=0,j=newr.length; i0) { + + ObjectInputStream is = new ObjectInputStream(fileLock.getBufferedInputStream(true)); + Object o; + o=is.readObject(); + if (o!=null) { + try { + clients = (ClientHandle[]) o; + } + catch (Exception e) { + System.err.println("Garbage in the clientHandle list "+this.sessionFile); + } + } + } + return clients; + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(System.err); + } catch (Exception e) { + e.printStackTrace(System.err); + } + } + return null; + } + /** + * get the clientList from the file. May return null if lock failed! + * @return clientList + */ + public ClientHandle[] retrieveClientList() { + if (lockFile()) { + ClientHandle[] clients = retrieveClientHandles(); + unlockFile(); + return clients; + } + return null; + } + /** + * get list from the locked ClientList. + * @param extantlock + * @return clientList or null if lock failed (or file was empty) + */ + public ClientHandle[] retrieveClientList(Lock extantlock) { + if (lockFile(extantlock)) { + ClientHandle[] clients = retrieveClientHandles(); + unlockFile(); + return clients; + } + return null; + } + /** + * adds clientHandle me to the clientList under an existing lock extantLock. + * @param me + * @param extantLock + * @return client index in list or 0 if lock was invalid or addClient operation failed. + */ + public int addClient(ClientHandle me, Lock extantLock) { + return addClient(me, true, extantLock); + } + + /** + * adds clientHandle me to the clientList under an existing lock. + * @param me - clientHandle + * @param disambig - if true then add will fail if an identical clientHandle already exists + * @param extantLock - existing lock + * @return client index in list or 0 if addClient (or the lock) failed. + */ + + public int addClient(ClientHandle me, boolean disambig, Lock extantLock) { + if (lockFile(extantLock)) { + syncnum = addClient(me, disambig); + unlockFile(); + return syncnum; + } + return 0; + } + + /** + * adds the ClientHandle to the list - if it is not unique, then the + * ClientHandle object is modified to make it unique in the list. returns the + * clientNumber for the client in the session. + * + * @param me + * @return + */ + + public int addClient(ClientHandle me) { + syncnum = addClient(me, true); + unlockFile(); + return syncnum; + } + + /** + * removes 'me' from the session ClientList without complaint if 'me' isn't in the clientList already. + * @param me client handle to be removed + * @param clientlock existing lock passed from watcher. + */ + public void removeClient(ClientHandle me, Lock clientlock) { + int mynum=-1; + if (lockFile(clientlock)) { + ClientHandle[] clients = retrieveClientHandles(); + if (clients != null) { + if ((syncnum<=0 || syncnum>clients.length) || clients[syncnum-1]!=me) { + for (int i = 0, j = clients.length; i < j; i++) + if (clients[i].equals(me)) { + mynum=i; + break; + } + } else { + mynum=syncnum-1; + } + if (mynum>-1) { + ClientHandle[] newlist = new ClientHandle[clients.length - 1]; + for (int k=0,i = 0, j = clients.length; i < j; i++) + if (i!=mynum) + newlist[k++] = clients[i]; + if (!putClientList(newlist)) + throw new Error("Failed to write new clientList!"); // failed to put the clientList to disk. + } + } + unlockFile(); + } else { + throw new Error("Couldn't get lock for "+((sessionFile==null) ? "Unitialised sessionFile in ClientsFile" : sessionFile.getAbsolutePath())); + } + } + /** + * Adds a ClientHandle to the ClientList file - optionally disambiguating + * the ClientHandle (modifes the URN). + * Note: Caller is left to release the lock on the ClientList. + * @param me + * @param disambiguate - + * flag indicating if the URN for me should be disambiguated to + * differentiate between sessions. + * @return index of clientHandle in new list, or -1-position of existing + * clientHandle (if disambiguate is true) + */ + 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) { + // TODO: move this into ClientUrn as a standard form method. + me.setClientUrn("vamsas://"+me.getClientName()+":"+me.getVersion()+"/"); + } + if (clients == null) { + clients = new ClientHandle[1]; + clients[0] = me; + newclient = 1; + } else { + int k = 0; + for (int i = 0, j = clients.length; i < j; i++) { + if (clients[i].equals(me)) { + if (disambiguate) { + while (clients[i].equals(me)) { + me.setClientUrn(me.getClientUrn() + k++); // TODO: make a better + // disambiguation of + // urn. + } + } else { + // will not write the ambiguous clientHandle to disk, just return + // its index. + return -1 - i; + } + } + } + int i, j; + ClientHandle[] newlist = new ClientHandle[clients.length + 1]; + for (i = 0, j = clients.length; i < j; i++) + newlist[i] = clients[i]; + newlist[j] = me; + clients = newlist; + newclient = j+1; + } + if (!putClientList(clients)) + return 0; // failed to put the clientList to disk. + } + return newclient; + } + /** + * when set true - get FileNotFoundExceptions on WinXP when writing to locked stream after the backup has been made (via the backupFile method) + */ + boolean backup=false; + /** + * safely writes clients array to the file referred to by sessionFile. + * + * @param clients + * @return true if successful write. Throws Errors otherwise. + */ + protected boolean putClientList(ClientHandle[] clients) { + if (lockFile()) { + File templist=null; + if (!backup || (templist = backupSessionFile()) != null) { + int retries=3; + while (retries-->0) { + try { + ObjectOutputStream os = + new ObjectOutputStream(fileLock.getBufferedOutputStream(true)); + log.debug("About to write "+clients.length+" clientHandles to output stream."); + os.writeObject(clients); + os.close(); + // All done - remove the backup. + if (backup) + templist.delete(); + templist = null; + retries=-1; + } catch (Exception e) { + System.err + .println("Serious - problems writing to sessionFile."); + if (retries>0 && templist != null) { + System.err.println("Recovering from Backup in " + + templist.getAbsolutePath()); + templist.renameTo(fileLock.target); + } + e.printStackTrace(System.err); + } + } + if (retries>-2) { + System.err + .println("Serious - problems writing to sessionFile. Giving Up."); + return false; + } + } else { + throw new Error( + "Couldn't create backup of the clientList before writing to it!"); + } + } else { + throw new Error("Could not lock the clientList: " + + ((sessionFile == null) ? "Unitialized ClientsFile" + : " failed to get lock on " + sessionFile.getAbsolutePath())); + } + // successful! + return true; + } + + public void clearList() { + if (lockFile()) { + try { + FileOutputStream fout = fileLock.getFileOutputStream(true); + fout.flush(); + fout.close(); + } catch (Exception e) { + throw new Error("Problems trying to clear clientlist!",e); + + } + } + + } +} diff --git a/src/uk/ac/vamsas/client/simpleclient/EventGeneratorThread.java b/src/uk/ac/vamsas/client/simpleclient/EventGeneratorThread.java new file mode 100644 index 0000000..8d028ef --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/EventGeneratorThread.java @@ -0,0 +1,277 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.Hashtable; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.client.Events; + +/** + * monitors watcher objects and generates events. + */ +public class EventGeneratorThread extends Thread implements Runnable { + private static Log log = LogFactory.getLog(EventGeneratorThread.class); + private SimpleClient client; + private Hashtable handlers; // manager object + private VamsasSession session; + + /** + * list with all the clientHandles for the session + */ + protected FileWatcher clientfile=null; + /** + * the session's vamsasDocument + */ + protected FileWatcher vamsasfile=null; + /** + * written to by client when its app calls storeDocument. + */ + protected FileWatcher storeFile=null; + + private boolean watch=false; + + + EventGeneratorThread(VamsasSession s, SimpleClient _client, Hashtable eventhandlers) { + if (eventhandlers==null || s==null || _client==null) + throw new Error("Null arguments to EventGeneratorThread constructor."); + handlers = eventhandlers; + session = s; + client = _client; + setName(s.sessionDir.getName()); + initWatchers(); + } + + private void initWatchers() { + if (clientfile==null) + clientfile = session.getClientWatcher(); + if (vamsasfile ==null) + vamsasfile = session.getDocWatcher(); + if (storeFile == null) + storeFile = session.getStoreWatcher(); + clientfile.setState(); + vamsasfile.setState(); + storeFile.setState(); + } + boolean ownsf = false; + /** + * scans all watchers and fires changeEvents if necessary + * @return number of events generated. + */ + private int checkforEvents() { + Lock watchlock; + //TODO : leave slog.info messages for the events that occur. + int raised=0; + // could make this general - but for now keep simple + if ((watchlock=storeFile.getChangedState())!=null) { + // TODO: define the storeFile semaphore mechanism : file exists - all clients inform their apps, and then the client that wrote the file should delete the file (it should hold the lock to it). + if (storeFile.exists) { + PropertyChangeSupport h = (PropertyChangeSupport) handlers.get(Events.DOCUMENT_FINALIZEAPPDATA); + if (h!=null) { + log.debug("Triggering DOCUMENT_FINALIZEAPPDATA"); + raised++; + h.firePropertyChange(client.getSessionUrn(), null, client); + // expect client to + vamsasfile.setState(); + } + } + } + if ((watchlock=clientfile.getChangedState())!=null) { + // see what happened to the clientfile - compare our internal version with the one in the file, or just send the updated list out...? + // + /** + * Generated when a new vamsas client is attached to a session (Handle is + * passed) Note: the newly created client does not receive the event. + * + public static final String CLIENT_CREATION = "org.vamsas.client.events.clientCreateEvent"; + */ // as the test + /** + * Generated when a vamsas client leaves a session (Handle is passed to all + * others). + public static final String CLIENT_FINALIZATION = "org.vamsas.client.events.clientFinalizationEvent"; + */ // again - as the test. + raised++; + } + if ((watchlock=vamsasfile.getChangedState())!=null) { + + /** + * Generated when a client has finished updating the document. Passes + * applicationHandle of client so the updating client can recognise its own + * updates. + public static final String DOCUMENT_UPDATE = "org.vamsas.client.events.documentUpdateEvent"; + */ + // read apphandle from 'lastUpdate' session file. + // pass apphandle name to appHandler ? + + } + /** + * Generated when a new vamsas document is created (perhaps from some existing + * Vamsas data) so an application may do its own data space initialization. + * TODO: decide if this is called when an app is connected to a stored + * session... + public static final String DOCUMENT_CREATE = "org.vamsas.client.events.documentCreateEvent"; + */ + // check if this session's appInit flag is set - if not - generate event for this app. + // prolly don't need this at the moment - when an app does getDocument it can to the initing then. + + + /** + * Generated prior to session Shutdown, after the last participating vamsas + * client has finalized. + * TODO: decide on purpose of this ? is this for benefit of multi-session Apps only ? + public static final String SESSION_SHUTDOWN = "org.vamsas.client.events.SessionShutdownEvent"; + */ + + /** + * Generated for all clients when any client calls IClient.storeDocument() to + * allow them to store any updates before an offline copy of the session is + * created. Any client that handles this should call the + * IClient.getDocument(), update and then IClient.updateDocument in the same + * handler thread. + * EventName: + * NewValue: org.vamsas.client.IClient for session. + * + public static final String DOCUMENT_FINALIZEAPPDATA = "org.vamsas.client.events.DocumentFinalizeAppData"; +*/ + // watch for finalization semaphore (last finalised sessionFile). + + /** + * Generated by Vorba stub after the penultimate client makes a call to + * closeDocument(). Sequence is as follows : 1. All other vamsas clients have + * called closeDocument() 2. Final living client monitors closures, and + * realises that it is last. 3. Final client generates event to prompt + * associated application to inquire if the user wishes to save the document + * for future reference. + * * Any call to closeDocument in a thread other than the registered + * EventListener will block until the RequestToClose handler has exited. + * + */ + // public static final String DOCUMENT_REQUESTTOCLOSE = "org.vamas.client.DocumentRequestToCloseEvent"; + + return raised; + } + + private void initEvents() { + + } + /** + * Events raised by IClient and propagated to others in session + */ + + /** + * number of milliseconds between any file state check. + */ + long POLL_UNIT = 20; + protected void wait(int u) { + if (u<=0) + u=1; + long l = System.currentTimeMillis()+POLL_UNIT*u; + while (System.currentTimeMillis()0) { + sequence++; + } else { + idstring+="1/"; + sequence=1; + } + newidstring=idstring+Integer.toString(sequence); + } while (extantids.containsKey(newidstring)); + extantids.put(newidstring, vobject); // hash the Vobject by its new Id + VorbaId id = newId(newidstring); // VorbaId.hash()==newidstring.hash() so we can still recover vobject + return id; + } + + /* (non-Javadoc) + * @see org.vamsas.client.VorbaIdFactory#setSession(org.vamsas.client.SessionHandle) + */ + protected void setSession(SessionHandle sessionhandle) { + if (sessionhandle!=null) + session=sessionhandle; + else + log.warn("setSession(null) called."); + } + + /* (non-Javadoc) + * @see org.vamsas.client.VorbaIdFactory#getSessionHandle() + */ + public SessionHandle getSessionHandle() { + return session; + } + + /* (non-Javadoc) + * @see org.vamsas.client.VorbaIdFactory#setClient(org.vamsas.client.ClientHandle) + */ + protected void setClient(ClientHandle appHandle) { + if (appHandle!=null) + client = appHandle; + else + log.warn("setClient(null) called."); + } + + /* (non-Javadoc) + * @see org.vamsas.client.VorbaIdFactory#getClientHandle() + */ + public ClientHandle getClientHandle() { + return client; + } + + /* (non-Javadoc) + * @see org.vamsas.client.VorbaIdFactory#setUser(org.vamsas.client.UserHandle) + */ + protected void setUser(UserHandle userHandle) { + if (userHandle!=null) + user = userHandle; + else + log.warn("setUser(null) called."); + } + + /* (non-Javadoc) + * @see org.vamsas.client.VorbaIdFactory#getUserHandle() + */ + public UserHandle getUserHandle() { + return user; + } + /** + * Convenience method used for default behaviour in testing + * and any anonymous internal vamsasDocument unmarshalling + * @param clientname + * @return + */ + protected static IdFactory getDummyFactory(String clientname) { + if (clientname==null) + clientname="uk.ac.vamsas.client.simpleclient.IdFactory"; + return new IdFactory(new SessionHandle("dummy.session"), + new ClientHandle(clientname,VersionEntries.latestVersion()), + new UserHandle(clientname, "Arnold User's Inc.")); + } +} diff --git a/src/uk/ac/vamsas/client/simpleclient/ListFile.java b/src/uk/ac/vamsas/client/simpleclient/ListFile.java new file mode 100644 index 0000000..29bb29e --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/ListFile.java @@ -0,0 +1,25 @@ +/** + * + */ +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; + + +/** + * base class for generic list storage and retrieval operations from a locked IO file + * TODO: LATER: generalize for all list storage and retrieval operations + * (pull-up from ClientsFile object) + */ +public class ListFile extends SessionFile { + + /** + * @param file + */ + public ListFile(File file) throws java.io.IOException { + super(file); + if (!this.sessionFile.exists()) + this.sessionFile.createNewFile(); + } + +} diff --git a/src/uk/ac/vamsas/client/simpleclient/Lock.java b/src/uk/ac/vamsas/client/simpleclient/Lock.java new file mode 100644 index 0000000..a124745 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/Lock.java @@ -0,0 +1,96 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.ByteChannel; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; + +import org.apache.commons.logging.LogFactory; + +/** + * transient object representing a file lock + * This lock should hold for all processes interacting in a session. + * @author jimp + */ + +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 + */ + protected Lock(java.io.File lockfile) { + target = lockfile; + } + /** + * + * @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 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) + * @param clear true means file will be cleared to zero length + * @return null if file is not locked + * @throws IOException + */ + 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 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 + * @return null if file is not locked. + */ + public BufferedInputStream getBufferedInputStream(boolean atStart) throws IOException { + FileInputStream fis = getFileInputStream(atStart); + if (fis!=null) + return new BufferedInputStream(fis); + return null; + } + /** + * safe lock target length() function. + * @return -1 for non-lockable target, otherwise target's file length + */ + public abstract long length(); + public abstract RandomAccessFile getRaFile() throws IOException; + public abstract FileChannel getRaChannel() throws IOException; +} diff --git a/src/uk/ac/vamsas/client/simpleclient/LockFactory.java b/src/uk/ac/vamsas/client/simpleclient/LockFactory.java new file mode 100644 index 0000000..d8098d7 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/LockFactory.java @@ -0,0 +1,52 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; + +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); + // fileLock = new Lock(sessionFile); + return fileLock.isLocked(); + } + + /** + * Explicitly release the SessionFile's lock. + * + * @return true if lock was released. + */ + protected void unlockFile() { + if (fileLock != null) { + fileLock.release(); + fileLock = null; + } + } + + /** + * Makes a backup of the sessionFile. + * @return Backed up SessionFile or null if failed to make backup. + */ + protected File backupSessionFile() { + return backupSessionFile(null, sessionFile.getName(),".old", sessionFile.getParentFile()); + } + + protected File backupSessionFile(Lock extantLock, String backupPrefix, String backupSuffix, File backupDir) { + File tempfile=null; + if (lockFile(extantLock)) { + try { + tempfile = File.createTempFile(backupPrefix, backupSuffix, backupDir); + if (fileLock.length()>0) { + FileOutputStream tos = new FileOutputStream(tempfile); + ReadableByteChannel channel; + tos.getChannel().transferFrom(channel=fileLock.getRaChannel(), 0, + fileLock.length()); + tos.close(); + if (!channel.isOpen()) + throw new Error(tos.getChannel().getClass()+".transferFrom closes source channel!"); + if (!lockFile(extantLock)) + throw new Error("Lost lock for "+sessionFile.getName()+" after backup."); + + } + } catch (FileNotFoundException e1) { + log.warn("Can't create temp file for "+sessionFile.getName(),e1); + tempfile=null; + } catch (IOException e1) { + log.warn("Error when copying content to temp file for "+sessionFile.getName(),e1); + tempfile=null; + } + } + return tempfile; + } + /** + * Replaces data in sessionFile with data from file handled by another sessionFile + * passes up any exceptions. + * @param newData source for new data + */ + protected void updateFrom(Lock extantLock, SessionFile newData) throws IOException { + log.debug("Updating "+sessionFile.getAbsolutePath()+" from "+newData.sessionFile.getAbsolutePath()); + if (newData==null) + throw new IOException("Null newData object."); + if (newData.sessionFile==null) + throw new IOException("Null SessionFile in newData."); + + if (!lockFile(extantLock)) + throw new IOException("Failed to get write lock for "+sessionFile); + if (!newData.lockFile()) + throw new IOException("Failed to get lock for updateFrom "+newData.sessionFile); + RandomAccessFile nrafile = newData.fileLock.getRaFile(); + nrafile.seek(0); + RandomAccessFile trafile = fileLock.getRaFile(); + /*long tries=5000; + while (trafile==null && --tries>0) { + log.debug("Lost lock on "+sessionFile+"! Re-trying for a transfer."); + lockFile(); + trafile = fileLock.getRaFile(); + }*/ + trafile.seek(0); + trafile.getChannel().transferFrom(nrafile.getChannel(), 0, + nrafile.length()); + } + /** + * remove all trace of the sessionFile file + * + */ + protected void eraseExistence() { + unlockFile(); + if (sessionFile!=null) { + sessionFile.delete(); + sessionFile = null; + } + } + /* (non-Javadoc) + * @see uk.ac.vamsas.client.simpleclient.Lock#getBufferedInputStream(boolean) + */ + public BufferedInputStream getBufferedInputStream(boolean atStart) throws IOException { + lockFile(); + return fileLock.getBufferedInputStream(atStart); + } + + /* (non-Javadoc) + * @see uk.ac.vamsas.client.simpleclient.Lock#getBufferedOutputStream(boolean) + */ + public BufferedOutputStream getBufferedOutputStream(boolean clear) throws IOException { + lockFile(); + return fileLock.getBufferedOutputStream(clear); + } + + /* (non-Javadoc) + * @see uk.ac.vamsas.client.simpleclient.Lock#getFileInputStream(boolean) + */ + public FileInputStream getFileInputStream(boolean atStart) throws IOException { + lockFile(); + return fileLock.getFileInputStream(atStart); + } + + /* (non-Javadoc) + * @see uk.ac.vamsas.client.simpleclient.Lock#getFileOutputStream(boolean) + */ + public FileOutputStream getFileOutputStream(boolean clear) throws IOException { + lockFile(); + return fileLock.getFileOutputStream(clear); + } + +} diff --git a/src/uk/ac/vamsas/client/simpleclient/SessionFlagFile.java b/src/uk/ac/vamsas/client/simpleclient/SessionFlagFile.java new file mode 100644 index 0000000..27f83c7 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/SessionFlagFile.java @@ -0,0 +1,85 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * methods for setting and checking + * binary flags in a vamsas session directory. + * all methods apart from the constructor will + * throw a fatal error if the flagFile is not + * a valid java.io.File object. + * LATER: extract SessionFlag interface for generalizing the vamsas session code + * @author jimp + * + */ +public class SessionFlagFile { + private static Log log = LogFactory.getLog(SessionFlagFile.class); + protected File flagFile=null; + private void checkFlagFile() { + if (flagFile==null) { + log.fatal("Implementation error - uninitialized SessionFlagFile", + new Error("Implementation error - uninitialized SessionFlagFile")); + } + } + /** + * will log a warning if exceptions occur during flag creation. + * @return true if flag was set successfully + */ + public boolean setFlag() { + checkFlagFile(); + try { + if (flagFile.createNewFile()) { + log.debug("Set session flag "+flagFile); + } else { + log.debug("Session flag already set "+flagFile); + } + return true; + } + catch (Exception e) { + log.warn("Couldn't set session flag "+flagFile, e); + } + return false; + } + /** + * + * @return true if flag was cleared successfully + */ + public boolean clearFlag() { + checkFlagFile(); + if (flagFile.exists()) { + log.debug("clearing session flag "+flagFile); + if (!flagFile.delete()) { + log.warn("failed to clear session flag "+flagFile); + return false; + } + } else { + log.debug("clearFlag called for already cleared flag "+flagFile); + } + return true; + } + /** + * + * @return state of session flag + */ + public boolean checkFlag() { + checkFlagFile(); + if (flagFile.exists()) { + if (log.isDebugEnabled()) + log.debug("Flag '"+flagFile+"' is set."); + return true; + } + if (log.isDebugEnabled()) + log.debug("Flag '"+flagFile+"' is not set."); + return false; + } + /** + * @param flagFile + */ + public SessionFlagFile(File flagFile) { + super(); + this.flagFile = flagFile; + }; +} diff --git a/src/uk/ac/vamsas/client/simpleclient/SessionUrn.java b/src/uk/ac/vamsas/client/simpleclient/SessionUrn.java new file mode 100644 index 0000000..08516cd --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/SessionUrn.java @@ -0,0 +1,40 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; +import java.net.MalformedURLException; + +/** + * SessionUrn for simpleclient sessions: + * simpleclient://{Absolute path to session directory} + * @author jimp + * + */ +public class SessionUrn extends org.vamsas.client.SessionUrn { + /** + * a simple client session urn prefix + */ + public static final String SIMPLECLIENT="simpleclient"; + public static String VAMSASDOCUMENT="vdoc"; + static { + TYPES.put(SIMPLECLIENT, SessionUrn.class); + TYPES.put(SessionUrn.VAMSASDOCUMENT, SessionUrn.class); + } + + public SessionUrn(File sessionLocation) throws MalformedURLException { + // TODO: LATER: implement switch to have vamsas document or simpleclient sessions for same constructor + super(SIMPLECLIENT, sessionLocation.getAbsoluteFile().toURL()); + //else + // super(VAMSASDOCUMENT, sessionLocation); + } + public SessionUrn(VamsasSession session) throws MalformedURLException { + super(SIMPLECLIENT, session.sessionDir.getAbsoluteFile().toURL()); + } + /** + * TODO: LATER: think about this again. + * @return File(urn.getPath()) + */ + public File asFile() { + return new File(urn.getPath()); + } + // TODO: add abstract 'handler' methods for resolving the URN to a particular class +} diff --git a/src/uk/ac/vamsas/client/simpleclient/SessionsFile.java b/src/uk/ac/vamsas/client/simpleclient/SessionsFile.java new file mode 100644 index 0000000..f931118 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/SessionsFile.java @@ -0,0 +1,342 @@ +/* EMBL - The European Bioinformatics institute +* MSD Group +* VAMSAS Project +* +* Copyright (c) 2005-2006 Thr European Bioinformatics Institute. All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* +* 3. The name MSD must not be used to endorse or promote products +* derived from this software without prior written permission. For +* written permission, please contact msd-help@ebi.ac.uk +* +* 4. Products derived from this software may not be called "MSD" +* nor may "MSD" appear in their names without prior written +* permission of the MSD developers. +* +* 5. Redistributions of any form whatsoever must retain the following +* acknowledgment: +* "This product includes software developed by MSD +* (http://www.ebi.ac.uk/)" +* +* THIS SOFTWARE IS PROVIDED BY THE MSD GROUP ``AS IS'' AND ANY +* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE ENSEMBL GROUP OR +* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +* OF THE POSSIBILITY OF SUCH DAMAGE. +* +* The European Bioinformatics Institute may publish revised and/or new +* versions of this license with new releases of VAMSAS software. +*============================================================================== +* +* @author Pierre MARGUERITE +* +* Dec 13, 2006 - VamsasClientV4 +* +*/ + +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.client.SessionHandle; + +/** + * @author Pierre MARGUERITE + * + * + */ +public class SessionsFile extends ListFile { + + private static Log log = LogFactory.getLog(SessionsFile.class); + /** + * when set true - get FileNotFoundExceptions on WinXP when writing to locked stream after the backup has been made (via the backupFile method) + */ + boolean backup=false; + /** + * number of my session in list - passed back when a session + * is added to list, and used (if valid) for quickly + * looking up presence of session handle in the list. + */ + private int syncnum = 1; + /** + * @param file + */ + public SessionsFile(File file) throws java.io.IOException { + super(file); + } + + + /** + * internal method for getting sessionsList - ensures a lock has been made but + * does not release it. + * + * @return list of clients + */ + private SessionHandle[] retrieveSessionHandles() { + if (lockFile()) { + try { + SessionHandle[] clients=null; + if (this.fileLock.length()>0) { + + ObjectInputStream is = new ObjectInputStream(this.fileLock.getBufferedInputStream(true)); + Object o; + o=is.readObject(); + if (o!=null) { + try { + clients = (SessionHandle[]) o; + } + catch (Exception e) { + log.error("Garbage in the clientHandle list "+this.sessionFile,e); + } + } + } + return clients; + } catch (FileNotFoundException e) { + // e.printStackTrace(System.err); + log.error(e); + } catch (Exception e) { + log.error(e); + //e.printStackTrace(System.err); + } + } + return null; + } + + /** + * get the SessionsList from the file. May return null if lock failed! + * @return sessionsList + */ + public SessionHandle[] retrieveSessionsList() { + if (lockFile()) { + SessionHandle[] clients = retrieveSessionHandles(); + unlockFile(); + return clients; + } + return null; + } + + /** + * get list from the locked ClientList. + * @param extantlock + * @return clientList or null if lock failed (or file was empty) + */ + public SessionHandle[] retrieveSessionsList(Lock extantlock) { + if (lockFile(extantlock)) { + SessionHandle[] clients = retrieveSessionHandles(); + unlockFile(); + return clients; + } + return null; + } + + + /** + * adds clientHandle me to the clientList under an existing lock extantLock. + * @param me + * @param extantLock + * @return client index in list or 0 if lock was invalid or addClient operation failed. + */ + public int addSession(SessionHandle me, Lock extantLock) { + return addSession(me, true, extantLock); + } + + /** + * adds SessionsHandle me to the sessionsList under an existing lock. + * @param me - sessionsHandle + * @param disambig - if true then add will fail if an identical clientHandle already exists + * @param extantLock - existing lock + * @return client index in list or 0 if addClient (or the lock) failed. + */ + + public int addSession(SessionHandle session, boolean disambig, Lock extantLock) { + if (lockFile(extantLock)) { + this.syncnum = addSession(session, disambig); + unlockFile(); + return this.syncnum; + } + return 0; + } + + /** + * removes the current session from the SessionsList without complaint if the session isn't in the sessionsList already. + * @param me client handle to be removed + * @param clientlock existing lock passed from watcher. + */ + public void removeSession(SessionHandle session, Lock clientlock) { + int mynum =-1; + if (lockFile(clientlock)) { + SessionHandle[] sessions = retrieveSessionHandles(); + if (sessions != null) { + if ((this.syncnum<=0 || this.syncnum>sessions.length) || sessions[this.syncnum-1]!=session) { + for (int i = 0, j = sessions.length; i < j; i++) + if (sessions[i].equals(session)) { + mynum=i; + break; + } + } else { + mynum=this.syncnum-1; + } + if (mynum>-1) { + SessionHandle[] newlist = new SessionHandle[sessions.length - 1]; + for (int k=0,i = 0, j = sessions.length; i < j; i++) + if (i!=mynum) + newlist[k++] = sessions[i]; + if (!putSessionsList(newlist)) + throw new Error("Failed to write new sessionsList!"); // failed to put the sessionList to disk. + } + } + unlockFile(); + } else { + throw new Error("Couldn't get lock for "+((sessionFile==null) ? "Unitialised sessionFile in SessionsFile" : this.sessionFile.getAbsolutePath())); + } + } + /** + * Adds a SessionHandle to the SessionList file - optionally disambiguating + * the SessionHandle (modifes the URN). + * Note: Caller is left to release the lock on the SessionList. + * @param me + * @param disambiguate - + * flag indicating if the URN for me should be disambiguated to + * differentiate between sessions. + * @return index of sessionHandle in new list, or -1-position of existing + * sessionHandle (if disambiguate is true) + */ + protected int addSession(SessionHandle session, boolean disambiguate) { + int newsession = 0; + int tries=5; + while (tries-->0 && !lockFile()) + try { Thread.sleep(1); } catch (Exception e){}; + if (lockFile()) { + SessionHandle[] sessions = retrieveSessionHandles(); + + if (sessions == null) { + sessions = new SessionHandle[1]; + sessions[0] = session; + newsession = 1; + } else { + int k = 0; + for (int i = 0, j = sessions.length; i < j; i++) { + if ( sessions[i].equals(session)) { + if (disambiguate) { + while (sessions[i].equals(session)) { + // me.setClientUrn(me.getClientUrn() + k++); // TODO: make a better + // disambiguation of + // urn. + } + } else { + // will not write the ambiguous clientHandle to disk, just return + // its index. + return -1 - i; + } + } + } + int i, j; + SessionHandle[] newlist = new SessionHandle[sessions.length + 1]; + for (i = 0, j = sessions.length; i < j; i++) + newlist[i] = sessions[i]; + newlist[j] = session; + sessions = newlist; + newsession = j+1; + } + if (!putSessionsList(sessions)) + return 0; // failed to put the clientList to disk. + } + return newsession; + } + + + /** + * safely writes sessions array to the file referred to by sessionFile. + * + * @param clients + * @return true if successful write. Throws Errors otherwise. + */ + protected boolean putSessionsList(SessionHandle[] clients) { + if (lockFile()) { + File templist=null; + if (!this.backup || (templist = backupSessionFile()) != null) { + int retries=3; + while (retries-->0) { + try { + ObjectOutputStream os = + new ObjectOutputStream(this.fileLock.getBufferedOutputStream(true)); + log.debug("About to write "+clients.length+" sessionHandles to output stream."); + os.writeObject(clients); + os.close(); + // All done - remove the backup. + if (this.backup) + templist.delete(); + templist = null; + retries=-1; + } catch (Exception e) { + // System.err + //.println("Serious - problems writing to sessionFile."); + log.error("Serious - problems writing to sessionFile.",e); + if (retries>0 && templist != null) { + // System.err.println("Recovering from Backup in " + // + templist.getAbsolutePath()); + log.error("Recovering from Backup in "+ templist.getAbsolutePath()); + templist.renameTo(this.fileLock.target); + } + //e.printStackTrace(System.err); + log.error(e); + } + } + if (retries>-2) { + // System.err + // .println("Serious - problems writing to sessionFile. Giving Up."); + log.error("Serious - problems writing to sessionFile. Giving Up."); + return false; + } + } else { + throw new Error( + "Couldn't create backup of the clientList before writing to it!"); + } + } else { + throw new Error("Could not lock the clientList: " + + ((this.sessionFile == null) ? "Unitialized ClientsFile" + : " failed to get lock on " + this.sessionFile.getAbsolutePath())); + } + // successful! + return true; + } + + public void clearList() { + if (lockFile()) { + try { + FileOutputStream fout = this.fileLock.getFileOutputStream(true); + fout.flush(); + fout.close(); + } catch (Exception e) { + throw new Error("Problems trying to clear clientlist!",e); + + } + } + + } +} diff --git a/src/uk/ac/vamsas/client/simpleclient/SimpleClient.java b/src/uk/ac/vamsas/client/simpleclient/SimpleClient.java new file mode 100644 index 0000000..2858dd3 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/SimpleClient.java @@ -0,0 +1,416 @@ +/* + * Created on 15-Sep-2005 + * + * TODO To change the template for this generated file go to + * Window - Preferences - Java - Code Style - Code Templates + */ +package uk.ac.vamsas.client.simpleclient; + +import java.beans.EventHandler; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Hashtable; +import java.util.Vector; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.client.ClientHandle; +import org.vamsas.client.Events; +import org.vamsas.client.IClient; +import org.vamsas.client.IClientDocument; +import org.vamsas.client.IObjectUpdate; +import org.vamsas.client.InvalidSessionUrnException; +import org.vamsas.client.SessionHandle; +import org.vamsas.client.UserHandle; +import org.vamsas.objects.core.ApplicationData; +import org.vamsas.objects.core.Entry; +import org.vamsas.objects.core.LockFile; +import org.vamsas.objects.core.VamsasDocument; +import org.vamsas.objects.utils.AppDataReference; +import org.vamsas.objects.utils.ProvenanceStuff; +import org.vamsas.objects.utils.document.VersionEntries; + +/** + * @author jimp + */ +public class SimpleClient implements IClient { + + private static Log log = LogFactory.getLog(SimpleClient.class); + + protected UserHandle user = null; + + protected SessionUrn session = null; + protected VamsasSession _session; + protected ClientHandle client = null; + protected EventGeneratorThread evgen = null; + protected ClientDocument cdocument = null; + /** + * object hash table that persists in each client holding vorbaIds and hash values after a document write + */ + protected Hashtable extantobjects=null; + /** + * construct a transient IdFactory instance - this should last only as long as the + * SimpleClient object holds the lock on the vamsas document being created/manipulated. + * @return + */ + private IdFactory makeVorbaIdFactory() { + return new IdFactory(getSessionHandle(), client, user); + } + + /** + * construct SimpleClient for user, client and VamsasSession directory + * use the SimpleClientFactory rather than this constructor directly. + * @param user + * @param client + * @param sess + */ + protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess) throws InvalidSessionUrnException { + // TODO: validate user/client/session + _session = sess; + this.user = user; + this.client = client; + try { + session = new SessionUrn(_session); + } catch (MalformedURLException e) { + log.error("Couldn't form a valid SessionUrn object!",e); + throw new InvalidSessionUrnException(_session.toString()); + } + } + /** + * construct new session by importing objects from an existing vamsas document + * @param user + * @param client + * @param sess + * @param importingArchive + * @throws Exception IOExceptions for Session IO problems, and general Exception if importing document is invalid. + */ + protected SimpleClient(UserHandle user, ClientHandle client, VamsasSession sess, File importingArchive) throws Exception { + this(user, client, sess); + VamsasArchive sessdoc = _session.getVamsasDocument(); + try { + VamsasArchiveReader odoc = new VamsasArchiveReader(importingArchive); + SimpleDocument sdoc = new SimpleDocument(makeVorbaIdFactory()); + VamsasDocument doc = sdoc.getVamsasDocument(odoc); + sessdoc.putVamsasDocument(doc, sdoc.vorba); + sessdoc.closeArchive(); + } catch (Exception e) { + sessdoc.cancelArchive(); + // write a dummy archive + _session.slog.info("Exception when importing document data from "+importingArchive); + throw new Exception("Failed to import data from "+importingArchive, e); + } + } + + /* + * (non-Javadoc) + * LATER: check that build substitution variables are correct + * @see org.vamsas.client.IClient#getAbout() + */ + public String getAbout() { + return new String("VORBA SimpleClient version $version$ build $build$"); + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#getSessionUrn() + */ + public String getSessionUrn() { + return session.getSessionUrn(); + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#getSessionHandle() + */ + public SessionHandle getSessionHandle() { + // TODO: eliminate SessionHandle ? need to refactor interfaces. + SessionHandle sh = new SessionHandle(session.getSessionUrn()); + return sh; + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#getClientHandle() + */ + public ClientHandle getClientHandle() { + return client; + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#getUserHandle() + */ + public UserHandle getUserHandle() { + return user; + } + /** + * + * @return user field for a provenance entry + */ + protected String getProvenanceUser() { + return new String(user.getFullName()); + } + /** + * construct a provenance entry for this client with the specified action string. + * @param action + * @return properly completed provenance entry + */ + protected Entry getProvenanceEntry(String action) { + Entry prov = ProvenanceStuff.newProvenanceEntry(client.getClientUrn(), getProvenanceUser(), action); + return prov; + } + private Hashtable handlers = initHandlers(); + + private Vector listeners = new Vector(); + + /** + * make all the PropertyChangeSupport objects for the + * events described in org.vamsas.client.Event + * @return + */ + private static Hashtable initHandlers() { + Hashtable events = new Hashtable(); + java.util.Iterator evt = Events.EventList.iterator(); + while (evt.hasNext()) { + Object ths = evt.next(); + events.put(ths, (Object) new PropertyChangeSupport(ths)); + } + return events; + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener) + */ + public void addDocumentUpdateHandler(PropertyChangeListener evt) { + if (handlers.containsKey(Events.DOCUMENT_UPDATE)) { + Object handler; + ((PropertyChangeSupport) (handler = handlers.get(Events.DOCUMENT_UPDATE))) + .addPropertyChangeListener(evt); + listeners.add(handler); + listeners.add((Object) evt); + } + } + boolean finalized=false; + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#finalizeClient() + */ + public void finalizeClient() { + // TODO: determine if this is last client in session + // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), ( + + // if (handlers.containsKey(Events.)) + // if (handlers.containsKey(Events.CLIENT_FINALIZATION)) + // deregister listeners. + // mark this instance as finalized + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#getClientDocument() + */ + public IClientDocument getClientDocument() throws IOException { + if (cdocument!=null) { + // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called. + return cdocument; + } + VamsasArchive va = null; + try { + // LATER: bail out if it takes too long to get the lock ? + va = _session.getVamsasDocument(); + } + catch (IOException e) { + throw new IOException("Failed to get lock on session document"); + } + VamsasDocument doc=null; + IdFactory vorba = null; + // TODO: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?) + try { + va.setVorba(vorba=makeVorbaIdFactory()); + // if session currently holds data - read it in - or get a dummy + _session.slog.debug("Accessing document"); + doc = + va.getVamsasDocument(getProvenanceUser(), + "created new session document.", null); + if (doc!=null) + _session.slog.debug("Successfully retrieved document."); + else + log.error("Unexpectedly retrieved null document!"); + } + catch (Exception e) { + log.error("Failed to get session document for session directory '"+_session.sessionDir+"'", e); + throw new IOException("Failed to get session document for session directory '"+_session.sessionDir+"'"); + } + // Construct the IClientDocument instance + + ClientDocument cdoc = new ClientDocument(doc, va, vorba, this); + return cdoc; + } + + /* + * (non-Javadoc) + * @throws Errors for invalid newdoc parameter + * @see org.vamsas.client.IClient#updateDocument(org.vamsas.client.IClientDocument) + */ + public void updateDocument(IClientDocument newdoc) { + if (!(newdoc instanceof ClientDocument)) { + throw new Error("Invalid IClientDocument instance for SimpleClient."); + } + if (cdocument==null) + throw new Error("Client Error - updateDocument() called before getClientDocument()."); + if (newdoc!=cdocument) + throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()"); + if (!cdocument.isModified()) { + if (log.isDebugEnabled()) + log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument."); + } else { + try { + if (!cdocument.updateSessionDocument()) { + log.warn("Session document did not update properly for session directory "+_session.sessionDir); + // cdocument.archive.cancelArchive(); // LATER: could be done - would need to prevent updateSessionDocument closing the archive. + _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()"); + } + } + catch (IOException e) { + log.warn("IO Problems when updating document!",e); + _session.slog.error("IO problems when attempting to update document."); + } + } + // garbage collect the ClientDocument instance. + try { + cdocument.finalize(); + + } catch (Throwable e) { + log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e); + } + cdocument = null; // this is probably done by finalize + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#storeDocument(java.io.File) + */ + public void storeDocument(File location) { + + // write storeDocument file to inform other clients that they should raise + Lock vamlock = evgen.want_to_store(); + // Events.DOCUMENT_FINALIZEAPPDATA + try { + _session.writeVamsasDocument(location, vamlock); + _session.clearUnsavedFlag(); + } catch (Exception e) { + log.warn("Exception whilst trying to store document in "+location,e); + } + + vamlock.release(); + } + + /* + * (non-Javadoc) + * + * @see org.vamsas.client.IClient#addVorbaEventHandler(java.lang.String, + * java.beans.PropertyChangeListener) + */ + public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) { + if (handlers.containsKey(EventChain)) { + Object handler; + ((PropertyChangeSupport) (handler = handlers.get(EventChain))) + .addPropertyChangeListener(evt); + listeners.add(handler); + listeners.add((Object) evt); + } + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClient#pollUpdate() + */ + public void pollUpdate() { + + if (evgen==null) { + log.warn("pollUpdate called on incomplete SimpleClient object."); + return; + } + + if (!evgen.isAlive()) { + log.warn("pollUpdate called before joinSession() - trying to do this."); + try { + joinSession(); + } catch (Exception e) { + log.error("Unexpected exception on default call to joinSession",e); + } + } + + //TODO ensure event generator robustly handles these interrupts. + log.debug("interrrupting event generator."); + evgen.interrupt(); + log.debug("interrrupted event generator."); + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClient#joinSession() + */ + public void joinSession() throws Exception { + // start the EventGenerator thread. + if (evgen==null) { + log.warn("joinSession called on incomplete SimpleClient object."); + return; + } + if (evgen.isAlive()) + throw new Error("Join session called twice for the same SimpleClient (IClient instance)."); + evgen.start(); + if (evgen.isAlive()) + log.debug("Started EventGenerator thread."); + else { + log.warn("Failed to start EventGenerator thread."); + throw new Exception("Failed to start event generator thread - client cannot be instantiated."); + } + if (evgen.countHandlersFor(Events.DOCUMENT_CREATE)>0) { + //TODO: is this application connecting to a newly created session document ? + //evgen.raise(Events.DOCUMENT_CREATE); + } + } + + + + /* (non-Javadoc) + * @see org.vamsas.client.IClient#importDocument(java.io.File) + */ + public void importDocument(File location) { + // TODO LATER: implement SimpleClient.importDocument() + log.error("importDocument is not yet implemented for a SimpleClient Session."); + } + + public IObjectUpdate getUpdateHandler(Class rootObject) { + // TODO Auto-generated method stub + return null; + } + + public IObjectUpdate[] getUpdateHandlers() { + // TODO Auto-generated method stub + return null; + } + + public void removeUpdateHandler(Class rootObject) { + // TODO Auto-generated method stub + + } + + public void setUpdateHandler(IObjectUpdate handler) { + // TODO Auto-generated method stub + + } +} diff --git a/src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java b/src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java new file mode 100644 index 0000000..dcaedee --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java @@ -0,0 +1,543 @@ +/** + * + */ +package uk.ac.vamsas.client.simpleclient; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.client.IClientAppdata; +import org.vamsas.objects.core.AppData; +import org.vamsas.objects.core.ApplicationData; +import org.vamsas.objects.core.User; +import org.vamsas.objects.utils.AppDataReference; + +/** + * @author jimp + * Access interface to data chunks read from a VamsasArchiveReader stream + * (or byte buffer input stream) or written to a VamsasArchive stream. + * // TODO: get VamsasArchiveReader from sclient + */ +public class SimpleClientAppdata implements IClientAppdata { + private static Log log = LogFactory.getLog(SimpleClientAppdata.class); + /** + * has the session's document been accessed to get the AppData entrys? + */ + protected boolean accessedDocument = false; + /** + * has the user datablock been modified ? + * temporary file containing new user specific application data chunk + */ + SessionFile newUserData=null; + JarOutputStream newUserDataStream = null; + /** + * has the apps global datablock been modified ? + * temporary file containing new global application data chunk + */ + SessionFile newAppData=null; + JarOutputStream newAppDataStream=null; + /** + * set by extractAppData + */ + protected ApplicationData appsGlobal = null; + /** + * set by extractAppData + */ + protected User usersData = null; + + ClientDocument clientdoc; + /** + * state flags + * - accessed ClientAppdata + * - accessed UserAppdata + * => inputStream from embedded xml or jar entry of backup has been created + * - set ClientAppdata + * - set UserAppdata + * => an output stream has been created and written to - or a data chunk has been written. + * - need flag for switching between embedded and jar entry mode ? - always write a jar entry for a stream. + * - need code for rewind and overwriting if the set*Appdata methods are called more than once. + * - need flags for streams to except a call to set*Appdata when an output stream exists and is open. + * - need + * @param clientdoc The ClientDocument instance that this IClientAppData is accessing + */ + protected SimpleClientAppdata(ClientDocument clientdoc) { + if (clientdoc==null) { + log.fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction."); + throw new Error("Implementation error - Null ClientDocument for SimpleClientAppdata construction."); + } + this.clientdoc = clientdoc; + } + /** + * gets appropriate app data for the application, if it exists in this dataset + * Called by every accessor to ensure data has been retrieved from document. + */ + private void extractAppData(org.vamsas.objects.core.VamsasDocument doc) { + if (doc==null) { + log.debug("extractAppData called for null document object"); + return; + } + if (accessedDocument) { + return; + } + Vector apldataset = AppDataReference.getUserandApplicationsData( + doc, clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle()); + accessedDocument = true; + if (apldataset!=null) { + if (apldataset.size()>0) { + AppData clientdat = (AppData) apldataset.get(0); + if (clientdat instanceof ApplicationData) { + appsGlobal = (ApplicationData) clientdat; + if (apldataset.size()>1) { + clientdat = (AppData) apldataset.get(1); + if (clientdat instanceof User) { + usersData = (User) clientdat; + } + if (apldataset.size()>2) + log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query."); + } + } else { + log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName()); + } + apldataset.removeAllElements(); // destroy references. + } + } + } + /** + * LATER: generalize this for different low-level session implementations (it may not always be a Jar) + * @param appdata + * @param docreader + * @return + */ + private JarInputStream getAppDataStream(AppData appdata, VamsasArchiveReader docreader) { + String entryRef = appdata.getDataReference(); + if (entryRef!=null) { + log.debug("Resolving appData reference +"+entryRef); + InputStream entry = docreader.getAppdataStream(entryRef); + if (entry!=null) { + if (entry instanceof JarInputStream) { + return (JarInputStream) entry; + } + log.warn("Implementation problem - docreader didn't return a JarInputStream entry."); + } + } else { + log.debug("GetAppDataStream called for an AppData without a data reference."); + } + return null; + } + /** + * yuk - size of buffer used for slurping appData JarEntry into a byte array. + */ + private final int _TRANSFER_BUFFER=4096*4; + + /** + * Resolve AppData object to a byte array. + * @param appdata + * @param archiveReader + * @return null or the application data as a byte array + */ + private byte[] getAppDataAsByteArray(AppData appdata, VamsasArchiveReader docreader) { + if (appdata.getData()==null) { + if (docreader==null) { + log.warn("Silently failing getAppDataAsByteArray with null docreader.",new Exception()); + return null; + } + // resolve and load data + JarInputStream entry = getAppDataStream(appdata, docreader); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try { + byte buff[] = new byte[_TRANSFER_BUFFER]; + int olen=0; + while (entry.available()>0) { + int len = entry.read(buff, olen, _TRANSFER_BUFFER); + bytes.write(buff, 0, len); + olen+=len; + } + buff=null; + } catch (Exception e) { + log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e); + } + if (bytes.size()>0) { + // LATER: deal with probable OutOfMemoryErrors here + log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference()); + byte data[] = bytes.toByteArray(); + bytes = null; + return data; + } + return null; + } else { + log.debug("Returning inline AppData block for "+appdata.getVorbaId()); + return appdata.getData(); + } + } + /** + * internal method for getting a DataInputStream from an AppData object. + * @param appdata + * @param docreader + * @return data in object or null if no data is accessible + */ + private DataInput getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) { + if (appdata!=null && docreader!=null) { + String entryRef = appdata.getDataReference(); + if (entryRef!=null) { + log.debug("Resolving AppData reference for "+entryRef); + InputStream jstrm = docreader.getAppdataStream(entryRef); + if (jstrm!=null) + return new AppDataInputStream(jstrm); + else { + log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId()); + return null; + } + } else { + // return a byteArray input stream + byte[] data=appdata.getData(); + if (data.length>0) { + ByteArrayInputStream stream = new ByteArrayInputStream(data); + return new DataInputStream(stream); + } else { + log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId()); + return null; + } + } + } else { + log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId()); + } + return null; + } + + /** + * internal method for getting ByteArray from AppData object + * @param clientOrUser - true for returning userData, otherwise return Client AppData. + * @return null or byte array + */ + private byte[] _getappdataByteArray(boolean clientOrUser) { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + byte[] data=null; + String appdName; + if (!clientOrUser) { + appdName = "Client's Appdata"; + } else { + appdName = "User's Appdata"; + } + log.debug("getting "+appdName+" as a byte array"); + extractAppData(clientdoc.getVamsasDocument()); + AppData object; + if (!clientOrUser) { + object = appsGlobal; + } else { + object = usersData; + } + if (object!=null) { + log.debug("Trying to resolve "+appdName+" object to byte array."); + data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader()); + } + if (data == null) + log.debug("Returning null for "+appdName+"ClientAppdata byte[] array"); + return data; + + } + + /** + * common method for Client and User AppData->InputStream accessor + * @param clientOrUser - the appData to resolve - false for client, true for user appdata. + * @return null or the DataInputStream desired. + */ + private DataInput _getappdataInputStream(boolean clientOrUser) { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + String appdName; + if (!clientOrUser) { + appdName = "Client's Appdata"; + } else { + appdName = "User's Appdata"; + } + if (log.isDebugEnabled()) + log.debug("getting "+appdName+" as an input stream."); + extractAppData(clientdoc.getVamsasDocument()); + AppData object; + if (!clientOrUser) { + object = appsGlobal; + } else { + object = usersData; + } + if (object!=null) { + log.debug("Trying to resolve ClientAppdata object to an input stream."); + return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader()); + } + log.debug("getClientInputStream returning null."); + return null; + } + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#getClientAppdata() + */ + public byte[] getClientAppdata() { + return _getappdataByteArray(false); + } + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#getClientInputStream() + */ + public DataInput getClientInputStream() { + return _getappdataInputStream(false); + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#getUserAppdata() + */ + public byte[] getUserAppdata() { + return _getappdataByteArray(true); + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#getUserInputStream() + */ + public DataInput getUserInputStream() { + return _getappdataInputStream(true); + } + /** + * methods for writing new AppData entries. + */ + private DataOutput _getAppdataOutputStream(boolean clientOrUser) { + String apdname; + SessionFile apdfile=null; + if (!clientOrUser) { + apdname = "clientAppData"; + apdfile = newAppData; + } else { + apdname = "userAppData"; + apdfile = newUserData; + } + try { + if (apdfile==null) { + apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar"); + log.debug("Successfully made temp appData file for "+apdname); + } else { + // truncate to remove existing data. + apdfile.fileLock.getRaFile().setLength(0); + log.debug("Successfully truncated existing temp appData for "+apdname); + } + } catch (Exception e) { + log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e); + } + // we do not make another file for the new entry if one exists already + if (!clientOrUser) { + newAppData = apdfile; + } else { + newUserData = apdfile; + } + try { + apdfile.lockFile(); + // LATER: Refactor these local AppDatastream IO stuff to their own class. + JarOutputStream dstrm = + new JarOutputStream(apdfile.fileLock.getBufferedOutputStream(true)); + if (!clientOrUser) { + newAppDataStream = dstrm; + } else { + newUserDataStream = dstrm; + } + dstrm.putNextEntry(new JarEntry("appData_entry.dat")); + // LATER: there may be trouble ahead if an AppDataOutputStream is written to by one thread when another truncates the file. This situation should be prevented if possible + return new AppDataOutputStream(dstrm); + } + catch (Exception e) { + log.error("Whilst opening jar output stream for file "+apdfile.sessionFile); + } + // tidy up and return null + apdfile.unlockFile(); + return null; + } + /** + * copy data from the appData jar file to an appropriately + * referenced jar or Data entry for the given ApplicationData + * Assumes the JarFile is properly closed. + * @param vdoc session Document handler + * @param appd the AppData whose block is being updated + * @param apdjar the new data in a Jar written by this class + */ + protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd, SessionFile apdjar) throws IOException { + if (apdjar==null || apdjar.sessionFile==null || !apdjar.sessionFile.exists()) { + throw new IOException("No temporary Appdata to recover and transfer."); + } + if (vdoc==null) { + log.fatal("FATAL! NO DOCUMENT TO WRITE TO!"); + throw new IOException("FATAL! NO DOCUMENT TO WRITE TO!"); + } + log.debug("Recovering AppData entry from "+apdjar.sessionFile); + JarInputStream istrm = new JarInputStream(apdjar.getBufferedInputStream(true)); + JarEntry je=null; + while (istrm.available()>0 && (je=istrm.getNextJarEntry())!=null && !je.getName().equals("appData_entry.dat")) { + if (je!=null) + log.debug("Ignoring extraneous entry "+je.getName()); + } + if (istrm.available()>0 && je!=null) { + log.debug("Found appData_entry.dat in Jar"); + String ref = appd.getDataReference(); + if (ref==null) { + throw new IOException("Null AppData.DataReference passed."); + } + if (vdoc.writeAppdataFromStream(ref, istrm)) { + log.debug("Entry updated successfully."); + } else { + throw new IOException("writeAppdataFromStream did not return true - expect future badness."); // LATER - verify why this might occur. + } + } else { + throw new IOException("Couldn't find appData_entry.dat in temporary jar file "+apdjar.sessionFile.getAbsolutePath()); + } + istrm.close(); + } + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#getClientOutputStream() + */ + public DataOutput getClientOutputStream() { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + if (log.isDebugEnabled()) + log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn()); + return _getAppdataOutputStream(false); + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#getUserOutputStream() + */ + public DataOutput getUserOutputStream() { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + if (log.isDebugEnabled()) + log.debug("trying to getUserOutputStream for (" + +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn()); + return _getAppdataOutputStream(true); + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#hasClientAppdata() + */ + public boolean hasClientAppdata() { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + extractAppData(clientdoc.getVamsasDocument()); + // LATER - check validity of a DataReference before we return true + if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null)) + return true; + return false; + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#hasUserAppdata() + */ + public boolean hasUserAppdata() { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + extractAppData(clientdoc.getVamsasDocument()); + // LATER - check validity of a DataReference before we return true + if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null)) + return true; + return false; + } + private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) { + try { + if (data!=null && data.length>0) + ostrm.write(data); + ostrm.closeEntry(); + return true; + } + catch (Exception e) { + log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e); + } + return false; + } + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#setClientAppdata(byte[]) + */ + public void setClientAppdata(byte[] data) { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + _getAppdataOutputStream(false); + if (newAppDataStream==null) { + // LATER: define an exception for this ? - operation may fail even if file i/o not involved + log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir); + } else { + _writeAppDataStream(newAppDataStream, data); + // LATER: deal with error case - do we make session read only, or what ? + } + } + + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#setUserAppdata(byte[]) + */ + public void setUserAppdata(byte[] data) { + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + _getAppdataOutputStream(true); + if (newUserDataStream==null) { + // LATER: define an exception for this ? - operation may fail even if file i/o not involved + log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir); + } else { + _writeAppDataStream(newUserDataStream, data); + // LATER: deal with error case - do we make session read only, or what ? + } + } + /** + * flush and close outstanding output streams. + * - do this before checking data length. + * @throws IOException + */ + protected void closeForWriting() throws IOException { + if (newAppDataStream!=null) { + newAppDataStream.flush(); + newAppDataStream.closeEntry(); + newAppDataStream.close(); + } + if (newUserDataStream!=null) { + newUserDataStream.flush(); + newUserDataStream.closeEntry(); + newUserDataStream.close(); + } + } + + + /** + * + * @return true if any AppData blocks have to be updated in session Jar + */ + protected boolean isModified() { + // LATER differentiate between core xml modification and Jar Entry modification. + if (newAppData.sessionFile.exists() || newUserData.sessionFile.exists()) + return true; + return false; + } + /* (non-Javadoc) + * @see java.lang.Object#finalize() + */ + protected void finalize() throws Throwable { + if (newAppDataStream!=null) { + newAppDataStream = null; + } + if (newAppDataStream!=null) { + newUserDataStream = null; + } + if (newAppData!=null) { + newAppData.eraseExistence(); + newAppData = null; + } + if (newUserData!=null) { + newUserData.eraseExistence(); + newUserData = null; + } + super.finalize(); + } + +} diff --git a/src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java b/src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java new file mode 100644 index 0000000..cd05e26 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java @@ -0,0 +1,262 @@ +/* +* VAMSAS Project +* + +* +* Dec 13, 2006 +* +*/ + +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; + +import java.io.IOException; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.client.ClientHandle; +import org.vamsas.client.IClient; +import org.vamsas.client.IClientFactory; +import org.vamsas.client.InvalidSessionUrnException; +import org.vamsas.client.NoDefaultSessionException; +import org.vamsas.client.SessionHandle; +import org.vamsas.client.UserHandle; + +/** + * + * + */ +public class SimpleClientFactory implements IClientFactory { + + private static Log log = LogFactory.getLog(SimpleClientFactory.class); + + private File sessionArena = null; + + private String vamsasSubdirectoryName = ".vamsas"; + + private SessionsFile sessionFile = null; + private static final String SESSION_LIST="sessions.obj"; + + //private String[] currentlyAvailableDessions = null; + + /** + * default constructor - called by CreateClientFactory only. + * + *Inits the sessionarena to the directory .vamsas of the user home directory. + * + */ + public SimpleClientFactory() throws IOException + { + // sessionArena + + //retrieves user home directory + String userHomeDirectory = System.getProperty("user.home"); + if (userHomeDirectory == null || userHomeDirectory.length()<1) + { + new IOException("Unable to detect user home directory"); + } + String sessionArenaPath = userHomeDirectory.concat(File.separator.concat(this.vamsasSubdirectoryName)); + + this.initSessionArena(sessionArenaPath); + this.initFactoryObjects(); + } + + + /** + * Create a client factory that works with sessions at the given + * path. + * @param path path to directory called session arena, where will be created session directories and session files. + */ + public SimpleClientFactory(String path) throws IOException + { + this.initSessionArena(path); + } + /** + * Inits sessionArena to a given path. + * checks if path is valid. + * + * @param path path to a directory to use + * @throws IOException if the path is incorrect + */ + private void initSessionArena (String path) throws IOException + { + // Check path is valid and read/writeable. + File arenaFile = new File (path); + if (!arenaFile.exists()) + { + if (! arenaFile.mkdirs()) + { + this.sessionArena = null; + throw(new IOException("Unable to create a directory called "+path)); + } + } + if (arenaFile.exists() && arenaFile.isDirectory() && arenaFile.canRead() && arenaFile.canWrite()) + { + this.sessionArena = arenaFile; + } + else + { + this.sessionArena = null; + throw(new IOException("Cannot read and write to a directory called "+path)); + } + } + + /** + * construct SessionFile objects and watchers for each + */ + private void initFactoryObjects() throws IOException { + if (this.sessionFile!=null ) + throw new IOException("initFactoryObjects called for initialised ClientFactory object."); + this.sessionFile = new SessionsFile(new File(this.sessionArena,SESSION_LIST)); + + } + /** + * @see org.vamsas.client.IClientFactory#getCurrentSessions() + */ + public String[] getCurrentSessions() + { + String[] sessions = null; + if (this.sessionFile!=null ) + { + SessionHandle[] sessionHandles = this.sessionFile.retrieveSessionsList(); + if (sessionHandles != null) + { + sessions = new String[sessionHandles.length]; + for (int i = sessionHandles.length -1; i > 0; i--) + { + SessionHandle sessionHandle = sessionHandles[i]; + sessions [i] = sessionHandle.getSessionUrn(); + } + } + } + return sessions; + } + + + private void discoverSession() + { + + } + + /** + * @see org.vamsas.client.IClientFactory#getIClient(org.vamsas.client.ClientHandle) + * + * Creates a IClient object, using default UserHandle with system variables:"user.name" or "USERNAME")), + "host.name" or "HOSTNAME" + */ + public IClient getIClient(ClientHandle applicationHandle) + throws NoDefaultSessionException { + + return this.getIClient(applicationHandle, (UserHandle) null); + } + + /** + * @see org.vamsas.client.IClientFactory#getIClient(org.vamsas.client.ClientHandle, java.lang.String) + */ + public IClient getIClient(ClientHandle applicationHandle, String sessionUrn) { + // TODO Auto-generated method stub + return null; + } + + /** + * @see org.vamsas.client.IClientFactory#getIClient(org.vamsas.client.ClientHandle, org.vamsas.client.UserHandle, java.lang.String) + */ + public IClient getIClient(ClientHandle applicationHandle, UserHandle userId, + String sessionUrn) { + // TODO Auto-generated method stub + return null; + } + + /** + * @see org.vamsas.client.IClientFactory#getIClient(org.vamsas.client.ClientHandle, org.vamsas.client.UserHandle) + */ + public IClient getIClient(ClientHandle applicationHandle, UserHandle userId) + throws NoDefaultSessionException { + SimpleClient client = null; + if (this.sessionArena==null) + throw new Error("Improperly initialised SimpleClientFactory object - null sessionArena."); + + ClientHandle clientHandle =applicationHandle; + //create default clientHandle with "SimpleVamsasClientApp","0.1", + if (clientHandle == null) + clientHandle = new ClientHandle("SimpleVamsasClientApp","0.1"); + + //check if any available session(s) + String[] availableSessions = this.getCurrentSessions(); + if (availableSessions != null) + {//there are available sessions + if (availableSessions.length>1) + {//more than one session if available... can not choose + + //represents list of session as String + StringBuffer sessionURNs = new StringBuffer(""); + for (int i = 0; i< availableSessions.length ; i++) + { + sessionURNs.append(availableSessions[i]+" "); + } + throw new NoDefaultSessionException("Several sessions available, please pick one: "+sessionURNs); + } + + //check if only one session available. if yes, open it + if (availableSessions.length == 1) + { + //only one session available, open it. + return this.getIClient(clientHandle, availableSessions[0]); + } + } + //no session available - create a new one + + + try + { + //create sessionDirectory + File sessdir = File.createTempFile("sess", ".simpleclient", this.sessionArena); + log.debug("Creating new session directory"); + if (!(sessdir.delete() && sessdir.mkdir())) + throw new IOException("Could not make session directory "+sessdir); + //create session + VamsasSession vamsasSession = new VamsasSession(sessdir); + + this.getSessionFile().addSession(new SessionHandle(new SessionUrn(vamsasSession).getSessionUrn()), false); + if (userId == null) + { + //create a default userHandle + //with current OS user and hostname + userId = new UserHandle(System.getProperty("user.name", System.getProperty("USERNAME","Joe Doe")), + System.getProperty("host.name",System.getProperty("HOSTNAME", "Unknown") ));// clientName, clientVersion, sessionPath); + } + + + //create simple client + client = new SimpleClient(userId, clientHandle, vamsasSession); + } + catch (IOException e) + { + log.error("error while creating new IClient",e); + } + catch (InvalidSessionUrnException e) + { + log.error("Unable to create new IClient. The session urn is incorrect ",e); + } + + return client; + } + + + /** + * @return the sessionFile + */ + private SessionsFile getSessionFile() throws IOException + { + if (this.sessionFile == null) + { + this.sessionFile = new SessionsFile( new File (this.sessionArena, SESSION_LIST)); + } + return this.sessionFile; + } + + + +} diff --git a/src/uk/ac/vamsas/client/simpleclient/SimpleDocBinding.java b/src/uk/ac/vamsas/client/simpleclient/SimpleDocBinding.java new file mode 100644 index 0000000..9eee917 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/SimpleDocBinding.java @@ -0,0 +1,138 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Vector; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.client.Vobject; +import org.vamsas.client.VorbaIdFactory; +import org.vamsas.client.VorbaXmlBinder; +import org.vamsas.objects.core.VAMSAS; +import org.vamsas.objects.core.VamsasDocument; +import org.vamsas.objects.utils.AppDataReference; +import org.vamsas.objects.utils.DocumentStuff; +import org.vamsas.objects.utils.ProvenanceStuff; +import org.vamsas.objects.utils.document.VersionEntries; + +/** + * Base class for SimpleClient Vamsas Document Object Manipulation + * holds static vamsasDocument from XML routines and + * state objects for a particular unmarshalled Document instance. + * @author jimp + */ + + +public class SimpleDocBinding { + + protected VorbaIdFactory vorba; + protected static Log log = LogFactory.getLog(SimpleDocBinding.class); + + /** + * @return Returns the vorba. + */ + public VorbaIdFactory getVorba() { + return vorba; + } + + /** + * @param vorba The vorba to set. + */ + public void setVorba(VorbaIdFactory vorba) { + this.vorba = vorba; + } + + /** + * Uses VorbaXmlBinder to retrieve the VamsasDocument from the given stream + */ + public VamsasDocument getVamsasDocument(VamsasArchiveReader oReader) throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + if (oReader!=null) { + // check the factory + if (vorba==null) { + log.error("Invalid SimpleDocument construction - no VorbaIdFactory defined!"); + return null; + } + + if (oReader.isValid()) { + // Read vamsasDocument.xsd instance + InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream()); + Object unmarsh[] = VorbaXmlBinder.getVamsasObjects(vdoc, vorba, new VamsasDocument()); + if (unmarsh==null) + log.fatal("Couldn't unmarshall document!"); + + Vobject vobjs = (Vobject) unmarsh[0]; + if (vobjs!=null) { + VamsasDocument doc=(VamsasDocument) vobjs; + if (doc!=null) + return doc; + } + log.debug("Found no VamsasDocument object in properly formatted Vamsas Archive."); + } else { + // deprecated data handler (vamsas.xsd instance) + InputStream vxmlis = oReader.getVamsasXmlStream(); + if (vxmlis!=null) { // Might be an old vamsas file. + BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream()); + InputStreamReader vxml = new InputStreamReader(ixml); + Object unmarsh[] = VorbaXmlBinder.getVamsasObjects(vxml, vorba, new VAMSAS()); + + if (unmarsh==null) + log.fatal("Couldn't unmarshall document!"); + + VAMSAS root[]= new VAMSAS[] { null}; + root[0] = (VAMSAS) unmarsh[0]; + + if (root[0]==null) { + log.debug("Found no VAMSAS object in VamsasXML stream."); + } else { + log.debug("Making new VamsasDocument from VamsasXML stream."); + VamsasDocument doc = DocumentStuff.newVamsasDocument(root, + ProvenanceStuff.newProvenance( + vorba.getUserHandle().getFullName(), + "Vamsas Document constructed from vamsas.xml"), VersionEntries.ALPHA_VERSION); + // VAMSAS: decide on 'system' operations provenance form + // LATER: implement classes for translating Vorba properties into provenance user fields. + // VAMSAS: decide on machine readable info embedding in provenance should be done + root[0]=null; + root=null; + return doc; + } + } + } + } + // otherwise - there was no valid original document to read. + return null; + } + + /** + * Extract all jarEntries in an archive referenced by the vamsas document + * LATER: a family of methods for finding extraneous jarEntries , and invalid appDataReferences + * @param doc + * @param oReader + * @return array of the subset of JarEntry names that are referenced in doc + */ + public Vector getReferencedEntries(VamsasDocument doc, VamsasArchiveReader oReader) { + if (oReader==null) + return null; + if (doc==null) { + try { doc = getVamsasDocument(oReader); } + catch (Exception e) { log.warn("Failed to get document from "+oReader.jfile.getName()); }; + } + Vector docrefs = AppDataReference.getAppDataReferences(doc); + if (docrefs==null) + return null; + Vector entries = oReader.getExtraEntries(); + if (entries!=null && docrefs.size()>0) { + int i=0, j=entries.size(); + do { + if (!docrefs.contains(entries.get(i))) { + entries.remove(i); + j--; + } else + i++; + } while (iarchive + * @throws IOException + */ + public VamsasArchive(File archive, boolean overwrite, boolean vamsasdocument, SessionFile extantLock) throws IOException { + super(); + if (archive==null || (archive!=null && !(archive.getAbsoluteFile().getParentFile().canWrite() && (!archive.exists() || archive.canWrite())))) { + log.fatal("Expect Badness! -- Invalid parameters for VamsasArchive constructor:"+((archive!=null) + ? "File cannot be overwritten." : "Null Object not valid constructor parameter")); + return; + } + + this.vamsasdocument = vamsasdocument; + if (archive.exists() && !overwrite) { + this.original = archive; + if (extantLock!=null) { + this.odoclock = extantLock; + if (odoclock.fileLock==null || !odoclock.fileLock.isLocked()) + odoclock.lockFile(); + } else { + this.odoclock = new SessionFile(archive); + } + odoclock.lockFile(); // lock the file *immediatly* + this.archive = null; // archive will be a temp file when the open method is called + virginArchive=false; + try { + this.accessOriginal(); + } catch (IOException e) { + throw new IOException("Lock failed for existing archive"+archive); + } + } else { + this.original = null; + this.archive = archive; // archive is written in place. + if (extantLock!=null) + rchive=extantLock; + else + rchive = new SessionFile(archive); + rchive.lockFile(); + if (rchive.fileLock==null || !rchive.fileLock.isLocked()) + throw new IOException("Lock failed for new archive"+archive); + rchive.fileLock.getRaFile().setLength(0); // empty the archive. + virginArchive = true; + } + this.openArchive(); // open archive + } + /** + * open original archive file for exclusive (locked) reading. + * @throws IOException + */ + private void accessOriginal() throws IOException { + if (original!=null && original.exists()) { + if (odoclock==null) + odoclock = new SessionFile(original); + odoclock.lockFile(); + if (odoc == null) + odoc = new VamsasArchiveReader(original); + // this constructor is not implemented yet odoc = new VamsasArchiveReader(odoclock.fileLock); + } + } + + /** + * Add unique entry strings to internal JarEntries list. + * @param entry + * @return true if entry was unique and was added. + */ + private boolean addEntry(String entry) { + if (entries!=null) + entries=new Hashtable(); + if (entries.containsKey(entry)) + return false; + entries.put(entry, new Integer(entries.size())); + return true; + } + /** + * adds named entry to newarchive or returns false. + * @param entry + * @return true if entry was unique and could be added + * @throws IOException if entry name was invalid or a new entry could not be made on newarchive + */ + private boolean addValidEntry(String entry) throws IOException { + JarEntry je = new JarEntry(entry); + if (!addEntry(entry)) + return false; + newarchive.flush(); + newarchive.putNextEntry(je); + return true; + } + /** + * called by app to get name of backup if it was made. + * If this is called, the caller app *must* delete the backup themselves. + * @return null or a valid file object + */ + public File backupFile() { + + if (!virginArchive) { + makeBackup(); + donotdeletebackup=false; // external reference has been made. + return ((original!=null) ? originalBackup : null); + } + return null; + } + + /** + * Stops any current write to archive, and reverts to the backup if it exists. + * All existing locks on the original will be released. All backup files are removed. + */ + public boolean cancelArchive() { + if (newarchive!=null) { + try { + newarchive.closeEntry(); + newarchive.putNextEntry(new JarEntry("deleted")); + newarchive.closeEntry(); + newarchive.close(); + + } catch (Exception e) { + log.debug("Whilst closing newarchive",e); + }; + if (!virginArchive) { + // then there is something to recover. + try { + recoverBackup(); + } + catch (Exception e) { + log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e); + return false; + } + } + + } else { + log.warn("Client Error: cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!"); + } + closeAndReset(); // tidy up and release locks. + return true; + } + + /** + * only do this if you want to destroy the current file output stream + * + */ + private void closeAndReset() { + if (rchive!=null) { + rchive.unlockFile(); + rchive=null; + } + if (original!=null) { + if (odoc!=null) { + odoc.close(); + odoc=null; + } + if (archive!=null) + archive.delete(); + if (odoclock!=null) { + odoclock.unlockFile(); + odoclock = null; + } + } + removeBackup(); + newarchive=null; + original=null; + entries=null; + } + /** + * Tidies up and closes archive, removing any backups that were created. + * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile() + * TODO: ensure all extant AppDataReference jar entries are transferred to new Jar + * TODO: provide convenient mechanism for generating new unique AppDataReferences and adding them to the document + */ + public void closeArchive() throws IOException { + if (newarchive!=null) { + newarchive.flush(); + newarchive.closeEntry(); + if (!isDocumentWritten()) + log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written."); + newarchive.finish();// close(); // use newarchive.finish(); for a stream IO + newarchive.flush(); + // + updateOriginal(); + closeAndReset(); + } else { + log.warn("Attempt to close archive that has not been opened for writing."); + } + } + /** + * Opens and returns the applicationData output stream for the appdataReference string. + * @param appdataReference + * @return Output stream to write to + * @throws IOException + */ + public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException { + if (newarchive==null) + throw new IOException("Attempt to write to closed VamsasArchive object."); + if (addValidEntry(appdataReference)) { + return new AppDataOutputStream(newarchive); + } + return null; + } + + /** + * + * @return JarEntry name for the vamsas XML stream in this archive + */ + protected String getDocumentJarEntry() { + if (vamsasdocument) + return VamsasArchiveReader.VAMSASDOC; + return VamsasArchiveReader.VAMSASXML; + } + /** + * Safely initializes the VAMSAS XML document Jar Entry. + * @return Writer to pass to the marshalling function. + * @throws IOException if a document entry has already been written. + */ + public PrintWriter getDocumentOutputStream() throws IOException { + if (newarchive==null) + openArchive(); + if (!isDocumentWritten()) { + try { + if (addValidEntry(getDocumentJarEntry())) + return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8")); + } catch (Exception e) { + log.warn("Problems opening XML document JarEntry stream",e); + } + } else { + throw new IOException("Vamsas Document output stream is already written."); + } + return null; + } + + /** + * Access original archive if it exists, pass the reader to the client + * Note: this is NOT thread safe and a call to closeArchive() will by necessity + * close and invalidate the VamsasArchiveReader object. + * @return null if no original archive exists. + */ + public VamsasArchiveReader getOriginalArchiveReader() throws IOException { + if (!virginArchive) { + accessOriginal(); + return odoc; + } + return null; + } + /** + * returns original document's root vamsas elements. + * @return + * @throws IOException + * @throws org.exolab.castor.xml.MarshalException + * @throws org.exolab.castor.xml.ValidationException + */ + public Vobject[] getOriginalRoots() throws IOException, + org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + return VamsasArchive.getOriginalRoots(this); + } + /** + * @return original document or a new empty document (with default provenance) + * @throws IOException + * @throws org.exolab.castor.xml.MarshalException + * @throws org.exolab.castor.xml.ValidationException + */ + public VamsasDocument getVamsasDocument() throws IOException, + org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + return getVamsasDocument("org.vamsas.simpleclient.VamsasArchive", "Created new empty document", null); + } + /** + * Return the original document or a new empty document with initial provenance entry. + * @param provenance_user (null sets user to be the class name) + * @param provenance_action (null sets action to be 'created new document') + * @param version (null means use latest version) + * @return (original document or a new vamsas document with supplied provenance and version info) + * @throws IOException + * @throws org.exolab.castor.xml.MarshalException + * @throws org.exolab.castor.xml.ValidationException + */ + public VamsasDocument getVamsasDocument(String provenance_user, String provenance_action, String version) throws IOException, + org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + if (_doc!=null) + return _doc; + _doc = getOriginalVamsasDocument(this, getVorba()); + if (_doc!=null) + return _doc; + // validate parameters + if (provenance_user==null) + provenance_user = "org.vamsas.simpleclient.VamsasArchive"; + if (provenance_action == null) + provenance_action="Created new empty document"; + if (version==null) + version = VersionEntries.latestVersion(); + // Create a new document and return it + _doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()}, + ProvenanceStuff.newProvenance(provenance_user, provenance_action), version); + return _doc; + } + /** + * @return Returns the current VorbaIdFactory for the archive. + */ + public VorbaIdFactory getVorba() { + if (vorba==null) + vorba = new SimpleDocument("simpleclient.VamsasArchive"); + return vorba.getVorba(); + } + /** + * @return true if Vamsas Document has been written to archive + */ + protected boolean isDocumentWritten() { + if (newarchive==null) + log.warn("isDocumentWritten() called for unopened archive."); + if (entries!=null) { + if (entries.containsKey(getDocumentJarEntry())) + return true; + } + return false; + } + private void makeBackup() { + if (!virginArchive) { + if (originalBackup==null && original!=null && original.exists()) { + try { + accessOriginal(); + originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile()); + } + catch (IOException e) { + log.warn("Problem whilst making a backup of original archive.",e); + } + } + } + } + /** + * opens the new archive ready for writing. If the new archive is replacing an existing one, + * then the existing archive will be locked, and the new archive written to a temporary file. + * The new archive will be put in place once close() is called. + * @param doclock LATER - pass existing lock on document, if it exists.... no need yet? + * @throws IOException + */ + private void openArchive() throws IOException { + + if (newarchive!=null) { + log.warn("openArchive() called multiple times."); + throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open."); + } + if (archive==null && (virginArchive || original==null)) { + log.warn("openArchive called on uninitialised VamsasArchive object."); + throw new IOException("Badly initialised VamsasArchive object - no archive file specified."); + } + if (!virginArchive) { + // lock the original + accessOriginal(); + // make a temporary file to write to + archive = File.createTempFile(original.getName(), ".new",original.getParentFile()); + } else { + if (archive.exists()) + log.warn("New archive file name already in use! Possible lock failure imminent?"); + } + + if (rchive==null) + rchive = new SessionFile(archive); + if (!rchive.lockFile()) + throw new IOException("Failed to get lock on file "+archive); + // LATER: locked IO stream based access. + Manifest newmanifest = new Manifest(); + newarchive = new JarOutputStream(rchive.fileLock.getBufferedOutputStream(true), newmanifest); + //newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive))); + entries = new Hashtable(); + } + public void putVamsasDocument(VamsasDocument doc) throws IOException, + org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + putVamsasDocument(doc, getVorba()); + } + /** + * + * @param doc + * @param vorba + * @return (vorbaId string, Vobjhash) pairs for last hash of each object in document + * @throws IOException + * @throws org.exolab.castor.xml.MarshalException + * @throws org.exolab.castor.xml.ValidationException + */ + public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba) throws IOException, + org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { + if (vamsasdocument) + doc.setVersion(VersionEntries.latestVersion()); // LATER: ensure this does the correct thing. + VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc); + } + + /** + * recovers the original file's contents from the (temporary) backup. + * @throws Exception if any SessionFile or file removal operations fail. + */ + private void recoverBackup() throws Exception { + if (originalBackup!=null) { + // backup has been made. + // revert from backup and delete it (changing backup filename) + if (rchive==null) { + rchive = new SessionFile(original); + } + SessionFile bckup = new SessionFile(originalBackup); + + rchive.updateFrom(null, bckup); // recover from backup file. + bckup.unlockFile(); + bckup=null; + removeBackup(); + } + } + + /** + * forget about any backup that was made - removing it first if it was only temporary. + */ + private void removeBackup() { + if (originalBackup!=null) { + log.debug("Removing backup in "+originalBackup.getAbsolutePath()); + if (!donotdeletebackup) + if (!originalBackup.delete()) + log.info("VamsasArchive couldn't remove temporary backup "+originalBackup.getAbsolutePath()); + originalBackup=null; + } + } + /** + * @param vorba the VorbaIdFactory to use for accessing vamsas objects. + */ + public void setVorba(VorbaIdFactory Vorba) { + if (Vorba!=null) { + if (vorba==null) + vorba = new SimpleDocument(Vorba); + else + vorba.setVorba(Vorba); + } else + getVorba(); + } + /** + * Convenience method to copy over the referred entry from the backup to the new version. + * Warning messages are raised if no backup exists or the + * entry doesn't exist in the backed-up original. + * Duplicate writes return true - but a warning message will also be raised. + * @param AppDataReference + * @return true if AppDataReference now exists in the new document + * @throws IOException + */ + public boolean transferAppDataEntry(String AppDataReference) throws IOException { + return transferAppDataEntry(AppDataReference, AppDataReference); + } + /** + * Validates the AppDataReference: not null and not already written to archive. + * @param AppDataReference + * @return true if valid. false if not + * @throws IOException for really broken references! + */ + protected boolean _validNewAppDataReference(String newAppDataReference) throws IOException { + // LATER: Specify valid AppDataReference form in all VamsasArchive handlers + if (newAppDataReference==null) + throw new IOException("null newAppDataReference!"); + if (entries.containsKey(newAppDataReference)) { + log.warn("Attempt to write '"+newAppDataReference+"' twice! - IGNORED"); + // LATER: fix me? warning message should raise an exception here. + return false; + } + return true; + } + /** + * Transfers an AppDataReference from old to new vamsas archive, with a name change. + * @see transferAppDataEntry(String AppDataReference) + * @param AppDataReference + * @param NewAppDataReference - AppDataReference in new Archive + * @return + * @throws IOException + */ + public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException { + if (original==null || !original.exists()) { + log.warn("No backup archive exists."); + return false; + } + if (AppDataReference==null) + throw new IOException("null AppDataReference!"); + + if (!_validNewAppDataReference(NewAppDataReference)) + return false; + + accessOriginal(); + + java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference); + + if (adstream==null) { + log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive."); + return false; + } + + java.io.OutputStream adout = getAppDataStream(NewAppDataReference); + // copy over the bytes + int written=-1; + long count=0; + byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer + do { + if ((written = adstream.read(buffer))>-1) { + adout.write(buffer, 0, written); + log.debug("Transferring "+written+"."); + count+=written; + } + } while (written>-1); + log.debug("Sucessfully transferred AppData for '" + +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)"); + return true; + } + /** + * write data from a stream into an appData reference. + * @param AppDataReference - New AppDataReference not already written to archive + * @param adstream Source of data for appData reference - read until .read(buffer) returns -1 + * @return true on success. + * @throws IOException for file IO or invalid AppDataReference string + */ + public boolean writeAppdataFromStream(String AppDataReference, java.io.InputStream adstream) throws IOException { + if (!_validNewAppDataReference(AppDataReference)) { + log.warn("Invalid AppDataReference passed to writeAppdataFromStream"); + throw new IOException("Invalid AppDataReference! (null, or maybe non-unique)!"); + } + + if (AppDataReference==null) { + log.warn("null appdata passed."); + throw new IOException("Null AppDataReference"); + } + + java.io.OutputStream adout = getAppDataStream(AppDataReference); + // copy over the bytes + int written=-1; + long count=0; + byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer + do { + if ((written = adstream.read(buffer))>-1) { + adout.write(buffer, 0, written); + log.debug("Transferring "+written+"."); + count+=written; + } + } while (written>-1); + return true; + } + /** + * transfers any AppDataReferences existing in the old document + * that haven't already been transferred to the new one + * LATER: do the same for transfers requiring a namechange - more document dependent. + * @return true if data was transferred. + */ + public boolean transferRemainingAppDatas() throws IOException { + boolean transfered=false; + if (original==null || !original.exists()) { + log.warn("No backup archive exists."); + return false; + } + accessOriginal(); + + if (getVorba()!=null) { + Vector originalRefs=null; + try { + originalRefs = vorba.getReferencedEntries(getVamsasDocument(), getOriginalArchiveReader()); + } catch (Exception e) { + log.warn("Problems accessing original document entries!",e); + } + if (originalRefs!=null) { + Iterator ref = originalRefs.iterator(); + while (ref.hasNext()) { + String oldentry = (String) ref.next(); + if (oldentry!=null && !entries.containsKey(oldentry)) { + log.debug("Transferring remaining entry '"+oldentry+"'"); + transfered |= transferAppDataEntry(oldentry); + } + } + } + } + return transfered; + } + /** + * called after archive is written to put file in its final place + */ + private void updateOriginal() { + if (!virginArchive) { + // make sure original document really is backed up and then overwrite it. + if (odoc!=null) { + // try to shut the odoc reader. + odoc.close(); + odoc = null; + } + // Make a backup if it isn't done already + makeBackup(); + try { + // copy new Archive data that was writen to a temporary file + odoclock.updateFrom(null, rchive); + } + catch (IOException e) { + // LATER: decide if leaving nastily named backup files around is necessary. + File backupFile=backupFile(); + if (backupFile!=null) + log.error("Problem updating archive from temporary file! - backup left in '" + +backupFile().getAbsolutePath()+"'",e); + else + log.error("Problems updating, and failed to even make a backup file. Ooops!", e); + } + // Tidy up if necessary. + removeBackup(); + } else { + + + } + } +} diff --git a/src/uk/ac/vamsas/client/simpleclient/VamsasArchiveReader.java b/src/uk/ac/vamsas/client/simpleclient/VamsasArchiveReader.java new file mode 100644 index 0000000..e8df789 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/VamsasArchiveReader.java @@ -0,0 +1,313 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.RandomAccessFile; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.vamsas.objects.utils.document.VersionEntries; +/** + * Basic methods for accessing an existing Vamsas Archive, + * and Jar entry names for creating new vamsas archives. + * + * @author jimp + * + */ +public class VamsasArchiveReader { + private static Log log = LogFactory.getLog(VamsasArchiveReader.class); + JarFile jfile=null; + boolean stream=false; // true if we are seeking on the stream. + RandomAccessFile rfile; + ZipInputStream jstream=null; + Hashtable strmentries = null; + private void streamInit() { + //throw new Error("VamsasArchiveReader(Stream) Not implemented!"); + if (!stream) { + log.debug("Skipping init for Jar Stream input."); + return; + } + strmentries = new Hashtable(); + log.debug("Jar Stream input Initialisation"); + try { + rfile.seek(0); + // no buffering - we need to be able to move around the random access stream. + jstream = new ZipInputStream(new FileInputStream(rfile.getFD())); // no manifest (probably) + if (jstream.available()==0) + log.warn("Can't read from JarInputStream (Locked stream!)"); + ZipEntry entry=null; + long pos=0; + do { + if ((entry=jstream.getNextEntry())!=null) { + if (strmentries.containsKey(entry.getName())) { + log.info("Only recording last of duplicate entries '"+entry.getName()+"'"); + } + strmentries.put(entry.getName(), new Long(pos++)); + jstream.closeEntry(); + } + } while (entry!=null); + } + catch (Exception e) { + log.warn("Exceptions during init!",e); + jstream=null; + } + } + + public VamsasArchiveReader(File vamsasfile) { + jfile=null; + if (vamsasfile.exists()) { + try { + jfile=new JarFile(vamsasfile); + } + catch (Exception e) { + log.debug("non-serious? couldn't open new JarFile on "+vamsasfile,e); + jfile=null; + } + } + + } + /** + * in an ideal world - this constructor will create a reader object + * for the locked file's random access stream. + * + * @param vamsaslock + */ + public VamsasArchiveReader(Lock vamsaslock) { + // LATER: implement or remove + if (vamsaslock==null || !vamsaslock.isLocked()) + throw new Error("IMPLEMENTATION ERROR: Cannot create a VamsasArchiveReader without a valid lock."); + // throw new Error("VamsasArchiveReading from locked IO stream not yet implemented."); + try { + rfile = vamsaslock.getRaFile(); + } catch (Exception e) { + log.warn("Unexpected IO Exception when accessing locked vamsas archive stream "+vamsaslock.target,e); + } + stream = true; + streamInit(); + if (jstream==null) + throw new Error("Failed to open archive from Locked random access stream."); + } + + /** + * the vamsas document version(s) handled by this Reader + */ + final public static String DOCUMENT_VERSION=VersionEntries.BETA_VERSION; + /** + * name of the jarEntry containing a well formatted vamsas XML Document + */ + + final public static String VAMSASDOC="vamsasDocument.xml"; + + /** + * name of the jarEntry containing a root VAMSAS element, and containing a + * random sequence of VAMSAS DataSet elements + */ + + final public static String VAMSASXML="vamsas.xml"; + /** + * seeks jstream to the given entry name and reads it. + * @param entryname + * @return + */ + private JarEntry seekEntry(String entryname) { + if (jstream==null) + return null; + if (!strmentries.containsKey(entryname)) + return null; + Long entrypos = (Long) strmentries.get(entryname); + if (entrypos==null) { + log.error("Null entry position for "+entryname); + return null; + } + try { + jstream=null; + rfile.seek(0); + jstream = new ZipInputStream(new FileInputStream(rfile.getFD())); + ZipEntry entry = null; + long epos = entrypos.longValue(); + do { + entry = jstream.getNextEntry(); + } while (entry!=null && --epos>=0); + // rfile.seek(entrypos.longValue()); + // make a Jar entry from a zip entry. + return new JarEntry(entry); + } + catch (Exception e) { + log.warn("Whilst seeking for "+entryname, e); + } + return null; + } + /** + * + * @return JarEntry for VamsasArchiveReader.VAMSASDOC + */ + protected JarEntry getVamsasDocumentEntry() { + return getJarEntry(VAMSASDOC); + } + /** + * + * @return JarEntry for VamsasArchiveReader.VAMSASXML + */ + protected JarEntry getVamsasXmlEntry() { + return getJarEntry(VAMSASXML); + } + /** + * Test for valid vamsas document archive + * @return true if getVamsasDocumentStream will return a stream likely to contain valid XML + */ + public boolean isValid() { + // TODO: check if VAMSASDOC is well formed (follows www.vamsas.ac.uk/schemas/vamsasDocument.xsd) and all appData references are resolvable - preferably as jar entries + if (jfile!=null || jstream!=null) + return (getVamsasDocumentEntry()!=null); + return false; + } + + + protected JarEntry getAppdataEntry(String AppdataRef) { + JarEntry entry; + if ((jfile==null && jstream==null) || !isValid() || (entry=getJarEntry(AppdataRef))==null) + return null; + + return entry; + } + + public InputStream getAppdataStream(String AppdataRef) { + JarEntry entry=getAppdataEntry(AppdataRef); + try { + if (entry!=null) + return getInputStream(entry); + } catch (IOException e) { + log.error("Failed when opening AppdataStream for "+AppdataRef, e); + } + return null; + } + /** + * get the VamsasDocument input stream, if it exists. + * @return null or valid input stream + */ + public InputStream getVamsasDocumentStream() { + InputStream vdoc; + if ((jfile==null && jstream==null) || !isValid()) + return null; + try { + vdoc = getInputStream(getVamsasDocumentEntry()); + } catch (IOException e) { + log.error("Whilst geting document stream",e); + vdoc=null; + } + return vdoc; + } + + /** + * get the VamsasXML input stream, if it exists. + * Note: Deprecated beyond our prealpha testing. + * @return null or valid input stream. + */ + + public InputStream getVamsasXmlStream() { + // log.warn("Deprecated call"); + JarEntry xmle=getVamsasXmlEntry(); + InputStream vdoc; + if (xmle==null) + return null; + try { + vdoc = getInputStream(xmle); + } catch (IOException e) { + log.error("Whilst getting VamsasXmlStream",e); + vdoc=null; + } + return vdoc; + } + + /** + * silently close the jar file. + * + */ + public void close() { + if (jfile!=null) { + try { + jfile.close(); + } catch (IOException e) { + log.error("Whilst closing JarFile "+jfile.getName(), e); + } + } + if (jstream!=null) { + try { + jstream.closeEntry(); + jstream=null; + // LATER: reference counting for random access file instances is necessary. + } + catch (Exception e) { + log.error("Whilst finishing reading from jar input stream",e); + } + } + } + + /** + * returns all entries not matching the filespec of a vamsas xml entry + * @return array of entries. + */ + public Vector getExtraEntries() { + if ((jfile==null && jstream==null)|| !isValid()) + return null; + Vector e = new Vector(); + if (jstream!=null) { + Enumeration entries = strmentries.keys(); + if (entries!=null && entries.hasMoreElements()) { + do { + JarEntry el = (JarEntry) entries.nextElement(); + if (!el.getName().equals(VAMSASDOC) && !el.getName().equals(VAMSASXML)) + e.add(new String(el.getName())); // avoid references + } while (entries.hasMoreElements()); + } + } else { + Enumeration entries = jfile.entries(); + if (entries!=null && entries.hasMoreElements()) { + do { + JarEntry el = (JarEntry) entries.nextElement(); + if (!el.getName().equals(VAMSASDOC) && !el.getName().equals(VAMSASXML)) + e.add(new String(el.getName())); // avoid references + } while (entries.hasMoreElements()); + } + return e; + } + return null; + } + + /* (non-Javadoc) + * @see java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry) + */ + private InputStream getInputStream(ZipEntry ze) throws IOException { + if (jfile!=null) + return jfile.getInputStream(ze); + if (jstream!=null) { + seekEntry(ze.getName()); + return new AppDataInputStream(jstream); + } + return null; + } + + /* (non-Javadoc) + * @see java.util.jar.JarFile#getJarEntry(java.lang.String) + */ + private JarEntry getJarEntry(String name) { + if (jfile!=null) + return jfile.getJarEntry(name); + if (jstream!=null) + return seekEntry(name); + return null; + } + } diff --git a/src/uk/ac/vamsas/client/simpleclient/VamsasFile.java b/src/uk/ac/vamsas/client/simpleclient/VamsasFile.java new file mode 100644 index 0000000..c30e5e6 --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/VamsasFile.java @@ -0,0 +1,100 @@ + +package uk.ac.vamsas.client.simpleclient; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.Timer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; + +import org.vamsas.objects.core.LockFileDescriptor; + +/** + * low level vamsas document management routines + * analogous to ClientsFile + * Grew out of io tests on VamsasArchive class in org.vamsas.test.simpleclient.VamsasArchive + * This class is not thread safe. + * @author jimp + * + */ +public class VamsasFile extends SessionFile { + /** + * + * Connect to an existing Vamsas document in a given sessionDir + * or create a new one. + * + * @param sessionDir + * @throws java.io.IOException + */ + public VamsasFile(File sessionFile) throws java.io.IOException { + super(sessionFile); + } + /** + * + * @return the VamsasFile + */ + public File getVamsasFile() { + return sessionFile; + } + /** + * Expand a previously stored session into the sessionDir + * @param sessionDir + * @param storedSession + + public VamsasFile(File sessionDir, JarFile storedSession) throws IOException { + // check if sessionDir is live or not + if (!sessionDir.exists()) { + sessionDir.mkdir(); + } + + { + // check its actually a writable directory + } + + File sfile = new File(sessionDir, "vamsas.jar"); + VamsasFile(sfile); + // if live - try to merge storedSession with sessionDir + // - will probably fail through duplicate Vobject references needing to be dereferenced. + // TODO: think of a way of specifying vorba_id scope for an application's references to allow merging of one vamsasDocument with another. + + } + */ + /** + * public interface for getting a lock. + * The lock object is internally referenced + * so the lock will persist even after the + * return value of the method goes out of scope. + * @return null if lock couldn't be got or a valid Lock object. + */ + public Lock getLock() { + if (lockFile()) + return fileLock; + return null; + } + /** + * + * @param extantLock + * @return null, extantLock or new Lock. + */ + public Lock getLock(Lock extantLock) { + if (lockFile(extantLock)) + return fileLock; + return null; + } + /** + * explicitly unlocks vamsas file. + * if you have called getLock() you *must* + * call this to release the lock. + */ + public void unLock() { + this.unlockFile(); + } + +} diff --git a/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java b/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java new file mode 100644 index 0000000..d230ead --- /dev/null +++ b/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java @@ -0,0 +1,312 @@ +package uk.ac.vamsas.client.simpleclient; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.RandomAccessFile; +import java.io.Writer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.log4j.Appender; +import org.apache.log4j.Logger; +import org.apache.log4j.FileAppender; +import org.vamsas.client.ClientHandle; +import org.vamsas.client.UserHandle; +/** + * Does all the IO operations for a SimpleClient instance accessing + * a SimpleClient vamsas session. + * + * Basically, it defines the various standard names for the files + * in the session directory (that maps to the sessionUrn), + * provides constructors for the file handlers and watchers of + * those file entities, and some higher level methods + * to check and change the state flags for the session. + * + * TODO: move the stuff below to the SimpleClientFactory documentation. + * much may not be valid now : + * Vamsas client is intialised with a path to create live session directories. + * This path may contain a vamsas.properties file + * that sets additional parameters (otherwise client + * just uses the one on the classpath). + * + * A vamsas session consists of : + * SessionDir - translates to urn of a live session. + * Contains: Vamsas Document (as a jar), Session client list file, + * both of which may be locked, and additional + * temporary versions of these files when write + * operations are taking place. + * + * Zip file entries + * - vamsasdocument.xml : core info + * one or more: + * - .version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry) + * + * Lockfile + * - filename given in the vamsasdocument.xml. Should be checked for validity by any client and rewritten if necessary. + * The lockfile can point to the jar itself. + * Mode of operation. + * Initially - documentHandler either: + * - creates a zip for a new session for the client + * - connect to an existing session zip + * 1. reads session urn file + * 2. waits for lock + * 3. examines session - decide whether to create new application data slice or connect to one stored in session. + * 4. writes info into session file + * 5. releases lock and generates local client events. + * 6. Creates Watcher thread to generate events. + * + * During the session + * - Update watcher checks for file change - + * + * Procedures for file based session message exchange + * - session document modification flag + * + */ + +public class VamsasSession { + /** + * indicator file for informing other processes that + * they should finalise their vamsas datasets for + * storing into a vamsas archive. + */ + public static final String CLOSEANDSAVE_FILE="stored.log"; + /** + * session file storing the last_stored_stat data + */ + public static final String MODIFIEDDOC_FILE="modified"; + + /** + * called to clear update flag after a successful offline storage event + */ + protected void clearUnsavedFlag() { + SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE)); + if (!laststored.clearFlag()) + log.warn("Unsaved flag was not cleared for "+sessionDir); + } + /** + * called to indicate session document has been modified. + * + */ + protected void setUnsavedFlag() { + SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE)); + if (!laststored.setFlag()) + log.warn("Couldn't set the Unsaved flag for "+sessionDir); + } + /** + * + * @return true if session document has been modified since last offline storage event + */ + protected boolean getUnsavedFlag() { + SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE)); + return laststored.checkFlag(); + } + /** + * log file location + */ + public static final String SESSION_LOG="Log.txt"; + private static Log log = LogFactory.getLog(VamsasSession.class); + protected Logger slog = Logger.getLogger("org.vamsas.client.SessionLog"); + /** + * setup the sessionLog using Log4j. + * @throws IOException + */ + private void initLog() throws IOException { + // LATER: make dedicated appender format for session log. + Appender app = slog.getAppender("SESSION_LOG"); + slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath())); + } + + /** + * the sessionDir is given as the session location for new clients. + */ + protected File sessionDir; + /** + * holds the list of attached clients + */ + ClientsFile clist; + public static final String CLIENT_LIST="Clients.obj"; + /** + * holds the data + */ + VamsasFile vamArchive; + public static final String VAMSAS_OBJ="VamDoc.jar"; + + /** + * sets up the vamsas session files and watchers in sessionDir + * @param sessionDir + */ + protected VamsasSession(File sessionDir) throws IOException { + if (sessionDir==null) + throw new Error("Null directory for VamsasSession."); + if (sessionDir.exists()) { + if (!sessionDir.isDirectory() || !sessionDir.canWrite() || !sessionDir.canRead()) + throw new IOException("Cannot access '"+sessionDir+"' as a read/writable Directory."); + if (checkSessionFiles(sessionDir)) { + // session files exist in the directory + this.sessionDir = sessionDir; + initSessionObjects(); + slog.debug("Initialising additional VamsasSession instance"); + log.debug("Attached to VamsasSession in "+sessionDir); + } + } else { + // start from scratch + if (!sessionDir.mkdir()) + throw new IOException("Failed to make VamsasSession directory in "+sessionDir); + this.sessionDir = sessionDir; + createSessionFiles(); + initSessionObjects(); + slog.debug("Session directory created."); + log.debug("Initialised VamsasSession in "+sessionDir); + } + } + /** + * tests presence of existing sessionfiles files in dir + * @param dir + * @return + */ + private boolean checkSessionFiles(File dir) throws IOException { + File c_file = new File(dir,CLIENT_LIST); + File v_doc = new File(dir,VAMSAS_OBJ); + if (c_file.exists() && v_doc.exists()) + return true; + return false; + } + /** + * create new empty files in dir + * + */ + private void createSessionFiles() throws IOException { + if (sessionDir==null) + throw new IOException("Invalid call to createSessionFiles() with null sessionDir"); + File c_file = new File(sessionDir,CLIENT_LIST); + File v_doc = new File(sessionDir,VAMSAS_OBJ); + if (c_file.createNewFile()) + log.debug("Created new ClientFile "+c_file); // don't care if this works or not + if (v_doc.createNewFile()) + log.debug("Created new Vamsas Session Document File "+v_doc); + } + /** + * construct SessionFile objects and watchers for each + */ + private void initSessionObjects() throws IOException { + if (clist!=null || vamArchive!=null) + throw new IOException("initSessionObjects called for initialised VamsasSession object."); + clist = new ClientsFile(new File(sessionDir,CLIENT_LIST)); + vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ)); + initLog(); + } + /** + * make a new watcher object for the clientFile + * @return new ClientFile watcher instance + */ + public FileWatcher getClientWatcher() { + return new FileWatcher(clist.sessionFile); + } + FileWatcher session_doc_watcher=null; + /** + * make a new watcher object for the vamsas Document + * @return new ClientFile watcher instance + */ + public FileWatcher getDocWatcher() { + if (session_doc_watcher==null) + return session_doc_watcher = new FileWatcher(vamArchive.sessionFile); + return new FileWatcher(vamArchive.sessionFile); + } + FileWatcher store_doc_file=null; + /** + * make a new watcher object for the messages file + * (thread safe - keeps a reference to the first watcher) + * @return new watcher instance + */ + public FileWatcher getStoreWatcher() { + if (store_doc_file==null) + return store_doc_file = new FileWatcher(new File(CLOSEANDSAVE_FILE)); + return new FileWatcher(new File(CLOSEANDSAVE_FILE)); + + } + /** + * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made. + * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered. + * @param client + * @param user + * @return + */ + public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException { + SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE)); + while (!sfw.lockFile()) + log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE); + RandomAccessFile sfwfile=sfw.fileLock.getRaFile(); + sfwfile.setLength(0); // wipe out any old info. + // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?) + sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization()); + sfw.unlockFile(); + if (store_doc_file!=null) + store_doc_file.setState(); + slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+""); + } + /** + * create a new session with an existing vamsas Document - by copying it into the session. + * @param archive + */ + public void setVamsasDocument(File archive) throws IOException { + log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile); + SessionFile xtantdoc = new SessionFile(archive); + vamArchive.updateFrom(null, xtantdoc); + // LATER: decide if session archive provenance should be updated to reflect access. + // TODO: soon! do a proper import objects from external file + log.debug("Transfer complete."); + } + /** + * write session as a new vamsas Document (this will overwrite any existing file without warning) + * TODO: test + * TODO: verify that lock should be released for vamsas document. + * @param destarchive + */ + protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException { + log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive); + SessionFile newdoc = new SessionFile(destarchive); + if (extlock==null && !vamArchive.lockFile()) + while (!vamArchive.lockFile()) + log.info("Trying to get lock for "+vamArchive.sessionFile); + // TODO: LATER: decide if session archive provenance should be written in vamsasDocument file for this export. + newdoc.updateFrom(extlock, vamArchive); + // LATER: LATER: fix use of updateFrom for file systems where locks cannot be made (because they don't have a lockManager, ie NFS/Unix, etc). + vamArchive.unLock(); + newdoc.unlockFile(); + log.debug("Transfer complete."); + } + + /** + * Creates a VamsasArchive Vobject for accessing and updating document + * Note: this will lock the Vamsas Document for exclusive access to the client. + * @return session vamsas document + * @throws IOException if locks fail or vamsas document read fails. + */ + protected VamsasArchive getVamsasDocument() throws IOException { + // TODO: check we haven't already done this once + if (!vamArchive.lockFile()) + throw new IOException("Failed to get lock for vamsas archive."); + + VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive); + + return va; + } + /** + * create a uniquely named file in the session Directory + * @see java.io.File.createTempFile + * @param pref Prefix for name + * @param suff Suffix for name + * @return SessionFile object configured for the new file (of length zero) + * @throws IOException + */ + protected SessionFile getTempSessionFile(String pref, String suff) throws IOException { + File tfile = File.createTempFile(pref,suff,sessionDir); + SessionFile tempFile = new SessionFile(tfile); + return tempFile; + } +} + + -- 1.7.10.2