attempts to remove any locks after client is finalized
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / SimpleClient.java
index 2858dd3..a2b999d 100644 (file)
@@ -6,34 +6,32 @@
  */
 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.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+
+import java.nio.channels.OverlappingFileLockException;
 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;
+
+import uk.ac.vamsas.client.ClientHandle;
+import uk.ac.vamsas.client.Events;
+import uk.ac.vamsas.client.IClient;
+import uk.ac.vamsas.client.IClientDocument;
+import uk.ac.vamsas.client.IObjectUpdate;
+import uk.ac.vamsas.client.InvalidSessionUrnException;
+import uk.ac.vamsas.client.SessionHandle;
+import uk.ac.vamsas.client.UserHandle;
+import uk.ac.vamsas.client.picking.IPickManager;
+import uk.ac.vamsas.objects.core.Entry;
+import uk.ac.vamsas.objects.core.VamsasDocument;
+import uk.ac.vamsas.objects.utils.ProvenanceStuff;
 
 /**
  * @author jimp
@@ -49,6 +47,13 @@ public class SimpleClient implements IClient {
   protected ClientHandle client = null;
   protected EventGeneratorThread evgen = null;
   protected ClientDocument cdocument = null;
+  
+  
+  
+  
+  private Lock activeClientFilelock = null;
+  private  File clientlockFile = null;
+  
   /**
    * object hash table that persists in each client holding vorbaIds and hash values after a document write
    */
