SimpleClient implementation testable against uk.ac.vamsas.test.ExampleApplication
authorjprocter <jprocter@compbio.dundee.ac.uk>
Fri, 12 Jan 2007 15:51:15 +0000 (15:51 +0000)
committerjprocter <jprocter@compbio.dundee.ac.uk>
Fri, 12 Jan 2007 15:51:15 +0000 (15:51 +0000)
git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@341 be28352e-c001-0410-b1a7-c7978e42abec

src/uk/ac/vamsas/client/simpleclient/ClientDocument.java
src/uk/ac/vamsas/client/simpleclient/EventGeneratorThread.java
src/uk/ac/vamsas/client/simpleclient/SimpleClient.java
src/uk/ac/vamsas/client/simpleclient/VamsasSession.java

index bc7a7e9..316e112 100644 (file)
@@ -23,14 +23,16 @@ import uk.ac.vamsas.objects.utils.AppDataReference;
 import uk.ac.vamsas.test.objects.Core;
 
 /**
- * Maintains a collection of vamsas objects, appdatas and states, and provides api for a SimpleClient's client.
+ * Maintains a collection of vamsas objects, appdatas and states, 
+ * and provides api for a SimpleClient's client.
+ * TODO: test and migrate ArchiveClient.getAppData methods to here and retest in ExampleApplication
  * @author jimp 
  */
 public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implements IClientDocument {
   private static Log log = LogFactory.getLog(ClientDocument.class);
   private VamsasDocument doc;
   protected SimpleClient sclient;
-  protected VamsasArchive archive = null;
+  protected VamsasArchive iohandler = null;
   /**
    * indicate if new data has been incorporated
    */
@@ -42,10 +44,9 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
   public boolean isModified() {
     return isModified;
   }
-  private Vector updatedObjects=null;
   /**
    *
-   *  prepare Application-side dataset from the vamsas Document archive
+   *  prepare Application-side dataset from the vamsas Document iohandler
    * @param doc - the dataset
    * @param docHandler - the sessionFile IO handler
    * @param Factory - the source of current and new vorbaIds
@@ -56,12 +57,12 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     
     
     /**
-     * prepare Application-side dataset from the vamsas Document archive
+     * prepare Application-side dataset from the vamsas Document iohandler
      */
     this.sclient = sclient;
-    archive = docHandler;
+    iohandler = docHandler;
     this.doc = doc;
-    updatedObjects=null;  /// TODO: correct this line
+    _VamsasRoots=doc.getVAMSAS();
   }
   
   /*
@@ -102,7 +103,20 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
    * internal reference to single copy of Document Roots array
    */
   private VAMSAS[] _VamsasRoots=null;
