moved methods for opening a stored session document in a new vamsas session into...
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / SimpleClient.java
index e66cb51..43ec15d 100644 (file)
@@ -6,14 +6,14 @@
  */
 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;
 
@@ -25,17 +25,14 @@ 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.InvalidSessionDocumentException;
 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.ApplicationData;
 import uk.ac.vamsas.objects.core.Entry;
-import uk.ac.vamsas.objects.core.LockFile;
 import uk.ac.vamsas.objects.core.VamsasDocument;
-import uk.ac.vamsas.objects.utils.AppDataReference;
 import uk.ac.vamsas.objects.utils.ProvenanceStuff;
-import uk.ac.vamsas.objects.utils.document.VersionEntries;
 
 /**
  * @author jimp
@@ -51,6 +48,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
    */
@@ -61,7 +65,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);
   }
   
   /**
@@ -76,37 +82,35 @@ 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
+   * construct new SimpleClientsession 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);
+    if (log.isDebugEnabled())
+    {
+      log.debug("Attempting to overwrite session document with file: "+importingArchive);
     }
+  // TODO: write provenance entry for new session indicating the import.
+    
   }
+   */
   
   /*
    * (non-Javadoc)
@@ -195,28 +199,50 @@ public class SimpleClient implements IClient {
    * @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 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.");
   }
   
   /*
@@ -225,10 +251,12 @@ public class SimpleClient implements IClient {
    * @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 ?
@@ -239,16 +267,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!");
     }
@@ -258,8 +286,8 @@ 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;
   }
   
   /*
@@ -268,56 +296,88 @@ public class SimpleClient implements IClient {
    * @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 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();
   }
   
@@ -329,6 +389,7 @@ public class SimpleClient implements IClient {
    */
   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);
@@ -341,13 +402,13 @@ public class SimpleClient implements IClient {
    * @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();
@@ -358,7 +419,7 @@ 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.");
   }
   
@@ -366,24 +427,26 @@ public class SimpleClient implements IClient {
    * @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);
     }
+    
   }
   
   
@@ -393,7 +456,15 @@ public class SimpleClient implements IClient {
    */
   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) {
@@ -416,11 +487,99 @@ public class SimpleClient implements IClient {
     
   }
 
+  /**
+   * 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() {
-    // TODO Auto-generated method stub
-    return null;
+    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;
+  }
+
 }