test clientsFile handlers.
authorjprocter <jprocter@compbio.dundee.ac.uk>
Tue, 11 Oct 2005 17:34:09 +0000 (17:34 +0000)
committerjprocter <jprocter@compbio.dundee.ac.uk>
Tue, 11 Oct 2005 17:34:09 +0000 (17:34 +0000)
git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@59 be28352e-c001-0410-b1a7-c7978e42abec

src/org/vamsas/client/ClientHandle.java
src/org/vamsas/client/Events.java
src/org/vamsas/client/SimpleClient.java
src/org/vamsas/client/simpleclient/ClientsFile.java
src/org/vamsas/client/simpleclient/Lock.java
src/org/vamsas/test/ExampleApplication.java
src/org/vamsas/test/simpleclient/ClientsFileTest.java [new file with mode: 0644]

index 5f3a77e..15add1a 100644 (file)
@@ -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));
+  }
 }
index bf35fa0..fea1b79 100644 (file)
@@ -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;
   }
 }
index 472e3dd..e70ffec 100644 (file)
@@ -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
+    
+  }
 }
index 132280e..8f938f6 100644 (file)
@@ -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;
   }
 }
index c4351d6..d7239e6 100644 (file)
@@ -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 <method>isLocked</method>)
    * if a lock could be obtained for <param>lockfile</param>
@@ -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());
index 63dcefe..e8bce3c 100644 (file)
@@ -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 <vamsasFileDirectory> <vamsasSessionURN> <action> [+<arguments>]\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 (file)
index 0000000..705c1fc
--- /dev/null
@@ -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<argneed)
+      throw new Error(cmd+" needs "+argneed+" arguments : "+msg);
+  }
+  public static void main(String[] args) {
+    java.io.File cf = new java.io.File(args[0]);
+    System.out.println("Connecting to clientFile "+args[0]);
+    ClientsFile cfhand;
+    try {
+      cfhand = new ClientsFile(cf);
+    }
+    catch (Exception e) {
+      e.printStackTrace(System.err);
+      return;
+    }
+    int argc=0;
+    while (argc<args.length) {
+      argc++;
+      Iterator coms = commands.iterator();
+      int com=-1;
+      while ((coms!=null) && coms.hasNext()) {
+        com++;
+        if (args[argc].toLowerCase().equals((String) coms.next())) {
+          System.out.println("Doing "+args[argc]);
+          ClientHandle ch;
+          argc++;
+          switch (com) {
+          case 0:
+            // Add
+            ClientsFileTest.complainArgs(args.length, argc, "add", 2, "for the Client's 'Name' and 'Version'");
+            int pos = cfhand.addClient(ch=new ClientHandle(args[argc],args[argc+1]));
+            argc+=1;
+            if (pos!=0)
+              System.out.println("Client added at "+pos+" as urn:"+ch.getClientUrn());
+            else
+              System.out.println("Client was not added.");
+            break;
+          case 1:
+            // remove
+            ClientsFileTest.complainArgs(args.length, argc, "remove", 3, "for the Client's 'Name', Version and URN");
+            ch=new ClientHandle(args[argc], args[argc+1]);
+            ch.setClientUrn(args[argc+2]);
+            argc+=2;
+            cfhand.removeClient(ch);
+            System.out.println("Client removed (apparently)");
+            break;
+          case 2:
+            // list
+            ClientHandle[] chlist = cfhand.retrieveClientList();
+            if (chlist!=null) {
+              for (int chi=0,che=chlist.length; chi<che; chi++) {
+                System.out.println("Client "+chi+" ("+chlist[chi].getClientName()+" "+chlist[chi].getVersion()+" "+chlist[chi].getClientUrn()+")");
+              }
+            } else {
+              System.out.println("Client list is empty.");
+            }
+            break;
+          case 3:
+            // clear
+            cfhand = null;
+            cf.delete();
+            try {
+              cf.createNewFile();
+              cfhand = new ClientsFile(cf);
+            }
+            catch (Exception e) {
+              System.err.println("Failed on new empty clientfile creation!");
+              e.printStackTrace(System.err);
+            }
+            break;
+          }
+          coms = null;
+        }
+      }
+      if (coms!=null) {
+        System.err.println("Unknown command : "+args[argc] + "*Ignored!*");
+      };
+    }
+  }
+
+}