-  /*
+
+  protected void updateDocumentRoots() {
+    if (doc==null) {
+      log.error("updateDocumentRoots called on null document. Probably an implementation error.");
+      return;
+    }
+    if (isModified) {
+      if (_VamsasRoots!=null) {
+        doc.setVAMSAS(_VamsasRoots);
+        _VamsasRoots=null;
+      }
+    }
+  }
+/*
    * (non-Javadoc)
    * LATER: currently there is only one Vector of roots ever passed to client - decide if this is correct (means this is not thread safe and may behave unexpectedly)
    * @see uk.ac.vamsas.client.IClientDocument#getVamsasRoots()
@@ -112,7 +126,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       log.debug("Null document for getVamsasRoots(), returning null");
       return null;
     }
-    if (archive==null) {
+    if (iohandler==null) {
       // LATER: decide on read-only status of ClientDocument object
       log.warn("getVamsasRoots() called on possibly read-only document.");
     }
@@ -122,6 +136,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     if (roots == null) {
       // Make a new one to return to client to get filled. 
       _VamsasRoots = new VAMSAS[] { new VAMSAS() };
+      registerObject(_VamsasRoots[0]);
       // Do provenance now. just in case.
       doc.getProvenance().addEntry(sclient.getProvenanceEntry("Created new document root [id="+_VamsasRoots[0].getId()+"]"));
       doc.addVAMSAS(_VamsasRoots[0]);
@@ -138,9 +153,9 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       return -1;
     if (docRoots==null || docRoots.length==0)
       return -1;
-    String r_id = root.getId();
+    VorbaId d_id=null,r_id = root.getVorbaId();
     for (int i=0,j=docRoots.length; i<j; i++)
-      if (docRoots[i]==root || (docRoots[i]!=null && docRoots[i].getId().equals(r_id)))
+      if (docRoots[i]==root || (docRoots[i]!=null && (d_id=docRoots[i].getVorbaId())!=null) && d_id.equals(r_id))
         return i;
     return -1;
   }
@@ -155,7 +170,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     if (oldVersion==newVersion) {
       // may be a virgin root element.
       if (!newVersion.isRegistered())
-        _registerObject(newVersion);
+        _registerObject(newVersion); // TODO: check - this call hasn't been tested.
       // Should retrieve original version and compare - unless local hashes can be used to determine if resultSet has been truncated.
       // just do internal validation for moment.
       if (newVersion.isValid())
@@ -170,7 +185,8 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     }
     return false;
     /**
-     * LATER isValidUpdate : Ideally. we efficiently walk down, comparing hashes, to deal with merging and verifying provenance for objects
+     * LATER: MUCH LATER! - not needed for simple case and this routine shouldn't live in this class anymore 
+     * isValidUpdate : Ideally. we efficiently walk down, comparing hashes, to deal with merging and verifying provenance for objects
     
     // extract root objects
     if (newroots != null) {
@@ -300,7 +316,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       log.debug("addVamsasRoots called on null document.");
       return;
     }
-    VAMSAS[] newroots = _combineRoots(new VAMSAS[] {newroot}, _VamsasRoots, this);
+    VAMSAS[] newroots = _combineRoots(new VAMSAS[] {newroot}, getVamsasRoots(), this);
     _VamsasRoots = newroots;  
   }
   
@@ -344,6 +360,11 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       log.warn("registerObjects called for null vamsasObjects hasharray.");
       return null;
     }
+    if (iohandler==null)  {
+      log.warn("registerObjects called for read only document.");
+      return null;
+    }
+      
     if (unregistered!=null) {
       VorbaId id = _registerObject(unregistered);
       log.debug("Registered object - total of "+vamsasObjects.size()+" ids.");
@@ -360,6 +381,8 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
    * @see uk.ac.vamsas.client.IClientDocument#getClientAppdata()
    */
   public IClientAppdata getClientAppdata() {
+    // TODO: getClientAppdata not tested in ArchiveClient mockup
+    log.error("TODO: TEST Client Appdata access methods");
     if (doc==null) {
       log.warn("getClientAppdata called on null document.");
       return null;
@@ -390,8 +413,13 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
    * @return
    */
   protected VamsasArchiveReader getVamsasArchiveReader() {
+    if (iohandler==null) {
+      log.error("Near fatal. Null VamsasArchive iohandler so can't get VamsasArchiveReader");
+      return null;
+    }
     try {
-      return archive.getOriginalArchiveReader();
+      log.info("TODO: test getVamsasArchiveReader");
+      return iohandler.getOriginalArchiveReader();
     } catch (Exception e) {
       log.warn("Unable to create OriginalArchiveReader!", e);
     }
@@ -403,8 +431,8 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
       log.warn("updateSessionDocument called on null document.");
       throw new java.io.IOException("Document is closed.");
     }
-    if (archive==null) {
-      log.warn("updateSessionDocument called document archive handler.");
+    if (iohandler==null) {
+      log.warn("updateSessionDocument called document iohandler handler.");
       throw new java.io.IOException("Document is closed.");
     }
     
@@ -417,7 +445,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     // update the VamsasDocument structure with any new appData's.
     // try to update the sessionFile
     log.debug("Attempting to update session "+sclient.session.getSessionUrn());
-    if (scappd.isModified()) {
+    if (scappd!=null && scappd.isModified()) {
       ClientHandle client = sclient.client;
       UserHandle user = sclient.user;
       scappd.closeForWriting();      
@@ -441,7 +469,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
           scappd.appsGlobal.setDataReference(AppDataReference.uniqueAppDataReference(doc, sclient.client.getClientUrn()));
         }
         // LATER: use a switch to decide if the data should be written as a reference or as an embedded data chunk
-        scappd.updateAnAppdataEntry(archive, scappd.appsGlobal, scappd.newAppData);
+        scappd.updateAnAppdataEntry(iohandler, scappd.appsGlobal, scappd.newAppData);
         log.debug("...Successfully updated Global Appdata Entry.");
       }
       if (scappd.newUserData!=null && scappd.newUserData.sessionFile.exists()) {
@@ -464,25 +492,31 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
           }
           appd.setDataReference(AppDataReference.uniqueAppDataReference(doc, sclient.client.getClientUrn()+safe_username));
         }
-        scappd.updateAnAppdataEntry(archive, scappd.usersData, scappd.newUserData);
+        scappd.updateAnAppdataEntry(iohandler, scappd.usersData, scappd.newUserData);
         log.debug("...Successfully updated user AppData entry.");
       }
     }
