attempts to remove any locks after client is finalized
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / EventGeneratorThread.java
index 8d028ef..b9824b5 100644 (file)
@@ -1,38 +1,38 @@
 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;
+
+import uk.ac.vamsas.client.Events;
 
 /**
  * monitors watcher objects and generates events.
  */
-public class EventGeneratorThread extends Thread implements Runnable {
+public class EventGeneratorThread {
   private static Log log = LogFactory.getLog(EventGeneratorThread.class);
   private SimpleClient client;
   private Hashtable handlers; // manager object
   private VamsasSession session;
-
+  /**
+   * thread watching all the session's file objects
+   */
+  protected VamsasFileWatcherThread watchThread=null;
   /** 
-   * list with all the clientHandles for the session
+   * Watcher element for list of all the clientHandles for the session
    */
-  protected FileWatcher clientfile=null;
+  protected SessionFileWatcherElement clientfile=null;
   /**
    * the session's vamsasDocument
    */
-  protected FileWatcher vamsasfile=null;
+  protected VamsasFileWatcherElement vamsasfile=null;
   /**
    * written to by client when its app calls storeDocument.
    */
-  protected FileWatcher storeFile=null;
-  
-  private boolean watch=false;
-  
+  protected SessionFileWatcherElement storeFile=null;
   
   EventGeneratorThread(VamsasSession s, SimpleClient _client, Hashtable eventhandlers) {
     if (eventhandlers==null || s==null || _client==null)
@@ -40,121 +40,165 @@ public class EventGeneratorThread extends Thread implements Runnable {
     handlers = eventhandlers;
     session = s;
     client = _client;
-    setName(s.sessionDir.getName());
+    log.debug("Creating VamsasFileWatcherThread.");
+    watchThread = new VamsasFileWatcherThread(this);
     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();
+    if (clientfile==null) {
+      log.debug("Initializing clientfile Watcher");
+      clientfile = session.getClientWatcherElement();
+     // handler is set in the Vamsas session
+/* clientfile.setHandler(new WatcherCallBack() {
+
+            public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
+              return clientListChanged(watcher, lock);
+            }        
+      });*/
+      watchThread.addElement(clientfile);
+    }
+    final EventGeneratorThread evgen=this;
+    
+    if (vamsasfile ==null) {
+      log.debug("Initializing VamsasFileWatcher");
+      vamsasfile = new VamsasFileWatcherElement(session.vamArchive,
+          new WatcherCallBack() {
+         public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
+           return evgen.documentChanged(lock);
+         }
+      });
+      watchThread.addElement(vamsasfile);
+    }
+    if (storeFile == null) {
+      storeFile = new SessionFileWatcherElement(session.getStoreDocFile(),
+          new WatcherCallBack() {
+        public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
+          return evgen.storeDocRequest(lock);
+        }
+      });
+      log.debug("Initializing storeDocFile flag watcher");
+    }
+    /*
+    */
+    log.debug("Watchers inited.");
+  }
+  /**
+   * Call registered handlers for a vamsas session event
+   * @param handlerEvent a named event
+   * @param property property name to pass to handler
+   * @param oldval old value of property to pass
+   * @param newval new value of property to pass
+   * @return true if event generation did not raise any exceptions.
+   */
+  boolean _raise(String handlerEvent, String property, Object oldval, Object newval) {
+    PropertyChangeSupport h = (PropertyChangeSupport) handlers.get(handlerEvent);
+    if (h!=null) {
+      log.debug("Triggering:"+handlerEvent);
+      try {
+        h.firePropertyChange(property, oldval, newval);
+      } catch (Exception e) {
+        log.warn("Client Exception during handling of "+handlerEvent, e);
+        return false;
+      }
+      catch (Error e)
+      {
+        log.error("Serious! Client Error during handling of "+handlerEvent, e);
+        return false;
+      }
+      log.debug("Finished  :"+handlerEvent);
+    } else
+      log.debug("No handlers for raised "+handlerEvent);
+    return true;
+  }
+  protected boolean storeDocRequest(Lock lock) {
+    if (log.isDebugEnabled())
+      log.debug("StoreDocRequest on "+(lock==null ? (lock.isLocked() ? "" : "Invalid ") : "Non-")+"Existing lock");
+    // 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.getWatcher().exists) {
+      _raise(Events.DOCUMENT_FINALIZEAPPDATA, client.getSessionUrn(), null, client);
+      // expect client to write to document so update watcher state on return
+      vamsasfile.getWatcher().setState();
+      lock.release();
+    }
+    return true;
+  }
+
+  protected boolean documentChanged(Lock doclock) {
+    boolean continueWatching=true;
+    if (!block_document_updates) {
+      session.vamArchive.fileLock=doclock;
+      if (client.pickmanager!=null)
+        client.pickmanager.setPassThru(false);
+      if (log.isDebugEnabled()) {
+        log.debug("Initiating a documentChanged event. Document is "+(client.cdocument==null ? "closed" : "open"));
+      }
+      // TODO: decide if individual object update handlers are called as well as overall event handler
+      if (!_raise(Events.DOCUMENT_UPDATE, client.getSessionUrn(), null, client))
+      {
+        log.info("Recovering from errors or exceptions generated by client application");
+        if (client.cdocument!=null)
+        {
+          try {
+            client.tidyAwaySessionDocumentState(); 
+          }
+          catch (Exception e)
+          {
+            log.warn("Exception generated by vamsas library - when tidying away session document:",e);
+          }
+          catch (Error e)
+          {
+            log.error("LIBRARY Implementation error - when tidying away session document:",e);
+          }
+        }
+          
+      }
+      if (client.pickmanager!=null)
+        client.pickmanager.setPassThru(true);
+      if (log.isDebugEnabled()) {
+        log.debug("Finished handling a documentChanged event. Document is "+(client.cdocument==null ? "closed" : "open"));
+      }
+      if (client.cdocument!=null)
+      {
+        log.warn("Implementation Error ?  ClientDocument instance has not been closed or updated by handler!");
+      }
+      /*try {
+        client._session.getVamsasDocument().closeArchive();
+      } catch (Exception e) {log.warn("Unexpected exception when closing document after update.",e);};
+      */
+    } else {
+      // TODO: check documentChanged */
+      log.debug("Ignoring documentChanged event for "+client.getSessionUrn());
+    }
+    return continueWatching;
   }
   boolean ownsf = false;
   /**
+   * Moved to SimpleClientSessionManager
    * 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;
+  private boolean clientListChanged(WatcherElement clientfile, Lock watchlock) {
+    log.debug("ClientListChanged handler called for "+clientfile.getWatcher().getSubject());
     // 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) {
+    if (watchlock!=null) {
+      // TODO: compare new client list to old list version. is it changed ?
       // 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";
+      public static final String CLIENT_CREATION = "uk.ac.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";
+      public static final String CLIENT_FINALIZATION = "uk.ac.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 ?
-      
+      watchlock.release();
     }
-    /**
-     * 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: <Vamsas-session URN>
-     * 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() {
-    
+    return true;
   }
   /**
    * Events raised by IClient and propagated to others in session
@@ -175,7 +219,7 @@ public class EventGeneratorThread extends Thread implements Runnable {
   
   private boolean block_document_updates=false;
   int STORE_WAIT=5; // how many units before we decide all clients have finalized their appdatas
-  
+  private boolean in_want_to_store_phase=false;
   /**
    * client App requests offline storage of vamsas data. 
    * Call blocks whilst other apps do any appData finalizing
@@ -186,16 +230,28 @@ public class EventGeneratorThread extends Thread implements Runnable {
    * @param STORE_WAIT indicates how lock the call will block for when nothing appears to be happening to the session.
    */
   protected Lock want_to_store() {
-    log.debug("Setting flag for document_update requests to be ignored");
-    block_document_updates=true;
-    log.debug("Waiting for other apps to do FinalizeApp handling.");
+    if (in_want_to_store_phase) {
+      log.error("client error: want_to_store called again before first call has completed.");
+      return null;
+    }
+    in_want_to_store_phase=true;
+    // TODO: test the storeDocumentRequest mechanism
+    /*/ watchThread.haltWatchers();
+     */
+    log.debug("Stopping document_update watcher");
+    vamsasfile.haltWatch();
+    // block_document_updates=true;
+    log.debug("Cleared flag for ignoring document_update requests");
+
+    log.debug("Sending Store Document Request");
     try {
       session.addStoreDocumentRequest(client.getClientHandle(), client.getUserHandle());
     } catch (Exception e) {
       log.warn("Whilst writing StoreDocumentRequest for "+client.getClientHandle().getClientUrn()+" "+client.getUserHandle(),
           e);
-      log.info("trying to continue.");
+      log.info("trying to continue after storeDocumentRequest exception.");
     }
+    log.debug("Waiting for other apps to do FinalizeApp handling.");
     // LATER: refine this semaphore process 
     // to make a robust signalling mechanism:
     // app1 requests, app1..n do something (or don't - they may be dead), 
@@ -203,21 +259,33 @@ public class EventGeneratorThread extends Thread implements Runnable {
     // this probably needs two files - a request file, 
     //  and a response file which is acknowledged by the app1 requestor for each app.
     //  eventually, no more responses are received for the request, and the app can then only continue with its store.
-    int units = 0;
+    FileWatcher sfwatcher=session.getStoreWatcher();
+    FileWatcher vfwatcher=session.getDocWatcher();
+    int units = 0; // zero if updates occured over a sleep period
     while (units<STORE_WAIT) {
-      wait(1);
-      if (storeFile.hasChanged() || vamsasfile.hasChanged())
+      try {
+      Thread.sleep(watchThread.WATCH_SLEEP); 
+      } catch (InterruptedException e) {
+        log.debug("interrupted.");
+      }
+      if (sfwatcher.hasChanged() || vfwatcher.hasChanged()) {
         units=0;
-      else
-        units++;
+      } else {
+         units++;
+      }
     }
     
     block_document_updates=false;
+    vamsasfile.enableWatch();
     log.debug("Cleared flag for ignoring document_update requests");
     // wait around again (until our own watcher has woken up and synchronized).
     while (units<STORE_WAIT) {
-      wait(1);
-      if (storeFile.hasChanged() || vamsasfile.hasChanged())
+      try {
+        Thread.sleep(watchThread.WATCH_SLEEP); 
+        } catch (InterruptedException e) {
+          log.debug("interrupted.");
+        }
+      if (sfwatcher.hasChanged() || vfwatcher.hasChanged())
         units=0;
       else
         units++;
@@ -225,11 +293,12 @@ public class EventGeneratorThread extends Thread implements Runnable {
     
     
     log.debug("finished waiting.");
+    in_want_to_store_phase=false;
     return session.vamArchive.getLock();
   }
   /**
    * count handlers for a particular vamsas event 
-   * @param event string enumeration from org.vamsas.client.Events
+   * @param event string enumeration from uk.ac.vamsas.client.Events
    * @return -1 for an invalid event, otherwise the number of handlers
    */
   protected int countHandlersFor(String event) {
@@ -242,35 +311,42 @@ public class EventGeneratorThread extends Thread implements Runnable {
     }
     return -1;
   }
-  /**
-   * probably don't need any of these below.
-   */
-  /* (non-Javadoc)
-   * @see java.lang.Thread#destroy()
-   */
-  public void destroy() {
-    super.destroy();
+
+  public void disableDocumentWatch() {
+    vamsasfile.haltWatch();
   }
-  /* (non-Javadoc)
-   * @see java.lang.Thread#interrupt()
-   */
-  public void interrupt() {
-    // TODO Auto-generated method stub
-    super.interrupt();
+
+  public boolean isDocumentWatchEnabled() {
+    return (vamsasfile!=null) && vamsasfile.isWatchEnabled();
   }
-  /* (non-Javadoc)
-   * @see java.lang.Thread#isInterrupted()
-   */
-  public boolean isInterrupted() {
-    // TODO Auto-generated method stub
-    return super.isInterrupted();
+
+  public void enableDocumentWatch() {
+    vamsasfile.enableWatch();
   }
-  /* (non-Javadoc)
-   * @see java.lang.Thread#run()
+
+  public boolean isWatcherAlive() {
+    return watchThread!=null && watchThread.running && watchThread.isAlive();
+  }
+
+  public void interruptWatching() {
+    if (watchThread!=null && watchThread.isAlive())
+      watchThread.interrupt();
+    
+  }
+  /**
+   * called to start the session watching thread which generates events
    */
-  public void run() {
-    // TODO Auto-generated method stub
-    super.run();
+  public void startWatching() {
+    enableDocumentWatch();
+    watchThread.start();
+    while (!watchThread.running && watchThread.isAlive())
+      log.debug("Waiting until watcher is really started.");
+  }
+
+  public void stopWatching() {
+    interruptWatching();
+    watchThread.haltWatchers();
+    
   }