@@ -59,7 +64,9 @@ public class SimpleClient implements IClient {
    * @return
    */
   private IdFactory makeVorbaIdFactory() {
-    return new IdFactory(getSessionHandle(), client, user);
+    if (extantobjects==null)
+      extantobjects=new Hashtable();
+    return new IdFactory(getSessionHandle(), client, user, extantobjects);
   }
   
   /**
@@ -74,12 +81,17 @@ public class SimpleClient implements IClient {
     _session = sess;
     this.user = user;
     this.client = client;
-    try {
+    //try {
+    log.debug("Creating new session for "+_session);
       session = new SessionUrn(_session);
-    } catch (MalformedURLException e) {
+      log.debug("Creating new Event Generator");
+      evgen = new EventGeneratorThread(_session, this, handlers);
+      /*} catch (MalformedURLException e) {
       log.error("Couldn't form a valid SessionUrn object!",e);
       throw new InvalidSessionUrnException(_session.toString());
-    }
+    }*/
+      log.debug("SimpleClient constructed for session "+session.getSessionUrn());
+      
   }
   /**
    * construct new session by importing objects from an existing vamsas document
@@ -98,10 +110,12 @@ public class SimpleClient implements IClient {
       VamsasDocument doc = sdoc.getVamsasDocument(odoc);
       sessdoc.putVamsasDocument(doc, sdoc.vorba);
       sessdoc.closeArchive();
+      log.debug("Imported new vamsas data from "+importingArchive);
     } catch (Exception e) {
       sessdoc.cancelArchive();
-      // write a dummy archive
+      // write a dummy iohandler
       _session.slog.info("Exception when importing document data from "+importingArchive);
+      log.warn("While importing session data from existing archive in "+importingArchive, e);      
       throw new Exception("Failed to import data from "+importingArchive, e);
     }
   }
@@ -109,7 +123,7 @@ public class SimpleClient implements IClient {
   /*
    * (non-Javadoc)
    * LATER: check that build substitution variables are correct
-   * @see org.vamsas.client.IClient#getAbout()
+   * @see uk.ac.vamsas.client.IClient#getAbout()
    */
   public String getAbout() {
     return new String("VORBA SimpleClient version $version$ build $build$");
@@ -118,7 +132,7 @@ public class SimpleClient implements IClient {
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#getSessionUrn()
+   * @see uk.ac.vamsas.client.IClient#getSessionUrn()
    */
   public String getSessionUrn() {
     return session.getSessionUrn();
@@ -127,7 +141,7 @@ public class SimpleClient implements IClient {
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#getSessionHandle()
+   * @see uk.ac.vamsas.client.IClient#getSessionHandle()
    */
   public SessionHandle getSessionHandle() {
     // TODO: eliminate SessionHandle ? need to refactor interfaces.
@@ -138,7 +152,7 @@ public class SimpleClient implements IClient {
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#getClientHandle()
+   * @see uk.ac.vamsas.client.IClient#getClientHandle()
    */
   public ClientHandle getClientHandle() {
     return client;
@@ -147,7 +161,7 @@ public class SimpleClient implements IClient {
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#getUserHandle()
+   * @see uk.ac.vamsas.client.IClient#getUserHandle()
    */
   public UserHandle getUserHandle() {
     return user;
@@ -174,7 +188,7 @@ public class SimpleClient implements IClient {
   
   /**
    * make all the PropertyChangeSupport objects for the
-   * events described in org.vamsas.client.Event
+   * events described in uk.ac.vamsas.client.Event
    * @return
    */
   private static Hashtable initHandlers() {
@@ -190,43 +204,67 @@ public class SimpleClient implements IClient {
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#addDocumentUpdateHandler(java.util.EventListener)
+   * @see uk.ac.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);
-    }
+    this.addVorbaEventHandler(Events.DOCUMENT_UPDATE, evt);
   }
   boolean finalized=false;
+  private void haltPickmanager() {
+    if (pickmanager!=null) {
+      final SimpleClient dying=this; 
+      new Thread() {
+        public void run() {
+          SimpleClient.log.debug("Stopping pickManager..");
+          dying.pickmanager.shutdown();
+          SimpleClient.log.debug("pickManager halted.");
+        }
+      }.start();
+    }
+  }
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#finalizeClient()
+   * @see uk.ac.vamsas.client.IClient#finalizeClient()
    */
   public void finalizeClient() {
+    if (finalized)
+      throw new Error("VAMSAS Client Implementation Error: Finalized called twice for same client instance.");
+
+    // mark this instance as finalized
+    finalized=true;
+    
     // TODO: determine if this is last client in session
-    // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
     
+    // TODO: raise events like : ((lst_client && document.request.to.close), (client_finalization), (
+    evgen._raise(Events.CLIENT_FINALIZATION, null, this,null);
     // if (handlers.containsKey(Events.))
     // if (handlers.containsKey(Events.CLIENT_FINALIZATION))
     // deregister listeners.
-    // mark this instance as finalized
+    log.debug("Stopping pickManager");
+    haltPickmanager();
+    
+    log.debug("Deregistering Client");
+    _session.removeClient(this);
+    //log.debug("Stopping EventGenerator..");
+    //evgen.stopWatching();
+    this.cdocument = null;
+    SimpleClient.log.debug("EventGenerator halted.");
+    log.debug("finalization Complete.");
   }
   
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#getClientDocument()
+   * @see uk.ac.vamsas.client.IClient#getClientDocument()
    */
   public IClientDocument getClientDocument() throws IOException {
+    log.debug("getClientDocument");
     if (cdocument!=null) {
       // cdocument is non-nill if the ClientDocument.finalise() method hasn't been called.
       return cdocument;
     }
+    evgen.disableDocumentWatch();
     VamsasArchive va = null;
     try {
       // LATER: bail out if it takes too long to get the lock ?
@@ -237,16 +275,16 @@ public class SimpleClient implements IClient {
     }
     VamsasDocument doc=null;
     IdFactory vorba = null;
-    // TODO: reduce size of vorba ids generated from these parameters to IdFactory (mainly sessionHandle rationalization ?)
+    // TODO: LATER: 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");
+      log.debug("Accessing document");
       doc = 
         va.getVamsasDocument(getProvenanceUser(),
             "created new session document.", null);
       if (doc!=null)
-        _session.slog.debug("Successfully retrieved document.");
+        log.debug("Successfully retrieved document.");
       else
         log.error("Unexpectedly retrieved null document!");
     }
@@ -256,77 +294,110 @@ public class SimpleClient implements IClient {
     }
     // Construct the IClientDocument instance
     
-    ClientDocument cdoc = new ClientDocument(doc, va, vorba, this);
-    return cdoc;
+    cdocument = new ClientDocument(doc, va, vorba, this);
+    return cdocument;
   }
   
   /*
    * (non-Javadoc)
    * @throws Errors for invalid newdoc parameter
-   * @see org.vamsas.client.IClient#updateDocument(org.vamsas.client.IClientDocument)
+   * @see uk.ac.vamsas.client.IClient#updateDocument(uk.ac.vamsas.client.IClientDocument)
    */
   public void updateDocument(IClientDocument newdoc) {
+    log.debug("updateDocument:");
+    // Check validity of simpleclient instance and that it holds a lock on the session's document
     if (!(newdoc instanceof ClientDocument)) {
-      throw new Error("Invalid IClientDocument instance for SimpleClient.");
+      throw new Error("Invalid IClientDocument passsed to SimpleClient.");
     }
     if (cdocument==null)
-      throw new Error("Client Error - updateDocument() called before getClientDocument().");
+      throw new Error("Client Error - updateDocument() called before getClientDocument() on this SimpleClient instance.");
     if (newdoc!=cdocument)
       throw new Error("Client Error - SimpleClient.updateDocument() can only take the IClientDocument instance returned from SimpleClient.getClientDocument()");
+    
+    if (evgen.isDocumentWatchEnabled())
+      throw new Error("Probable Client Error (did you remember to call SimpleClient.updateDocument(clientdoc) at the end of the document update handler?) - or Library Bug : Document watcher still enabled whilst ClientDocument instance exists.");
+    
     if (!cdocument.isModified()) {
+      // client document is silently got rid of, with no session update events.
       if (log.isDebugEnabled())
-        log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument.");
+        log.debug("updateDocument for "+session.getSessionUrn()+" with unmodified IClientDocument (skipping the write)");
     } 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.");
-      }
+      writeSessionDocument();
     }
-    // garbage collect the ClientDocument instance.
+    tidyAwaySessionDocumentState(); 
+  }
+  /**
+   * garbage collect the ClientDocument instance and re-enable watchers.
+   */
+  protected void tidyAwaySessionDocumentState() {
     try {
+      log.debug("Finalizing ClientDocument instance.");
       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
+
+    try {
+      _session.unlockVamsasDocument();
+      evgen.enableDocumentWatch();
+    } catch (IOException e) {
+      log.warn("IO Problems when releasing lock on session document!",e);
+      _session.slog.error("IO problems when attempting to release lock on session document.");
+    }
   }
-  
+
+  /**
+   * write the cdocument instance to the session for real.
+   */
+  private void writeSessionDocument() {
+    try {
+      boolean updated=cdocument.updateSessionDocument();
+      if (!updated) {
+        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 iohandler.
+        _session.slog.warn("Session Document updated but may not be valid (false return from org.vamsas.simpleclient.ClientDocument.updateSessionDocument()");
+      } else {
+        log.debug("Document update successful.");
+      }
+      _session.setUnsavedFlag();
+    }
+    catch (IOException e) {
+      log.warn("IO Problems when updating document!",e);
+      _session.slog.error("IO problems when attempting to update document.");
+    }
+  }
+
   /*
    * (non-Javadoc)
    * 
-   * @see org.vamsas.client.IClient#storeDocument(java.io.File)
+   * @see uk.ac.vamsas.client.IClient#storeDocument(java.io.File)
    */
   public void storeDocument(File location) {
-    
+    if (location==null)
+      throw new Error("Vamsas Client API Usage Error: storeDocument called with null location.");
+    log.debug("StoreDocument to "+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();
+       _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,
+   * @see uk.ac.vamsas.client.IClient#addVorbaEventHandler(java.lang.String,
    *      java.beans.PropertyChangeListener)
    */
   public void addVorbaEventHandler(String EventChain, PropertyChangeListener evt) {
     if (handlers.containsKey(EventChain)) {
+      log.debug("Adding new handler for "+EventChain);
       Object handler;
       ((PropertyChangeSupport) (handler = handlers.get(EventChain)))
       .addPropertyChangeListener(evt);
@@ -336,16 +407,16 @@ public class SimpleClient implements IClient {
   }
   
   /* (non-Javadoc)
-   * @see org.vamsas.client.IClient#pollUpdate()
+   * @see uk.ac.vamsas.client.IClient#pollUpdate()
    */
   public void pollUpdate() {
-    
+    log.debug("pollUpdate");
     if (evgen==null) {
       log.warn("pollUpdate called on incomplete SimpleClient object.");
       return;
     }
     
-    if (!evgen.isAlive()) {
+    if (!evgen.isWatcherAlive()) {
       log.warn("pollUpdate called before joinSession() - trying to do this.");
       try {
         joinSession();
@@ -356,42 +427,52 @@ public class SimpleClient implements IClient {
     
     //TODO ensure event generator robustly handles these interrupts.
     log.debug("interrrupting event generator.");
-    evgen.interrupt();
+    evgen.interruptWatching();
     log.debug("interrrupted event generator.");
   }
   
   /* (non-Javadoc)
-   * @see org.vamsas.client.IClient#joinSession()
+   * @see uk.ac.vamsas.client.IClient#joinSession()
    */
   public void joinSession() throws Exception {
+    log.debug("Joining Session.");
     // start the EventGenerator thread.
     if (evgen==null) {
       log.warn("joinSession called on incomplete SimpleClient object.");
       return;
     }
-    if (evgen.isAlive())
+    if (evgen.isWatcherAlive())
       throw new Error("Join session called twice for the same SimpleClient (IClient instance).");
-    evgen.start();
-    if (evgen.isAlive())
+    evgen.startWatching();
+    if (evgen.isWatcherAlive())
       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 ?
+      //TODO: LATER: 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)
+   * @see uk.ac.vamsas.client.IClient#importDocument(java.io.File)
    */
   public void importDocument(File location) {
     // TODO LATER: implement SimpleClient.importDocument()
+    // if (numberOfClients<1 or document file is empty/new, then can import directly.)
+    // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead.
     log.error("importDocument is not yet implemented for a SimpleClient Session.");
+    
+    /*try {
+      this._session.setVamsasDocument(location);
+    } catch (IOException e) {
+      log.error("importDocument failed.");
+    }*/
   }
 
   public IObjectUpdate getUpdateHandler(Class rootObject) {
@@ -413,4 +494,100 @@ public class SimpleClient implements IClient {
     // TODO Auto-generated method stub
     
   }
+
+  /**
+   * retrieves the current VamsasSession to which belong the client
+   * @return the _session
+   */
+  protected VamsasSession get_session() {
+    return this._session;
+  }
+  SimplePickManager pickmanager=null;
+  /* (non-Javadoc)
+   * @see uk.ac.vamsas.client.IClient#getPickManager()
+   */
+  public IPickManager getPickManager() {
+    createPickManager();
+    return pickmanager;
+  }
+
+  private void createPickManager() {
+    if (pickmanager==null){
+      // TODO: Construct PickManager for session using details from sessionURN!
+      log.debug("Creating PickManager (not from sessionURN yet)");
+      pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
+    }
+  }
+  
+  
+  protected void releaseActiveClientFile() throws IOException
+  {
+   
+    log.debug("Releasing active client locks");
+    if( activeClientFilelock != null)
+    {// Release the lock
+      log.debug("Releasing lock on active client lock file");
+      activeClientFilelock.release();
+      log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
+      activeClientFilelock = null;
+    } else {
+      log.debug("ReleaseActiveClientFile called when client has no lock on its clientLockFile");
+    }
+    if (this.clientlockFile != null)
+    {
+      log.debug("trying to delete active client lock file");
+      if (this.clientlockFile.exists())
+      {
+        this.clientlockFile.delete();
+        log.debug("deleted active client lock file");
+      }
+    } else {
+      log.debug("ReleaseActiveClientFile called when client has no clientLockFile");
+    }
+  }
+  
+  protected void  createActiveClientFile() throws IOException
+  {
+    if(this.clientlockFile != null )return; 
+   log.debug("createActiveClientFile");
+    //create, if need,  subdirectory to contain client files
+   File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory);
+    if( !clientlockFileDir.exists())
+      {//the directory does not exist, create it
+        if (! clientlockFileDir.mkdirs())
+        {
+          throw new IOException("Failed to create sub directory to session directory  for client lock files'"+clientlockFileDir.getAbsolutePath()+"'");
+        }
+      }
+    else
+    {
+      if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite()))
+      {
+        throw new IOException("Directory  for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'");
+       }
+    }
+    this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll("[:;/\\\\]+",""));
+    
+    log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath());
+    Lock clientLock = uk.ac.vamsas.client.simpleclient.LockFactory.getLock(clientlockFile, false);
+    if (clientLock==null || !clientLock.isLocked())
+    {
+      log.fatal("IMPLEMENTATION ERROR: Couldn't get a lock for the client lock file "+clientlockFile);
+    }
+    activeClientFilelock = clientLock;
+  }
+  /**
+   * @return the clientlockFile
+   */
+  protected File getClientlockFile() {
+    return clientlockFile;
+  }
+  /**
+   * 
+   * @return the lock for the client in the session
+   */
+  protected Lock getClientLock() {
+    return activeClientFilelock;
+  }
+
 }