+    try {
+      if (iohandler.transferRemainingAppDatas())
+        log.debug("Remaining appdatas were transferred.");
+      else
+        log.debug("No remaining appdatas were transferred. (Correct?)");
+    } catch (Exception e) {
+      log.error("While transferring remaining AppDatas", e);
+    }
     log.debug("Updating Document...");
-    // now update the document.
+    // now update the document. - this was basically the doUpdate method in test.ArchiveClient
+    updateDocumentRoots();
     try {
-      archive.putVamsasDocument(doc);
+      iohandler.putVamsasDocument(doc);
       log.debug("Successfully written document entry.");
     }
     catch (Exception e) {
       log.error("Marshalling error for vamsas document.",e);
       docupdate = false; // pass on the (probable) object validation error 
     }
-    if (archive.transferRemainingAppDatas())
-      log.debug("Remaining appdatas were transferred.");
-    else
-      log.debug("No remaining appdatas were transferred. (Correct?)");
-    archive.closeArchive();
+    iohandler.closeArchive();
+    iohandler=null; // so this method cannot be called again for this instance
     log.debug("...successully finished and closed.");
     return docupdate; // no errors ?
   }
@@ -506,6 +540,7 @@ public class ClientDocument extends uk.ac.vamsas.client.ClientDocument implement
     super.finalize();
   }
   public Vector getUpdatedObjects() {
-    return updatedObjects;
+    // TODO: WALK through the document objects calling the update mechanism for each one, or just pass this vector back to client ?return updatedObjects;
+    return null;
   }
 }
index b53210f..ded388f 100644 (file)
@@ -1,6 +1,5 @@
 package uk.ac.vamsas.client.simpleclient;
 
-import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.util.Hashtable;
@@ -13,27 +12,27 @@ 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)
@@ -41,45 +40,100 @@ 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();
+      clientfile.setHandler(new WatcherCallBack() {
+
+            public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
+              // TODO Auto-generated method stub
+              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.");
+  }
+  void _raise(String handlerEvent, String property, Object oldval, Object newval) {
+    PropertyChangeSupport h = (PropertyChangeSupport) handlers.get(handlerEvent);
+    if (h!=null) {
+      log.debug("Triggering:"+handlerEvent);
+      h.firePropertyChange(property, oldval, newval);
+      log.debug("Finished  :"+handlerEvent);
+    } else
+      log.debug("No handlers for raised "+handlerEvent);
+  }
+  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;
+      // TODO: decide if individual object update handlers are called as well as overall event handler
+      _raise(Events.DOCUMENT_UPDATE, client.getSessionUrn(), null, client);
+      if (log.isDebugEnabled()) {
+        log.debug("Finished handling a documentChanged event. Document is "+(client.cdocument==null ? "closed" : "open"));
+      }
+      /*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;
   /**
    * 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...?
       //
       /**
@@ -93,69 +147,8 @@ public class EventGeneratorThread extends Thread implements Runnable {
        * others).
       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 = "uk.ac.vamsas.client.events.documentUpdateEvent";
-       */
-      // read apphandle from 'lastUpdate' session file.
-      // pass apphandle name to appHandler ?
-      
     }
