From 60d3fb8b4c31842b601284e79906e8f8bc63c4f8 Mon Sep 17 00:00:00 2001 From: jprocter Date: Tue, 11 Oct 2005 17:34:09 +0000 Subject: [PATCH] test clientsFile handlers. git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@59 be28352e-c001-0410-b1a7-c7978e42abec --- src/org/vamsas/client/ClientHandle.java | 9 + src/org/vamsas/client/Events.java | 75 +++-- src/org/vamsas/client/SimpleClient.java | 10 +- .../vamsas/client/simpleclient/ClientsFile.java | 293 +++++++++++++++++--- src/org/vamsas/client/simpleclient/Lock.java | 4 +- src/org/vamsas/test/ExampleApplication.java | 19 +- .../vamsas/test/simpleclient/ClientsFileTest.java | 98 +++++++ 7 files changed, 433 insertions(+), 75 deletions(-) create mode 100644 src/org/vamsas/test/simpleclient/ClientsFileTest.java diff --git a/src/org/vamsas/client/ClientHandle.java b/src/org/vamsas/client/ClientHandle.java index 5f3a77e..15add1a 100644 --- a/src/org/vamsas/client/ClientHandle.java +++ b/src/org/vamsas/client/ClientHandle.java @@ -84,4 +84,13 @@ public class ClientHandle implements Serializable { public void setClientName(String clientName) { this.clientName = clientName; } + + public boolean equals(Object that) { + if (that instanceof ClientHandle) + return this.equals((ClientHandle) that); + return false; + } + public boolean equals(ClientHandle that) { + return (clientName.equals(that.clientName) && version.equals(that.version) && clientUrn.equals(that.clientUrn)); + } } diff --git a/src/org/vamsas/client/Events.java b/src/org/vamsas/client/Events.java index bf35fa0..fea1b79 100644 --- a/src/org/vamsas/client/Events.java +++ b/src/org/vamsas/client/Events.java @@ -1,48 +1,68 @@ package org.vamsas.client; + /** - * Enumerates the event types generated - * during the lifecycle of a Vamsas session. + * Enumerates the event types generated during the lifecycle of a Vamsas + * session. */ public class Events { /** - * Generated when a client has finished updating the document. - * Passes applicationHandle of client so the updating client can recognise its own updates. + * 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"; + public static final String DOCUMENT_UPDATE = "org.vamsas.client.events.documentUpdateEvent"; + /** - * 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... + * 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"; + public static final String DOCUMENT_CREATE = "org.vamsas.client.events.documentCreateEvent"; + /** - * Generated when a new vamsas client is attached to a session (Handle is passed) - * Note: the newly created client does not receive the event. + * 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"; + public static final String CLIENT_CREATION = "org.vamsas.client.events.clientCreateEvent"; + /** - * Generated when a vamsas client leaves a session (Handle is passed to all others). + * 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"; + public static final String CLIENT_FINALIZATION = "org.vamsas.client.events.clientFinalizationEvent"; + /** - * Generated prior to session Shutdown, after the last participating vamsas client has finalized. + * Generated prior to session Shutdown, after the last participating vamsas + * client has finalized. */ - public static final String SESSION_SHUTDOWN="org.vamsas.client.events.SessionShutdownEvent"; + public static final String SESSION_SHUTDOWN = "org.vamsas.client.events.SessionShutdownEvent"; + /** - * 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. - * + * 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. */ - public static final String DOCUMENT_REQUESTTOCLOSE="org.vamas.client.DocumentRequestToCloseEvent"; + public static final String DOCUMENT_FINALIZEAPPDATA = "org.vamsas.client.events.DocumentFinalizeAppData"; + + /** + * 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"; + public static java.util.Vector EventList = initList(); + private static java.util.Vector initList() { java.util.Vector vec = new java.util.Vector(); vec.add((Object) DOCUMENT_UPDATE); @@ -51,6 +71,7 @@ public class Events { vec.add((Object) CLIENT_FINALIZATION); vec.add((Object) SESSION_SHUTDOWN); vec.add((Object) DOCUMENT_REQUESTTOCLOSE); + vec.add((Object) DOCUMENT_FINALIZEAPPDATA); return vec; } } diff --git a/src/org/vamsas/client/SimpleClient.java b/src/org/vamsas/client/SimpleClient.java index 472e3dd..e70ffec 100644 --- a/src/org/vamsas/client/SimpleClient.java +++ b/src/org/vamsas/client/SimpleClient.java @@ -40,7 +40,7 @@ public class SimpleClient implements IClient { /* * (non-Javadoc) - * + * TODO: check that build substitution variables are correct * @see org.vamsas.client.IClient#getAbout() */ public String getAbout() { @@ -352,4 +352,12 @@ private static Object[] getVamsasDocument(Reader instream, public static void main(String[] args) { } + + /* (non-Javadoc) + * @see org.vamsas.client.IClient#joinSession() + */ + public void joinSession() throws Exception { + // TODO Auto-generated method stub + + } } diff --git a/src/org/vamsas/client/simpleclient/ClientsFile.java b/src/org/vamsas/client/simpleclient/ClientsFile.java index 132280e..8f938f6 100644 --- a/src/org/vamsas/client/simpleclient/ClientsFile.java +++ b/src/org/vamsas/client/simpleclient/ClientsFile.java @@ -1,4 +1,5 @@ package org.vamsas.client.simpleclient; + import org.vamsas.client.*; import java.io.BufferedInputStream; @@ -16,71 +17,275 @@ import java.io.OutputStream; import java.util.Vector; /** - * @author jim - * Handler for the clientsFile within a vamsas session. + * @author jim Handler for the clientsFile within a vamsas session. */ public class ClientsFile { private File filelist; + /** - * number of my client in list - * (not known at start but used when known to make lock) + * number of my client in list (not known at start but used when known to make + * lock) */ - private int syncnum=1; - - ClientsFile(File filelist) { - this.filelist=filelist; + private int syncnum = 1; + + public ClientsFile(File filelist) throws IOException { + this.filelist = filelist; + if (!this.filelist.exists()) + this.filelist.createNewFile(); } - public ClientHandle[] retrieveClientList() { - if (filelist!=null) { + + private Lock listlock = null; + + /** + * Get a lock for the ClientsFile + * + * @return true if lock was made + */ + protected boolean lockList() { + if (listlock != null && listlock.isLocked()) + return true; + listlock = null; + if (filelist != null) { if (filelist.exists()) { - Lock listlock; - do { - listlock = new Lock(filelist); // TODO: wait around if we can't get the lock. - } while (!listlock.isLocked()); + // TODO: see if we need to loop-wait for locks or they just block until + // lock is made... + // do { + // listlock = new Lock(filelist); // TODO: wait around if we can't get + // the lock. + // } while (!listlock.isLocked()); + listlock = new Lock(filelist); + return listlock.isLocked(); + } + } else + throw new Error( + "org.vamsas.client.simpleclient.ClientsFile.lockList called for non-initialised ClientsFile!"); + + // no lock possible + return false; + } + + /** + * Explicitly release the ClientsFile lock. + * + * @return true if lock was released. + */ + protected boolean unlockList() { + if (listlock != null) { + + if (listlock.isLocked()) { try { - ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(new java.io.FileInputStream(filelist))); - ClientHandle[] clients; - clients = (ClientHandle[]) ((Vector) is.readObject()).toArray(); - return clients; + listlock.lock.release(); + } catch (IOException e) { + // TODO Deal with unlock Lock.release exception! + e.printStackTrace(System.err); } - catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); + } + + listlock = null; + return true; + } + return false; + } + + /** + * internal method for getting clientList - ensures a lock has been made but + * does not release it. + * + * @return list of clients + */ + private ClientHandle[] retrieveClientHandles() { + if (lockList()) { + try { + ClientHandle[] clients=null; + if (this.listlock.rafile.length()>0) { + ObjectInputStream is = new ObjectInputStream(new BufferedInputStream( + new java.io.FileInputStream(filelist))); + Object o; + o=is.readObject(); + if (o!=null) { + try { + clients = (ClientHandle[]) o; + } + catch (Exception e) { + System.err.println("Garbage in the clientHandle list "+this.filelist); + } + } } + return clients; + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(System.err); + } catch (Exception e) { + e.printStackTrace(System.err); } - // else return null. - } else { - throw new Error("Tried to retrieve a clientList without specifying client list filename"); } return null; - } - public boolean putClientList(ClientHandle[] clients) { - if (filelist!=null) { - if (filelist.exists()) { - Lock listlock; - do { - listlock = new Lock(filelist); // TODO: wait around if we can't get the lock? should return and make client wait until write has finished so it can read new client info... - } while (!listlock.isLocked()); + /** + * get the clientList from the file. May return false if lock failed! + * @return clientList + */ + public ClientHandle[] retrieveClientList() { + if (lockList()) { + ClientHandle[] clients = retrieveClientHandles(); + if (unlockList()) + return clients; + } + return null; + } + + /** + * 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); + unlockList(); + return syncnum; + } + + /** + * removes 'me' from the session ClientList without complaint if 'me' isn't in the clientList already. + * @param me + */ + public void removeClient(ClientHandle me) { + int mynum=-1; + if (lockList()) { + ClientHandle[] clients = retrieveClientHandles(); + if (clients != null) { + if (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(clients)) + throw new Error("Failed to write new clientList!"); // failed to put the clientList to disk. + } + } + unlockList(); + } else { + throw new Error("Couldn't get lock for "+((filelist==null) ? "Unitialised filelist in ClientsFile" : filelist.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 disambiguate 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; + if (lockList()) { + 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; + } + + /** + * safely writes clients array to the file referred to by filelist. + * + * @param clients + * @return true if successful write. Throws Errors otherwise. + */ + protected boolean putClientList(ClientHandle[] clients) { + if (lockList()) { + File templist = null; + try { + templist = File.createTempFile(filelist.getName(),".old", filelist.getParentFile()); + FileOutputStream tos = new FileOutputStream(templist); + tos.getChannel().transferFrom(listlock.rafile.getChannel(), 0, + listlock.rafile.length()); + tos.close(); + } catch (FileNotFoundException e1) { + System.err.println("Can't create temp file for clientlist"); + e1.printStackTrace(System.err); + } catch (IOException e1) { + System.err + .println("Error when copying content to temp file for clientlist"); + e1.printStackTrace(System.err); + } + if (templist != null) { try { - File templist=File.createTempFile(filelist.getName(),".temp",filelist); - ObjectOutputStream os = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(templist))); + listlock.rafile.setLength(0); + ObjectOutputStream os = new ObjectOutputStream( + new BufferedOutputStream(new FileOutputStream(this.filelist))); os.writeObject(clients); os.close(); - } - catch (FileNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + // All done - remove the backup. + templist.delete(); + templist = null; } catch (Exception e) { - e.printStackTrace(); + if (templist != null) { + System.err + .println("Serious - problems writing to filelist. Backup in " + + templist.getAbsolutePath()); + e.printStackTrace(System.err); + } } + } else { + throw new Error( + "Couldn't create backup of the clientList before writing to it!"); } - // else return null. } else { - throw new Error("Tried to retrieve a clientList without specifying client list filename"); + throw new Error("Could not lock the clientList: " + + ((filelist == null) ? "Unitialized ClientsFile" + : " failed to get lock on " + filelist.getAbsolutePath())); } - return false; + // successful! + return true; } } diff --git a/src/org/vamsas/client/simpleclient/Lock.java b/src/org/vamsas/client/simpleclient/Lock.java index c4351d6..d7239e6 100644 --- a/src/org/vamsas/client/simpleclient/Lock.java +++ b/src/org/vamsas/client/simpleclient/Lock.java @@ -3,6 +3,7 @@ package org.vamsas.client.simpleclient; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.channels.FileLock; /** @@ -15,6 +16,7 @@ import java.nio.channels.FileLock; public class Lock { FileLock lock = null; + RandomAccessFile rafile; /** * creates a valid Lock (test with isLocked) * if a lock could be obtained for lockfile @@ -30,7 +32,7 @@ public class Lock { return; } - lock = new FileOutputStream(lockfile).getChannel().tryLock(); + lock = (rafile=new RandomAccessFile(lockfile,"rw")).getChannel().tryLock(); } catch (FileNotFoundException e) { System.err.println("Error! Couldn't create a lockfile at " + lockfile.getAbsolutePath()); diff --git a/src/org/vamsas/test/ExampleApplication.java b/src/org/vamsas/test/ExampleApplication.java index 63dcefe..e8bce3c 100644 --- a/src/org/vamsas/test/ExampleApplication.java +++ b/src/org/vamsas/test/ExampleApplication.java @@ -81,7 +81,15 @@ public class ExampleApplication { System.out.println("Session "+evt.getPropertyName()+" is shutting down."); // tell app to finalize its session data before shutdown. } - }); + }); + vorbaclient.addVorbaEventHandler(Events.DOCUMENT_FINALIZEAPPDATA, + new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + System.out.println("Application received a DOCUMENT_FINALIZEAPPDATA event."); + // tell app to finalize its session data prior to the storage of the current session as an archive. + } + }); + } public static String Usage="ExampleApplication [+]\n" @@ -108,7 +116,14 @@ public class ExampleApplication { user = new UserHandle("arnolduser","deathsdoor"); vorbaclient = clientfactory.getIClient(app, user); addHandlers(vorbaclient); - vorbaclient.joinSession(); + try { + vorbaclient.joinSession(); + } + catch (Exception se) { + se.printStackTrace(); + System.err.println(se+" when joining session.\n"+Usage); + System.exit(1); + } // register an update listener and a close listener. // get document data processVamsasDocument(vorbaclient.getClientDocument()); diff --git a/src/org/vamsas/test/simpleclient/ClientsFileTest.java b/src/org/vamsas/test/simpleclient/ClientsFileTest.java new file mode 100644 index 0000000..705c1fc --- /dev/null +++ b/src/org/vamsas/test/simpleclient/ClientsFileTest.java @@ -0,0 +1,98 @@ +package org.vamsas.test.simpleclient; + +import java.util.Iterator; +import java.util.Vector; + +import org.vamsas.client.ClientHandle; +import org.vamsas.client.simpleclient.ClientsFile; + +public class ClientsFileTest { + private static Vector commands; + static { + ClientsFileTest.commands=new Vector(); + ClientsFileTest.commands.add(new String("add")); + ClientsFileTest.commands.add(new String("remove")); + ClientsFileTest.commands.add(new String("list")); + ClientsFileTest.commands.add(new String("clear")); + } + private static void complainArgs(int argl, int argpos, String cmd, int argneed, String msg) { + if (argl-argpos