-    /**
-     * 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 = "uk.ac.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 = "uk.ac.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: uk.ac.vamsas.client.IClient for session.
-     *
-    public static final String DOCUMENT_FINALIZEAPPDATA = "uk.ac.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
@@ -176,7 +169,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
@@ -187,16 +180,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), 
@@ -204,21 +209,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++;
@@ -226,6 +243,7 @@ public class EventGeneratorThread extends Thread implements Runnable {
     
     
     log.debug("finished waiting.");
+    in_want_to_store_phase=false;
     return session.vamArchive.getLock();
   }
   /**
@@ -243,35 +261,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();
+  }
+
+  public boolean isWatcherAlive() {
+    return watchThread!=null && watchThread.running && watchThread.isAlive();
+  }
+
+  public void interruptWatching() {
+    if (watchThread!=null && watchThread.isAlive())
+      watchThread.interrupt();
+    
   }
-  /* (non-Javadoc)
-   * @see java.lang.Thread#run()
+  /**
+   * 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();
+    
   }
   
 
index 739cf58..ad74209 100644 (file)
@@ -77,11 +77,16 @@ public class SimpleClient implements IClient {
     this.user = user;
     this.client = client;
     //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
@@ -100,10 +105,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);
     }
   }
@@ -195,29 +202,47 @@ 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.haltPickManager();
+          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
-    this._session.removeClient(this);
+    haltPickmanager();
+    SimpleClient.log.debug("Stopping EventGenerator..");
+    evgen.stopWatching();
+    SimpleClient.log.debug("EventGenerator halted.");
+    log.debug("Deregistering Client");
+    _session.removeClient(this);
+    log.debug("finalization Complete.");
   }
   
   /*
@@ -226,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 ?
@@ -240,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!");
     }
@@ -259,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;
   }
   
   /*
@@ -269,33 +296,49 @@ 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("Client Error - 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.
+          // 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()");
         }
+        log.debug("Document update successful.");
       }
       catch (IOException e) {
         log.warn("IO Problems when updating document!",e);
         _session.slog.error("IO problems when attempting to update document.");
       }
     }
+    try {
+      _session.setUnsavedFlag();
+      _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.");
+    }
     // garbage collect the ClientDocument instance.
     try {
+      log.debug("Finalizing ClientDocument instance.");
       cdocument.finalize();
-
     } catch (Throwable e) {
       log.error("Exception when trying to garbage collect ClientDocument for "+session.getSessionUrn(), e);
     }
@@ -308,7 +351,9 @@ public class SimpleClient implements IClient {
    * @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
@@ -330,6 +375,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);
@@ -342,13 +388,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();
@@ -359,7 +405,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.");
   }
   
@@ -367,24 +413,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);
     }
+    
   }
   
   
@@ -424,12 +472,20 @@ public class SimpleClient implements IClient {
   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());
+    }
   }
 }
index 1c77d66..c849893 100644 (file)
@@ -1,18 +1,19 @@
 package uk.ac.vamsas.client.simpleclient;
 
-import java.io.BufferedWriter;
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.io.RandomAccessFile;
 import java.io.Writer;
+import java.util.Enumeration;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.log4j.Appender;
 import org.apache.log4j.Logger;
 import org.apache.log4j.FileAppender;
+import org.apache.log4j.PatternLayout;
 
 import uk.ac.vamsas.client.ClientHandle;
 import uk.ac.vamsas.client.IClient;
@@ -119,22 +120,22 @@ public class VamsasSession {
    * @throws IOException
    */
   private void initLog() throws IOException {
+    // TODO: fix session event logging
     // LATER: make dedicated appender format for session log.
-    Appender app = slog.getAppender("SESSION_LOG");
-   // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
-    
-    //Appender app = slog.getAppender("SESSION_LOG");
-    if (app == null) log.info("No appender for SESSION_LOG");
+    /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
+    // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
+    // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
+    for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
+      System.out.println(e.nextElement());
+
+    }*/
   
-    if (slog!= null && app != null)
-      {
-        if (app instanceof FileAppender)
-          {
-            File sessionLogFile =  new File(this.sessionDir, ((FileAppender)app).getFile());
-            slog.addAppender(new FileAppender(app.getLayout(), sessionLogFile.getAbsolutePath()));
-          }
-        // slog.removeAppender("SESSION_LOG");
-      }
+    if (slog!= null ) {
+      File sessionLogFile =  new File(this.sessionDir, SESSION_LOG);
+      slog.addAppender(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true));
+    } else {
+      log.info("No appender for SessionLog");
+    }
   }
   
   /**
@@ -162,22 +163,17 @@ public class VamsasSession {
     if (sessionDir1.exists()) {
       if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
         throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
-      this.sessionDir = sessionDir1;//createSessionFiles, need sessionDir attribute set
-      if (!checkSessionFiles(sessionDir1)) 
-        {
-          createSessionFiles();
-        }
-        // session files exist in the directory
-        
-        initSessionObjects();
-        slog.debug("Initialising additional VamsasSession instance");
-        log.debug("Attached to VamsasSession in "+sessionDir1);
+      if (!checkSessionFiles(sessionDir1))
+      log.warn("checkSessionFiles() returned false. Possible client implementation error");
+      this.sessionDir = sessionDir1; 
+      initSessionObjects();
+      slog.debug("Initialising additional VamsasSession instance");
+      log.debug("Attached to VamsasSession in "+sessionDir1);
       //} 
     } else {
       // start from scratch
       if (!sessionDir1.mkdir())
         throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
-      this.sessionDir = sessionDir1; 
       createSessionFiles();
       initSessionObjects();
       slog.debug("Session directory created.");
@@ -205,19 +201,21 @@ public class VamsasSession {
       throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
     File c_file = new File(sessionDir,CLIENT_LIST);
     File v_doc = new File(sessionDir,VAMSAS_OBJ);
-    if (c_file.createNewFile())
+    if (!c_file.exists() && c_file.createNewFile())
       log.debug("Created new ClientFile "+c_file); // don't care if this works or not
-    if (v_doc.createNewFile())
+    if (!v_doc.exists() && v_doc.createNewFile())
       log.debug("Created new Vamsas Session Document File "+v_doc); 
   }
   /**
    * construct SessionFile objects and watchers for each
    */
   private void initSessionObjects() throws IOException {
+    createSessionFiles();
     if (clist!=null || vamArchive!=null)
       throw new IOException("initSessionObjects called for initialised VamsasSession object.");
     clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
     vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
+    storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
     initLog();
   }
   /**
@@ -227,26 +225,21 @@ public class VamsasSession {
   public FileWatcher getClientWatcher() {
     return new FileWatcher(clist.sessionFile);
   }
-  FileWatcher session_doc_watcher=null;
   /**
    * make a new watcher object for the vamsas Document
    * @return new ClientFile watcher instance
    */
   public FileWatcher getDocWatcher() {
-    if (session_doc_watcher==null)
-      return session_doc_watcher = new FileWatcher(vamArchive.sessionFile);
     return new FileWatcher(vamArchive.sessionFile);
   }
   FileWatcher store_doc_file=null;
+  public ClientsFile storedocfile=null;
   /**
    * make a new watcher object for the messages file
-   * (thread safe - keeps a reference to the first watcher)
    * @return new watcher instance
    */
   public FileWatcher getStoreWatcher() {
-    if (store_doc_file==null)
-      return store_doc_file = new FileWatcher(new File(CLOSEANDSAVE_FILE));
-    return new FileWatcher(new File(CLOSEANDSAVE_FILE));
+    return new FileWatcher(new File(sessionDir,CLOSEANDSAVE_FILE));
 
   }
   /**
@@ -257,6 +250,7 @@ public class VamsasSession {
    * @return
    */
   public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
+    // TODO: replace this with clientsFile mechanism
     SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
     while (!sfw.lockFile())
       log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
@@ -293,7 +287,7 @@ public class VamsasSession {
     if (extlock==null && !vamArchive.lockFile())
       while (!vamArchive.lockFile())
         log.info("Trying to get lock for "+vamArchive.sessionFile);
-    // TODO: LATER: decide if session archive provenance should be written in vamsasDocument file for this export.
+    // TODO: LATER: decide if a provenance entry should be written in the exported document recording the export from the session
     newdoc.updateFrom(extlock, vamArchive);
     // LATER: LATER: fix use of updateFrom for file systems where locks cannot be made (because they don't have a lockManager, ie NFS/Unix, etc).
     vamArchive.unLock();
@@ -308,15 +302,29 @@ public class VamsasSession {
    * @throws IOException if locks fail or vamsas document read fails.
    */
   protected VamsasArchive getVamsasDocument() throws IOException {
-    // TODO: check we haven't already done this once
-    if (!vamArchive.lockFile()) 
+    // TODO: check we haven't already done this once - probably should be done by caller
+    // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
+    long tries=5000;
+    while (vamArchive.getLock()==null && --tries>0) {
+//       Thread.sleep(1);
+        log.debug("Trying to get a document lock for the "+tries+"'th time.");
+      }
+    if (tries==0) 
       throw new IOException("Failed to get lock for vamsas archive.");
-    
+      
     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
-    
+
     return va;
   }
   /**
+   * Unlocks the vamsas archive session document after it has been closed.
+   * @throws IOException
+   */
+  protected void unlockVamsasDocument() throws IOException {
+    if (vamArchive!=null)
+      vamArchive.unLock();
+  }
+  /**
    * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
    * @see java.io.File.createTempFile
    * @param pref Prefix for name
@@ -339,9 +347,14 @@ public class VamsasSession {
   protected void addClient(IClient client)
   {
     if (client == null)
-      this.slog.error("Try to add a null client to the session ");
-    else
-      this.clist.addClient(client.getClientHandle(), getClientWatcher().getChangedState());
+      slog.error("Try to add a null client to the session ");
+    else {
+      log.debug("Adding client "+client.getClientHandle().getClientUrn());
+      getClientWatcherElement().haltWatch();
+      clist.addClient(client.getClientHandle());
+      getClientWatcherElement().enableWatch();
+      log.debug("Added.");
+    }
   }
   
 /**
@@ -357,20 +370,30 @@ public class VamsasSession {
   {
     if (client == null)
       {
-      //System.out.println("Try to remove a null client.");
-        this.slog.error("Try to remove a null client.");
+        log.error("Null client passed to removeClient");
         return;
       }
-    this.clist.removeClient(client.getClientHandle(), getClientWatcher().getChangedState());
+    SessionFileWatcherElement cwe=getClientWatcherElement();
+    if (cwe!=null && cwe.isWatchEnabled()) {
+      cwe.haltWatch();
+    } else {
+      cwe=null;
+    }
+    clist.removeClient(client.getClientHandle(),null);
     if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
       {//assume it is the last client has been removed shutting down session
-        System.out.println("last client removed: removing session");
+        slog.info("last client removed: removing session");
+        log.debug("last client removed: removing session");
         this.getSessionManager().removeSession(client.getSessionHandle());
       }
     else
     {
-      this.slog.debug("Still "+this.clist.retrieveClientList().length +" active clients");
-      System.out.println("Still "+(this.clist.retrieveClientList()==null?"null":this.clist.retrieveClientList().length+"") +" active clients");
+      int active=clist.retrieveClientList().length;
+      log.debug("Still "+active+" active clients");
+      slog.info("Still "+active+" active clients");
+    }
+    if (cwe!=null) {
+      cwe.enableWatch();
     }
   }
 /**
@@ -385,6 +408,19 @@ protected SimpleSessionManager getSessionManager() {
 protected void setSessionManager(SimpleSessionManager sessionManager) {
   this.sessionManager = sessionManager;
 }
+public ClientsFile getStoreDocFile() {
+  if (storedocfile==null) {
+    
+  }
+  return storedocfile;
+}
+SessionFileWatcherElement clistWatchElement=null;
+public SessionFileWatcherElement getClientWatcherElement() {
+  if (clistWatchElement==null) {
+    clistWatchElement=new SessionFileWatcherElement(clist,null);
+  }
+  return clistWatchElement;
+}
 }