applied LGPLv3 and source code formatting.
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / VamsasSession.java
index 87e904a..af5ab37 100644 (file)
-package uk.ac.vamsas.client.simpleclient;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.channels.FileChannel;
-import java.nio.channels.OverlappingFileLockException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.log4j.FileAppender;
-import org.apache.log4j.Logger;
-import org.apache.log4j.PatternLayout;
-
-import uk.ac.vamsas.client.ClientHandle;
-import uk.ac.vamsas.client.Events;
-import uk.ac.vamsas.client.IClient;
-import uk.ac.vamsas.client.SessionHandle;
-import uk.ac.vamsas.client.UserHandle;
-
-/**
- * Does all the IO operations for a SimpleClient instance accessing a
- * SimpleClient vamsas session.
- * 
- * Basically, it defines the various standard names for the files in the session
- * directory (that maps to the sessionUrn), provides constructors for the file
- * handlers and watchers of those file entities, and some higher level methods
- * to check and change the state flags for the session.
- * 
- * TODO: move the stuff below to the SimpleClientFactory documentation. much may
- * not be valid now : Vamsas client is intialised with a path to create live
- * session directories. This path may contain a vamsas.properties file that sets
- * additional parameters (otherwise client just uses the one on the classpath).
- * 
- * A vamsas session consists of : SessionDir - translates to urn of a live
- * session. Contains: Vamsas Document (as a jar), Session client list file, both
- * of which may be locked, and additional temporary versions of these files when
- * write operations are taking place.
- * 
- * Zip file entries - vamsasdocument.xml : core info one or more: -
- * <applicationname>.version.sessionnumber.raw (string given in
- * vamsasdocument.xml applicationData entry)
- * 
- * Lockfile - filename given in the vamsasdocument.xml. Should be checked for
- * validity by any client and rewritten if necessary. The lockfile can point to
- * the jar itself. Mode of operation. Initially - documentHandler either: -
- * creates a zip for a new session for the client - connect to an existing
- * session zip 1. reads session urn file 2. waits for lock 3. examines session -
- * decide whether to create new application data slice or connect to one stored
- * in session. 4. writes info into session file 5. releases lock and generates
- * local client events. 6. Creates Watcher thread to generate events.
- * 
- * During the session - Update watcher checks for file change -
- * 
- * Procedures for file based session message exchange - session document
- * modification flag
- * 
- */
-
-public class VamsasSession {
-  /**
-   * indicator file for informing other processes that they should finalise
-   * their vamsas datasets for storing into a vamsas archive.
-   */
-  public static final String CLOSEANDSAVE_FILE = "stored.log";
-
-  /**
-   * session file storing the last_stored_stat data
-   */
-  public static final String MODIFIEDDOC_FILE = "modified";
-
-  private SimpleSessionManager sessionManager = null;
-
-  /**
-   * Count of cycles before considering the current client as the last one of
-   * the session (if no other client registered as active )
-   */
-  private final int watchCycleCountBeforeLastClient = 1220;
-
-  /**
-   * time between checking
-   */
-  public int WATCH_SLEEP = 30;
-
-  protected String clientFileDirectory = "clients";
-
-  /**
-   * called to clear update flag after a successful offline storage event
-   */
-  protected void clearUnsavedFlag() {
-    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
-        MODIFIEDDOC_FILE));
-    if (!laststored.clearFlag())
-      log.warn("Unsaved flag was not cleared for " + sessionDir);
-  }
-
-  /**
-   * called to indicate session document has been modified.
-   * 
-   */
-  protected void setUnsavedFlag() {
-    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
-        MODIFIEDDOC_FILE));
-    if (!laststored.setFlag())
-      log.warn("Couldn't set the Unsaved flag for " + sessionDir);
-  }
-
-  /**
-   * 
-   * @return true if session document has been modified since last offline
-   *         storage event
-   */
-  protected boolean getUnsavedFlag() {
-    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
-        MODIFIEDDOC_FILE));
-    return laststored.checkFlag();
-  }
-
-  /**
-   * log file location
-   */
-  public static final String SESSION_LOG = "Log.txt";
-
-  private static Log log = LogFactory.getLog(VamsasSession.class);
-
-  protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
-
-  /**
-   * the appender that writes to the log file inside the session's directory.
-   */
-  private FileAppender slogAppender = null;
-
-  /**
-   * setup the sessionLog using Log4j.
-   * 
-   * @throws IOException
-   */
-  private void initLog() throws IOException {
-    // TODO: fix session event logging
-    // LATER: make dedicated appender format 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) {
-      File sessionLogFile = new File(this.sessionDir, SESSION_LOG);
-      slog.addAppender(slogAppender = new FileAppender(new PatternLayout(
-          "%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(),
-          true));
-    } else {
-      log.info("No appender for SessionLog");
-    }
-  }
-
-  private void closeSessionLog() {
-    if (slog != null) {
-      if (slogAppender != null) {
-        slog.removeAppender(slogAppender);
-        slogAppender.close();
-        slogAppender = null;
-      }
-    }
-  }
-
-  /**
-   * the sessionDir is given as the session location for new clients.
-   */
-  protected File sessionDir;
-
-  /**
-   * holds the list of attached clients
-   */
-  ClientsFile clist;
-
-  public static final String CLIENT_LIST = "Clients.obj";
-
-  /**
-   * holds the data
-   */
-  VamsasFile vamArchive;
-
-  public static final String VAMSAS_OBJ = "VamDoc.jar";
-
-  /**
-   * sets up the vamsas session files and watchers in sessionDir1
-   * 
-   * @param sessionDir1
-   */
-  protected VamsasSession(File sessionDir1) throws IOException {
-    this(sessionDir1, null);
-  }
-
-  /**
-   * sets up the vamsas session files and watchers in sessionDir1
-   * 
-   * @param sessionDir1
-   * @param extVamDoc
-   *          null or an existing archive to initialise the session with
-   * @throws any
-   *           IOExceptions from creating session directory and files.
-   * @throws error
-   *           if both extVamDoc and sessionDir1 already exist (cannot import
-   *           new data into session in this way)
-   */
-  protected VamsasSession(File sessionDir1, File extVamDoc) throws IOException {
-    if (sessionDir1 == null)
-      throw new Error("Null directory for VamsasSession.");
-    if (!sessionDir1.exists() && !sessionDir1.mkdir()) {
-      throw new IOException("Failed to make VamsasSession directory in "
-          + sessionDir1);
-    }
-    if (!sessionDir1.isDirectory() || !sessionDir1.canWrite()
-        || !sessionDir1.canRead()) {
-      throw new IOException("Cannot access '" + sessionDir1
-          + "' as a read/writable Directory.");
-    }
-    boolean existingSession=checkSessionFiles(sessionDir1);
-    if (existingSession)
-    {
-      if (extVamDoc!=null) {
-        throw new Error(
-        "Client Initialisation Error: Cannot join an existing session directory with an  existing vamsas document to import.");
-      } else {
-        log
-        .debug("Joining an existing session.");
-      }
-    }
-    this.sessionDir = sessionDir1;
-    initSessionObjects();
-    if (existingSession)
-    {  slog.debug("Initialising additional VamsasSession instance");
-    } else
-    {
-      slog.debug("Founding client has joined VamsasSession instance");
-    }
-      
-    log.debug("Attached to VamsasSession in " + sessionDir1);
-    if (extVamDoc!=null)
-    {
-      setVamsasDocument(extVamDoc);
-    }
-    slog.debug("Session directory created.");
-    log.debug("Initialised VamsasSession in " + sessionDir1);
-  }
-
-  /**
-   * tests presence of existing sessionfiles files in dir
-   * 
-   * @param dir
-   * @return
-   */
-  private boolean checkSessionFiles(File dir) throws IOException {
-    File c_file = new File(dir, CLIENT_LIST);
-    File v_doc = new File(dir, VAMSAS_OBJ);
-    if (c_file.exists() && v_doc.exists())
-      return true;
-    return false;
-  }
-
-  /**
-   * create new empty files in dir
-   * 
-   */
-  private void createSessionFiles() throws IOException {
-    if (sessionDir == null)
-      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.exists() && c_file.createNewFile())
-      log.debug("Created new ClientFile " + c_file); // don't care if this
-                                                      // works or not
-    if (!v_doc.exists()) {
-      if (v_doc.createNewFile()) {
-        log.debug("Created new Vamsas Session Document File " + v_doc);
-      } else {
-        log.warn("Didn't create Vamsas Session Document file in " + 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();
-  }
-
-  /**
-   * make a new watcher object for the clientFile
-   * 
-   * @return new ClientFile watcher instance
-   */
-  public FileWatcher getClientWatcher() {
-    return new FileWatcher(clist.sessionFile);
-  }
-
-  /**
-   * make a new watcher object for the vamsas Document
-   * 
-   * @return new ClientFile watcher instance
-   */
-  public FileWatcher getDocWatcher() {
-    return new FileWatcher(vamArchive.sessionFile);
-  }
-
-  FileWatcher store_doc_file = null;
-
-  public ClientsFile storedocfile = null;
-
-  /**
-   * make a new watcher object for the messages file
-   * 
-   * @return new watcher instance
-   */
-  public FileWatcher getStoreWatcher() {
-    return new FileWatcher(new File(sessionDir, CLOSEANDSAVE_FILE));
-
-  }
-
-  /**
-   * write to the StoreWatcher file to indicate that a storeDocumentRequest has
-   * been made. The local client's storeWatcher FileWatcher object is updated so
-   * the initial change is not registered.
-   * 
-   * @param client
-   * @param user
-   * @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);
-    RandomAccessFile sfwfile = sfw.fileLock.getRaFile();
-    sfwfile.setLength(0); // wipe out any old info.
-    // TODO: rationalise what gets written to this file (ie do we want other
-    // clients to read the id of the requestor?)
-    sfwfile.writeUTF(client.getClientUrn() + ":" + user.getFullName() + "@"
-        + user.getOrganization());
-    sfw.unlockFile();
-    if (store_doc_file != null)
-      store_doc_file.setState();
-    slog.info("FinalizeAppData request from " + user.getFullName() + " using "
-        + client.getClientUrn() + "");
-  }
-
-  /**
-   * create a new session with an existing vamsas Document - by copying it into
-   * the session.
-   * 
-   * @param archive
-   */
-  public void setVamsasDocument(File archive) throws IOException {
-    log.debug("Transferring vamsas data from " + archive + " to session:"
-        + vamArchive.sessionFile);
-    SessionFile xtantdoc = new SessionFile(archive);
-    while (!vamArchive.lockFile())
-      log.info("Trying to get lock for " + vamArchive.sessionFile);
-    vamArchive.updateFrom(null, xtantdoc);
-    xtantdoc.unlockFile();
-    unlockVamsasDocument();
-    // TODO: session archive provenance should be updated to reflect import from external source
-    log.debug("Transfer complete.");
-  }
-
-  /**
-   * write session as a new vamsas Document (this will overwrite any existing
-   * file without warning) TODO: test TODO: verify that lock should be released
-   * for vamsas document.
-   * 
-   * @param destarchive
-   */
-  protected void writeVamsasDocument(File destarchive, Lock extlock)
-      throws IOException {
-    log.debug("Transferring vamsas data from " + vamArchive.sessionFile
-        + " to session:" + destarchive);
-    SessionFile newdoc = new SessionFile(destarchive);
-    if (extlock == null && !vamArchive.lockFile())
-      while (!vamArchive.lockFile())
-        log.info("Trying to get lock for " + vamArchive.sessionFile);
-    // TODO: LATER: decide if a provenance entry should be written in the
-    // exported document recording the export from the session
-    newdoc.updateFrom(null, 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();
-    newdoc.unlockFile();
-    log.debug("Transfer complete.");
-  }
-
-  /**
-   * extant archive IO handler
-   */
-  VamsasArchive _va = null;
-
-  /**
-   * Creates a VamsasArchive Vobject for accessing and updating document Note:
-   * this will lock the Vamsas Document for exclusive access to the client.
-   * 
-   * @return session vamsas document
-   * @throws IOException
-   *           if locks fail or vamsas document read fails.
-   */
-  protected VamsasArchive getVamsasDocument() throws IOException {
-    // check we haven't already done this once - probably should be done by
-    // caller
-    if (_va != null)
-      return _va;
-    // 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 (_va != null)
-      _va.closeArchive();
-    _va = null;
-    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
-   * @param suff
-   *          Suffix for name
-   * @return SessionFile object configured for the new file (of length zero)
-   * @throws IOException
-   */
-  protected SessionFile getTempSessionFile(String pref, String suff)
-      throws IOException {
-    File tfile = File.createTempFile(pref, suff, sessionDir);
-    SessionFile tempFile = new SessionFile(tfile);
-    return tempFile;
-  }
-
-  /**
-   * add a IClient to the session
-   * 
-   * add the client to the client list file
-   * 
-   * @param client
-   *          client to add to the session
-   */
-  protected void addClient(SimpleClient client) {
-    if (client == null)
-      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());
-
-      log.debug("Added.");
-      log.debug("Register Client as Active.");
-      try {
-        client.createActiveClientFile();
-      } catch (IOException e) {
-        log.debug("Error during  active client file creation.");
-      }
-      // tracks modification to the client list and readds client to the list
-      getClientWatcherElement().setHandler(new AddClientWatchCallBack(client));
-      getClientWatcherElement().enableWatch();
-
-    }
-  }
-
-  /**
-   * Handler for the client watcher.
-   * 
-   * If (the current client is not in the client list, it is added again;)
-   */
-  private class AddClientWatchCallBack implements WatcherCallBack {
-
-    private SimpleClient client;
-
-    /**
-     * Inits the handler with the client to check in the list
-     * 
-     * @param client
-     *          client to monitor in the client list
-     */
-    protected AddClientWatchCallBack(SimpleClient client) {
-      this.client = client;
-    }
-
-    /**
-     * If the client list is modified, checks if the current is still in the
-     * list. otherwise, readds ti.
-     * 
-     * @return true to enable watcher, or false to disable it in future
-     *         WatcherThread cycles.
-     */
-    public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
-      boolean isWatchEnable = watcher.isWatchEnabled();
-      if (lock == null)// no update on the list
-        return isWatchEnable;
-      log.debug("change on the client list ");
-      if (client != null) {
-
-        // checks if the client is not already in the lists
-        ClientHandle[] cl = clist.retrieveClientList(lock);// clist.retrieveClientList();
-        boolean found = false;
-        if (cl != null) {
-          for (int chi = cl.length - 1; !found && chi > -1; chi--) {
-            found = cl[chi].equals(this.client.getClientHandle());
-          }
-
-        }
-        if (!found) {
-          log.debug("client not in the list ");
-          if (log.isDebugEnabled())
-            log
-                .debug("the client has not been found in the list. Adding it again :"
-                    + cl);
-          addClient(client);
-        } else
-          log.debug("client is in the list");
-
-      }
-      log.debug("isWatchEnable " + isWatchEnable);
-      return isWatchEnable;
-    }
-  }
-
-  /**
-   * 
-   * removes a client from the current session removes the client from the
-   * session client list if the client is the last one from the session
-   * (ClientList), the current session is removed from active session list.
-   * 
-   * The active should add them self to the client list. To insure to close the
-   * session,when the current client is the lact active client, clears the list
-   * of clients and when two cycles to insure there is no more active client,
-   * that otherwise would have readd themself to the list
-   * 
-   * @param client
-   *          client to remove
-   */
-  protected void removeClient(SimpleClient client)// IClient client)
-  {
-    if (client == null) {
-      log.error("Null client passed to removeClient");
-      return;
-    }
-    // ClientSessionFileWatcherElement cwe=getClientWatcherElement();
-    // if (cwe!=null && cwe.isWatchEnabled()) {
-    // cwe.haltWatch();
-    // };
-    // set handler to check is the the last active client of the session
-    // Wait for several watchers cycle to see if the current client was the last
-    // client active in the session.
-    // if yes, close the session
-
-    // getClientWatcherElement().setHandler(new RemoveClientWatchCallBack
-    // (client));
-    // getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);
-    log.info("remove client from list");
-    if (clistWatchElement != null) {
-      clistWatchElement.haltWatch();
-      clistWatchElement.watched.unlockFile();
-    }
-    // clist.clearList();
-    // clist.unlockFile();
-    log.info("list cleared");
-    // if (cwe!=null) {
-    // cwe.enableWatch();
-
-    log.debug("Stopping EventGenerator..");
-    client.evgen.stopWatching();
-    // cwe.setHandler(null);
-    // ask to the client to copy application data into the document
-    client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client, null);
-    boolean closeSession = isLastActiveClient(client);
-    if (closeSession) {
-      if (client.get_session().getUnsavedFlag())
-      {
-        log.debug("Raising request-to-save event");
-        client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
-      }
-      log.debug("Raising session shutdown event");
-      client.evgen._raise(Events.SESSION_SHUTDOWN, null, client
-          .getSessionHandle(), null);
-      log.debug("All events raised for finalising session "+client.getSessionHandle().toString());
-    }
-    // cwe.haltWatch();
-    client.evgen.stopWatching();
-    try {
-      log.debug("Attempting to release active client locks");
-      client.releaseActiveClientFile();
-    } catch (IOException e) {
-      log.error("error during active file client release");
-    }
-    tidyUp();
-    if (closeSession) {
-      log.debug("Last active client: closing session");
-      log.info("Closing session");
-      getSessionManager().removeSession(client.getSessionHandle());
-    }
-  }
-
-  /**
-   * close every file and stop.
-   */
-  private void tidyUp() {
-    if (clist != null)
-      clist.unlockFile();
-    clist = null;
-    storedocfile.unlockFile();
-    storedocfile = null;
-    closeSessionLog();
-  }
-
-  private boolean isLastActiveClient(SimpleClient client) {
-    log.debug("Testing if current client is the last one.");
-    log
-        .debug("current client lockfile is '" + client.getClientlockFile()
-            + "'");
-    boolean noOtherActiveClient = true;
-    // create, if need, subdirectory to contain client files
-    File clientlockFileDir = new File(this.sessionDir, clientFileDirectory);
-    if (!clientlockFileDir.exists()) {
-      log
-          .error("Something wrong the active client file does not exits... should not happen");
-      return false;
-    }
-
-    try {
-
-      // no check every file in the directory and try to get lock on it.
-      File[] clientFiles = clientlockFileDir.listFiles();
-      if (clientFiles == null || clientFiles.length == 0) {// there is not file
-                                                            // on the directory.
-                                                            // the current
-                                                            // client should be
-                                                            // the last one.
-        return true;
-      }
-
-      for (int i = clientFiles.length - 1; i > -1 && noOtherActiveClient; i--) {
-        File clientFile = clientFiles[i];
-        log.debug("testing file for lock: " + clientFile.getAbsolutePath());
-        if (client.getClientLock().isTargetLockFile(clientFile)) {
-          log.debug("current client file found");
-          continue;
-        }
-        if (clientFile != null && clientFile.exists()) {
-          try {
-            log.debug("Try to acquire a lock on the file");
-            // Get a file channel for the file
-            FileChannel channel = new RandomAccessFile(clientFile, "rw")
-                .getChannel();
-
-            // Use the file channel to create a lock on the file.
-            // This method blocks until it can retrieve the lock.
-            // java.nio.channels.FileLock activeClientFilelock = channel.lock();
-
-            // Try acquiring the lock without blocking. This method returns
-            // null or throws an exception if the file is already locked.
-            try {
-              java.nio.channels.FileLock activeClientFilelock = channel
-                  .tryLock();
-
-              // the lock has been acquired.
-              // the file was not lock and so the corresponding application
-              // seems to have die
-              if (activeClientFilelock != null) {
-                log
-                    .debug("lock obtained : file must be from a crashed application");
-
-                activeClientFilelock.release();
-                log.debug("lock released");
-
-                channel.close();
-                log.debug("channel closed");
-
-                // delete file
-                clientFile.delete();
-                log.debug("crashed application file deleted");
-
-              } else {
-                noOtherActiveClient = false;
-                log.debug("lock not obtained : another application is active");
-              }
-            } catch (OverlappingFileLockException e) {
-              // File is already locked in this thread or virtual machine
-              // that the expected behaviour
-              log.debug("lock not accessible ", e);
-            }
-          } catch (Exception e) {
-            log.debug("error during lock testing ", e);
-          }
-        }
-      }
-
-    } catch (Exception e) {
-      log.error("error during counting active clients");
-    }
-    return noOtherActiveClient;
-  }
-
-  /**
-   * Handler for the client watcher. after a client have been removed
-   * 
-   * Checks if the client is not the last active one.
-   * 
-   * If (the current client is not in the client list readd it;)
-   */
-  private class RemoveClientWatchCallBack implements WatcherCallBack {
-
-    private SimpleClient client;
-
-    private boolean manualCheckOfClientCount = false;
-
-    /**
-     * Inits the handler with the client to check in the list
-     * 
-     * @param client
-     *          client to monitor in the client list
-     */
-    protected RemoveClientWatchCallBack(SimpleClient client) {
-      this.client = client;
-    }
-
-    /**
-     * If the client list is modified, checks if the current is still in the
-     * list. otherwise, readds ti.
-     * 
-     * @return true to enable watcher, or false to disable it in future
-     *         WatcherThread cycles.
-     */
-    public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
-      // if lock is null, no client has been added since last, clear.
-      // the client is then the last client
-      if (client != null) {
-
-        if (lock == null) {
-
-          // checks if the client is not already in the lists
-          // ClientHandle[] cl =
-          // clist.retrieveClientList();//lock);//clist.retrieveClientList();
-
-          boolean islastClient = true;
-          if (manualCheckOfClientCount) {
-            log.debug("manual checking of count of client");
-            // checks if the client is not already in the lists
-            ClientHandle[] cl = clist.retrieveClientList();// lock);//clist.retrieveClientList();
-            if (cl == null || cl.length < 1)
-            // {//no client has registered as active
-            {
-              islastClient = true;
-              log.debug("list is empty");
-            } else
-              islastClient = false;
-            log.debug("list is not empty");
-          }
-          // if(cl == null || cl.length<1 )
-          // {//no client has registered as active
-          if (islastClient) {
-            // the client is the last one, so close current session
-            log.info("FROMCLIENTLIST WATCHER: last client removed: closing session");
-            closeSession(client);
-          }
-        } else {
-          log.debug("not the last client found ");
-          // ask to the client to cpoy application data into the document
-          // client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null,
-          // client,null);
-
-          // / }
-
-        }
-        log.debug("Stopping EventGenerator..");
-        // TODO: ensure ClientsFile lock is really released!!
-        // clist.unlockFile();
-        client.evgen.stopWatching();
-      }
-      watcher.setHandler(null);// Do not check if the client is the last
-                                // client. watcher will shutdown anyway
-      // watcher.haltWatch();
-      // watcher.
-      return false;
-    }
-  }
-
-  /**
-   * closes the current session, and send an event to the last client to close
-   * the document
-   * 
-   * @param client
-   *          the last client of the client
-   */
-  private void closeSession(SimpleClient client) {
-    // close document
-    client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
-    log.debug("close document request done");
-    closeSession(client.getSessionHandle());
-  }
-
-  /**
-   * CLoses the current session
-   * 
-   * @param sessionHandle
-   *          sessionHandle of the session to remove
-   */
-  private void closeSession(SessionHandle sessionHandle) {
-    getSessionManager().removeSession(sessionHandle);
-    log.debug("Session removed");
-  }
-
-  /**
-   * @return the sessionManager
-   */
-  protected SimpleSessionManager getSessionManager() {
-    return sessionManager;
-  }
-
-  /**
-   * @param sessionManager
-   *          the sessionManager to set
-   */
-  protected void setSessionManager(SimpleSessionManager sessionManager) {
-    this.sessionManager = sessionManager;
-  }
-
-  public ClientsFile getStoreDocFile() {
-    if (storedocfile == null) {
-
-    }
-    return storedocfile;
-  }
-
-  ClientSessionFileWatcherElement clistWatchElement = null;
-
-  /**
-   * get or create a watcher on clist.
-   * 
-   * @return the contents of clistWatchElement or initialise it
-   */
-  public ClientSessionFileWatcherElement getClientWatcherElement() {
-    if (clistWatchElement == null) {
-      clistWatchElement = new ClientSessionFileWatcherElement(clist, null);
-    }
-    return clistWatchElement;
-  }
-}
+/*\r
+ * This file is part of the Vamsas Client version 0.1. \r
+ * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, \r
+ *  Andrew Waterhouse and Dominik Lindner.\r
+ * \r
+ * Earlier versions have also been incorporated into Jalview version 2.4 \r
+ * since 2008, and TOPALi version 2 since 2007.\r
+ * \r
+ * The Vamsas Client is free software: you can redistribute it and/or modify\r
+ * it under the terms of the GNU Lesser General Public License as published by\r
+ * the Free Software Foundation, either version 3 of the License, or\r
+ * (at your option) any later version.\r
+ *  \r
+ * The Vamsas Client is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU Lesser General Public License for more details.\r
+ * \r
+ * You should have received a copy of the GNU Lesser General Public License\r
+ * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.\r
+ */\r
+package uk.ac.vamsas.client.simpleclient;\r
+\r
+import java.io.File;\r
+import java.io.FileNotFoundException;\r
+import java.io.IOException;\r
+import java.io.RandomAccessFile;\r
+import java.nio.channels.FileChannel;\r
+import java.nio.channels.OverlappingFileLockException;\r
+\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+import org.apache.log4j.FileAppender;\r
+import org.apache.log4j.Logger;\r
+import org.apache.log4j.PatternLayout;\r
+\r
+import uk.ac.vamsas.client.ClientHandle;\r
+import uk.ac.vamsas.client.Events;\r
+import uk.ac.vamsas.client.IClient;\r
+import uk.ac.vamsas.client.SessionHandle;\r
+import uk.ac.vamsas.client.UserHandle;\r
+\r
+/**\r
+ * Does all the IO operations for a SimpleClient instance accessing a\r
+ * SimpleClient vamsas session.\r
+ * \r
+ * Basically, it defines the various standard names for the files in the session\r
+ * directory (that maps to the sessionUrn), provides constructors for the file\r
+ * handlers and watchers of those file entities, and some higher level methods\r
+ * to check and change the state flags for the session.\r
+ * \r
+ * TODO: move the stuff below to the SimpleClientFactory documentation. much may\r
+ * not be valid now : Vamsas client is intialised with a path to create live\r
+ * session directories. This path may contain a vamsas.properties file that sets\r
+ * additional parameters (otherwise client just uses the one on the classpath).\r
+ * \r
+ * A vamsas session consists of : SessionDir - translates to urn of a live\r
+ * session. Contains: Vamsas Document (as a jar), Session client list file, both\r
+ * of which may be locked, and additional temporary versions of these files when\r
+ * write operations are taking place.\r
+ * \r
+ * Zip file entries - vamsasdocument.xml : core info one or more: -\r
+ * <applicationname>.version.sessionnumber.raw (string given in\r
+ * vamsasdocument.xml applicationData entry)\r
+ * \r
+ * Lockfile - filename given in the vamsasdocument.xml. Should be checked for\r
+ * validity by any client and rewritten if necessary. The lockfile can point to\r
+ * the jar itself. Mode of operation. Initially - documentHandler either: -\r
+ * creates a zip for a new session for the client - connect to an existing\r
+ * session zip 1. reads session urn file 2. waits for lock 3. examines session -\r
+ * decide whether to create new application data slice or connect to one stored\r
+ * in session. 4. writes info into session file 5. releases lock and generates\r
+ * local client events. 6. Creates Watcher thread to generate events.\r
+ * \r
+ * During the session - Update watcher checks for file change -\r
+ * \r
+ * Procedures for file based session message exchange - session document\r
+ * modification flag\r
+ * \r
+ */\r
+\r
+public class VamsasSession {\r
+  /**\r
+   * indicator file for informing other processes that they should finalise\r
+   * their vamsas datasets for storing into a vamsas archive.\r
+   */\r
+  public static final String CLOSEANDSAVE_FILE = "stored.log";\r
+\r
+  /**\r
+   * session file storing the last_stored_stat data\r
+   */\r
+  public static final String MODIFIEDDOC_FILE = "modified";\r
+\r
+  private SimpleSessionManager sessionManager = null;\r
+\r
+  /**\r
+   * Count of cycles before considering the current client as the last one of\r
+   * the session (if no other client registered as active )\r
+   */\r
+  private final int watchCycleCountBeforeLastClient = 1220;\r
+\r
+  /**\r
+   * time between checking\r
+   */\r
+  public int WATCH_SLEEP = 30;\r
+\r
+  protected String clientFileDirectory = "clients";\r
+\r
+  /**\r
+   * called to clear update flag after a successful offline storage event\r
+   */\r
+  protected void clearUnsavedFlag() {\r
+    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,\r
+        MODIFIEDDOC_FILE));\r
+    if (!laststored.clearFlag())\r
+      log.warn("Unsaved flag was not cleared for " + sessionDir);\r
+  }\r
+\r
+  /**\r
+   * called to indicate session document has been modified.\r
+   * \r
+   */\r
+  protected void setUnsavedFlag() {\r
+    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,\r
+        MODIFIEDDOC_FILE));\r
+    if (!laststored.setFlag())\r
+      log.warn("Couldn't set the Unsaved flag for " + sessionDir);\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @return true if session document has been modified since last offline\r
+   *         storage event\r
+   */\r
+  protected boolean getUnsavedFlag() {\r
+    SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,\r
+        MODIFIEDDOC_FILE));\r
+    return laststored.checkFlag();\r
+  }\r
+\r
+  /**\r
+   * log file location\r
+   */\r
+  public static final String SESSION_LOG = "Log.txt";\r
+\r
+  private static Log log = LogFactory.getLog(VamsasSession.class);\r
+\r
+  protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");\r
+\r
+  /**\r
+   * the appender that writes to the log file inside the session's directory.\r
+   */\r
+  private FileAppender slogAppender = null;\r
+\r
+  /**\r
+   * setup the sessionLog using Log4j.\r
+   * \r
+   * @throws IOException\r
+   */\r
+  private void initLog() throws IOException {\r
+    // TODO: fix session event logging\r
+    // LATER: make dedicated appender format for session log.\r
+    /*\r
+     * Appender app = slog.getAppender("log4j.appender.SESSIONLOG"); //\r
+     * slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir,\r
+     * SESSION_LOG).getAbsolutePath())); // slog.addAppender(new\r
+     * FileAppender(app.getLayout(), new File(sessionDir,\r
+     * SESSION_LOG).getAbsolutePath())); for (Enumeration e =\r
+     * slog.getAllAppenders() ; e.hasMoreElements() ;) {\r
+     * System.out.println(e.nextElement()); }\r
+     */\r
+\r
+    if (slog != null) {\r
+      File sessionLogFile = new File(this.sessionDir, SESSION_LOG);\r
+      slog.addAppender(slogAppender = new FileAppender(new PatternLayout(\r
+          "%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(),\r
+          true));\r
+    } else {\r
+      log.info("No appender for SessionLog");\r
+    }\r
+  }\r
+\r
+  private void closeSessionLog() {\r
+    if (slog != null) {\r
+      if (slogAppender != null) {\r
+        slog.removeAppender(slogAppender);\r
+        slogAppender.close();\r
+        slogAppender = null;\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * the sessionDir is given as the session location for new clients.\r
+   */\r
+  protected File sessionDir;\r
+\r
+  /**\r
+   * holds the list of attached clients\r
+   */\r
+  ClientsFile clist;\r
+\r
+  public static final String CLIENT_LIST = "Clients.obj";\r
+\r
+  /**\r
+   * holds the data\r
+   */\r
+  VamsasFile vamArchive;\r
+\r
+  public static final String VAMSAS_OBJ = "VamDoc.jar";\r
+\r
+  /**\r
+   * sets up the vamsas session files and watchers in sessionDir1\r
+   * \r
+   * @param sessionDir1\r
+   */\r
+  protected VamsasSession(File sessionDir1) throws IOException {\r
+    this(sessionDir1, null);\r
+  }\r
+\r
+  /**\r
+   * sets up the vamsas session files and watchers in sessionDir1\r
+   * \r
+   * @param sessionDir1\r
+   * @param extVamDoc\r
+   *          null or an existing archive to initialise the session with\r
+   * @throws any\r
+   *           IOExceptions from creating session directory and files.\r
+   * @throws error\r
+   *           if both extVamDoc and sessionDir1 already exist (cannot import\r
+   *           new data into session in this way)\r
+   */\r
+  protected VamsasSession(File sessionDir1, File extVamDoc) throws IOException {\r
+    if (sessionDir1 == null)\r
+      throw new Error("Null directory for VamsasSession.");\r
+    if (!sessionDir1.exists() && !sessionDir1.mkdir()) {\r
+      throw new IOException("Failed to make VamsasSession directory in "\r
+          + sessionDir1);\r
+    }\r
+    if (!sessionDir1.isDirectory() || !sessionDir1.canWrite()\r
+        || !sessionDir1.canRead()) {\r
+      throw new IOException("Cannot access '" + sessionDir1\r
+          + "' as a read/writable Directory.");\r
+    }\r
+    boolean existingSession = checkSessionFiles(sessionDir1);\r
+    if (existingSession) {\r
+      if (extVamDoc != null) {\r
+        throw new Error(\r
+            "Client Initialisation Error: Cannot join an existing session directory with an  existing vamsas document to import.");\r
+      } else {\r
+        log.debug("Joining an existing session.");\r
+      }\r
+    }\r
+    this.sessionDir = sessionDir1;\r
+    initSessionObjects();\r
+    if (existingSession) {\r
+      slog.debug("Initialising additional VamsasSession instance");\r
+    } else {\r
+      slog.debug("Founding client has joined VamsasSession instance");\r
+    }\r
+\r
+    log.debug("Attached to VamsasSession in " + sessionDir1);\r
+    if (extVamDoc != null) {\r
+      setVamsasDocument(extVamDoc);\r
+    }\r
+    slog.debug("Session directory created.");\r
+    log.debug("Initialised VamsasSession in " + sessionDir1);\r
+  }\r
+\r
+  /**\r
+   * tests presence of existing sessionfiles files in dir\r
+   * \r
+   * @param dir\r
+   * @return\r
+   */\r
+  private boolean checkSessionFiles(File dir) throws IOException {\r
+    File c_file = new File(dir, CLIENT_LIST);\r
+    File v_doc = new File(dir, VAMSAS_OBJ);\r
+    if (c_file.exists() && v_doc.exists())\r
+      return true;\r
+    return false;\r
+  }\r
+\r
+  /**\r
+   * create new empty files in dir\r
+   * \r
+   */\r
+  private void createSessionFiles() throws IOException {\r
+    if (sessionDir == null)\r
+      throw new IOException(\r
+          "Invalid call to createSessionFiles() with null sessionDir");\r
+    File c_file = new File(sessionDir, CLIENT_LIST);\r
+    File v_doc = new File(sessionDir, VAMSAS_OBJ);\r
+    if (!c_file.exists() && c_file.createNewFile())\r
+      log.debug("Created new ClientFile " + c_file); // don't care if this\r
+    // works or not\r
+    if (!v_doc.exists()) {\r
+      if (v_doc.createNewFile()) {\r
+        log.debug("Created new Vamsas Session Document File " + v_doc);\r
+      } else {\r
+        log.warn("Didn't create Vamsas Session Document file in " + v_doc);\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * construct SessionFile objects and watchers for each\r
+   */\r
+  private void initSessionObjects() throws IOException {\r
+    createSessionFiles();\r
+    if (clist != null || vamArchive != null)\r
+      throw new IOException(\r
+          "initSessionObjects called for initialised VamsasSession object.");\r
+    clist = new ClientsFile(new File(sessionDir, CLIENT_LIST));\r
+    vamArchive = new VamsasFile(new File(sessionDir, VAMSAS_OBJ));\r
+    storedocfile = new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));\r
+    initLog();\r
+  }\r
+\r
+  /**\r
+   * make a new watcher object for the clientFile\r
+   * \r
+   * @return new ClientFile watcher instance\r
+   */\r
+  public FileWatcher getClientWatcher() {\r
+    return new FileWatcher(clist.sessionFile);\r
+  }\r
+\r
+  /**\r
+   * make a new watcher object for the vamsas Document\r
+   * \r
+   * @return new ClientFile watcher instance\r
+   */\r
+  public FileWatcher getDocWatcher() {\r
+    return new FileWatcher(vamArchive.sessionFile);\r
+  }\r
+\r
+  FileWatcher store_doc_file = null;\r
+\r
+  public ClientsFile storedocfile = null;\r
+\r
+  /**\r
+   * make a new watcher object for the messages file\r
+   * \r
+   * @return new watcher instance\r
+   */\r
+  public FileWatcher getStoreWatcher() {\r
+    return new FileWatcher(new File(sessionDir, CLOSEANDSAVE_FILE));\r
+\r
+  }\r
+\r
+  /**\r
+   * write to the StoreWatcher file to indicate that a storeDocumentRequest has\r
+   * been made. The local client's storeWatcher FileWatcher object is updated so\r
+   * the initial change is not registered.\r
+   * \r
+   * @param client\r
+   * @param user\r
+   * @return\r
+   */\r
+  public void addStoreDocumentRequest(ClientHandle client, UserHandle user)\r
+      throws IOException {\r
+    // TODO: replace this with clientsFile mechanism\r
+    SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));\r
+    while (!sfw.lockFile())\r
+      log.debug("Trying to get lock for " + CLOSEANDSAVE_FILE);\r
+    RandomAccessFile sfwfile = sfw.fileLock.getRaFile();\r
+    sfwfile.setLength(0); // wipe out any old info.\r
+    // TODO: rationalise what gets written to this file (ie do we want other\r
+    // clients to read the id of the requestor?)\r
+    sfwfile.writeUTF(client.getClientUrn() + ":" + user.getFullName() + "@"\r
+        + user.getOrganization());\r
+    sfw.unlockFile();\r
+    if (store_doc_file != null)\r
+      store_doc_file.setState();\r
+    slog.info("FinalizeAppData request from " + user.getFullName() + " using "\r
+        + client.getClientUrn() + "");\r
+  }\r
+\r
+  /**\r
+   * create a new session with an existing vamsas Document - by copying it into\r
+   * the session.\r
+   * \r
+   * @param archive\r
+   */\r
+  public void setVamsasDocument(File archive) throws IOException {\r
+    log.debug("Transferring vamsas data from " + archive + " to session:"\r
+        + vamArchive.sessionFile);\r
+    SessionFile xtantdoc = new SessionFile(archive);\r
+    while (!vamArchive.lockFile())\r
+      log.info("Trying to get lock for " + vamArchive.sessionFile);\r
+    vamArchive.updateFrom(null, xtantdoc);\r
+    xtantdoc.unlockFile();\r
+    unlockVamsasDocument();\r
+    // TODO: session archive provenance should be updated to reflect import from\r
+    // external source\r
+    log.debug("Transfer complete.");\r
+  }\r
+\r
+  /**\r
+   * write session as a new vamsas Document (this will overwrite any existing\r
+   * file without warning) TODO: test TODO: verify that lock should be released\r
+   * for vamsas document.\r
+   * \r
+   * @param destarchive\r
+   */\r
+  protected void writeVamsasDocument(File destarchive, Lock extlock)\r
+      throws IOException {\r
+    log.debug("Transferring vamsas data from " + vamArchive.sessionFile\r
+        + " to session:" + destarchive);\r
+    SessionFile newdoc = new SessionFile(destarchive);\r
+    if (extlock == null && !vamArchive.lockFile())\r
+      while (!vamArchive.lockFile())\r
+        log.info("Trying to get lock for " + vamArchive.sessionFile);\r
+    // TODO: LATER: decide if a provenance entry should be written in the\r
+    // exported document recording the export from the session\r
+    newdoc.updateFrom(null, vamArchive);\r
+    // LATER: LATER: fix use of updateFrom for file systems where locks cannot\r
+    // be made (because they don't have a lockManager, ie NFS/Unix, etc).\r
+    vamArchive.unLock();\r
+    newdoc.unlockFile();\r
+    log.debug("Transfer complete.");\r
+  }\r
+\r
+  /**\r
+   * extant archive IO handler\r
+   */\r
+  VamsasArchive _va = null;\r
+\r
+  /**\r
+   * Creates a VamsasArchive Vobject for accessing and updating document Note:\r
+   * this will lock the Vamsas Document for exclusive access to the client.\r
+   * \r
+   * @return session vamsas document\r
+   * @throws IOException\r
+   *           if locks fail or vamsas document read fails.\r
+   */\r
+  protected VamsasArchive getVamsasDocument() throws IOException {\r
+    // check we haven't already done this once - probably should be done by\r
+    // caller\r
+    if (_va != null)\r
+      return _va;\r
+    // patiently wait for a lock on the document. (from\r
+    // ArchiveClient.getUpdateable())\r
+    long tries = 5000;\r
+    while (vamArchive.getLock() == null && --tries > 0) {\r
+      // Thread.sleep(1);\r
+      log.debug("Trying to get a document lock for the " + tries + "'th time.");\r
+    }\r
+    if (tries == 0)\r
+      throw new IOException("Failed to get lock for vamsas archive.");\r
+\r
+    VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true,\r
+        vamArchive);\r
+\r
+    return va;\r
+  }\r
+\r
+  /**\r
+   * Unlocks the vamsas archive session document after it has been closed.\r
+   * \r
+   * @throws IOException\r
+   */\r
+  protected void unlockVamsasDocument() throws IOException {\r
+    if (_va != null)\r
+      _va.closeArchive();\r
+    _va = null;\r
+    if (vamArchive != null)\r
+      vamArchive.unLock();\r
+\r
+  }\r
+\r
+  /**\r
+   * create a uniquely named\r
+   * uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in\r
+   * the session Directory\r
+   * \r
+   * @see java.io.File.createTempFile\r
+   * @param pref\r
+   *          Prefix for name\r
+   * @param suff\r
+   *          Suffix for name\r
+   * @return SessionFile object configured for the new file (of length zero)\r
+   * @throws IOException\r
+   */\r
+  protected SessionFile getTempSessionFile(String pref, String suff)\r
+      throws IOException {\r
+    File tfile = File.createTempFile(pref, suff, sessionDir);\r
+    SessionFile tempFile = new SessionFile(tfile);\r
+    return tempFile;\r
+  }\r
+\r
+  /**\r
+   * add a IClient to the session\r
+   * \r
+   * add the client to the client list file\r
+   * \r
+   * @param client\r
+   *          client to add to the session\r
+   */\r
+  protected void addClient(SimpleClient client) {\r
+    if (client == null)\r
+      slog.error("Try to add a null client to the session ");\r
+    else {\r
+      log.debug("Adding client " + client.getClientHandle().getClientUrn());\r
+      getClientWatcherElement().haltWatch();\r
+      clist.addClient(client.getClientHandle());\r
+\r
+      log.debug("Added.");\r
+      log.debug("Register Client as Active.");\r
+      try {\r
+        client.createActiveClientFile();\r
+      } catch (IOException e) {\r
+        log.debug("Error during  active client file creation.");\r
+      }\r
+      // tracks modification to the client list and readds client to the list\r
+      getClientWatcherElement().setHandler(new AddClientWatchCallBack(client));\r
+      getClientWatcherElement().enableWatch();\r
+\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Handler for the client watcher.\r
+   * \r
+   * If (the current client is not in the client list, it is added again;)\r
+   */\r
+  private class AddClientWatchCallBack implements WatcherCallBack {\r
+\r
+    private SimpleClient client;\r
+\r
+    /**\r
+     * Inits the handler with the client to check in the list\r
+     * \r
+     * @param client\r
+     *          client to monitor in the client list\r
+     */\r
+    protected AddClientWatchCallBack(SimpleClient client) {\r
+      this.client = client;\r
+    }\r
+\r
+    /**\r
+     * If the client list is modified, checks if the current is still in the\r
+     * list. otherwise, readds ti.\r
+     * \r
+     * @return true to enable watcher, or false to disable it in future\r
+     *         WatcherThread cycles.\r
+     */\r
+    public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {\r
+      boolean isWatchEnable = watcher.isWatchEnabled();\r
+      if (lock == null)// no update on the list\r
+        return isWatchEnable;\r
+      log.debug("change on the client list ");\r
+      if (client != null) {\r
+\r
+        // checks if the client is not already in the lists\r
+        ClientHandle[] cl = clist.retrieveClientList(lock);// clist.retrieveClientList();\r
+        boolean found = false;\r
+        if (cl != null) {\r
+          for (int chi = cl.length - 1; !found && chi > -1; chi--) {\r
+            found = cl[chi].equals(this.client.getClientHandle());\r
+          }\r
+\r
+        }\r
+        if (!found) {\r
+          log.debug("client not in the list ");\r
+          if (log.isDebugEnabled())\r
+            log\r
+                .debug("the client has not been found in the list. Adding it again :"\r
+                    + cl);\r
+          addClient(client);\r
+        } else\r
+          log.debug("client is in the list");\r
+\r
+      }\r
+      log.debug("isWatchEnable " + isWatchEnable);\r
+      return isWatchEnable;\r
+    }\r
+  }\r
+\r
+  /**\r
+   * \r
+   * removes a client from the current session removes the client from the\r
+   * session client list if the client is the last one from the session\r
+   * (ClientList), the current session is removed from active session list.\r
+   * \r
+   * The active should add them self to the client list. To insure to close the\r
+   * session,when the current client is the lact active client, clears the list\r
+   * of clients and when two cycles to insure there is no more active client,\r
+   * that otherwise would have readd themself to the list\r
+   * \r
+   * @param client\r
+   *          client to remove\r
+   */\r
+  protected void removeClient(SimpleClient client)// IClient client)\r
+  {\r
+    if (client == null) {\r
+      log.error("Null client passed to removeClient");\r
+      return;\r
+    }\r
+    // ClientSessionFileWatcherElement cwe=getClientWatcherElement();\r
+    // if (cwe!=null && cwe.isWatchEnabled()) {\r
+    // cwe.haltWatch();\r
+    // };\r
+    // set handler to check is the the last active client of the session\r
+    // Wait for several watchers cycle to see if the current client was the last\r
+    // client active in the session.\r
+    // if yes, close the session\r
+\r
+    // getClientWatcherElement().setHandler(new RemoveClientWatchCallBack\r
+    // (client));\r
+    // getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);\r
+    log.info("remove client from list");\r
+    if (clistWatchElement != null) {\r
+      clistWatchElement.haltWatch();\r
+      clistWatchElement.watched.unlockFile();\r
+    }\r
+    // clist.clearList();\r
+    // clist.unlockFile();\r
+    log.info("list cleared");\r
+    // if (cwe!=null) {\r
+    // cwe.enableWatch();\r
+\r
+    log.debug("Stopping EventGenerator..");\r
+    client.evgen.stopWatching();\r
+    // cwe.setHandler(null);\r
+    // ask to the client to copy application data into the document\r
+    client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client, null);\r
+    boolean closeSession = isLastActiveClient(client);\r
+    if (closeSession) {\r
+      if (client.get_session().getUnsavedFlag()) {\r
+        log.debug("Raising request-to-save event");\r
+        client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);\r
+      }\r
+      log.debug("Raising session shutdown event");\r
+      client.evgen._raise(Events.SESSION_SHUTDOWN, null, client\r
+          .getSessionHandle(), null);\r
+      log.debug("All events raised for finalising session "\r
+          + client.getSessionHandle().toString());\r
+    }\r
+    // cwe.haltWatch();\r
+    client.evgen.stopWatching();\r
+    try {\r
+      log.debug("Attempting to release active client locks");\r
+      client.releaseActiveClientFile();\r
+    } catch (IOException e) {\r
+      log.error("error during active file client release");\r
+    }\r
+    tidyUp();\r
+    if (closeSession) {\r
+      log.debug("Last active client: closing session");\r
+      log.info("Closing session");\r
+      getSessionManager().removeSession(client.getSessionHandle());\r
+    }\r
+  }\r
+\r
+  /**\r
+   * close every file and stop.\r
+   */\r
+  private void tidyUp() {\r
+    if (clist != null)\r
+      clist.unlockFile();\r
+    clist = null;\r
+    storedocfile.unlockFile();\r
+    storedocfile = null;\r
+    closeSessionLog();\r
+  }\r
+\r
+  private boolean isLastActiveClient(SimpleClient client) {\r
+    log.debug("Testing if current client is the last one.");\r
+    log\r
+        .debug("current client lockfile is '" + client.getClientlockFile()\r
+            + "'");\r
+    boolean noOtherActiveClient = true;\r
+    // create, if need, subdirectory to contain client files\r
+    File clientlockFileDir = new File(this.sessionDir, clientFileDirectory);\r
+    if (!clientlockFileDir.exists()) {\r
+      log\r
+          .error("Something wrong the active client file does not exits... should not happen");\r
+      return false;\r
+    }\r
+\r
+    try {\r
+\r
+      // no check every file in the directory and try to get lock on it.\r
+      File[] clientFiles = clientlockFileDir.listFiles();\r
+      if (clientFiles == null || clientFiles.length == 0) {// there is not file\r
+        // on the directory.\r
+        // the current\r
+        // client should be\r
+        // the last one.\r
+        return true;\r
+      }\r
+\r
+      for (int i = clientFiles.length - 1; i > -1 && noOtherActiveClient; i--) {\r
+        File clientFile = clientFiles[i];\r
+        log.debug("testing file for lock: " + clientFile.getAbsolutePath());\r
+        if (client.getClientLock().isTargetLockFile(clientFile)) {\r
+          log.debug("current client file found");\r
+          continue;\r
+        }\r
+        if (clientFile != null && clientFile.exists()) {\r
+          try {\r
+            log.debug("Try to acquire a lock on the file");\r
+            // Get a file channel for the file\r
+            FileChannel channel = new RandomAccessFile(clientFile, "rw")\r
+                .getChannel();\r
+\r
+            // Use the file channel to create a lock on the file.\r
+            // This method blocks until it can retrieve the lock.\r
+            // java.nio.channels.FileLock activeClientFilelock = channel.lock();\r
+\r
+            // Try acquiring the lock without blocking. This method returns\r
+            // null or throws an exception if the file is already locked.\r
+            try {\r
+              java.nio.channels.FileLock activeClientFilelock = channel\r
+                  .tryLock();\r
+\r
+              // the lock has been acquired.\r
+              // the file was not lock and so the corresponding application\r
+              // seems to have die\r
+              if (activeClientFilelock != null) {\r
+                log\r
+                    .debug("lock obtained : file must be from a crashed application");\r
+\r
+                activeClientFilelock.release();\r
+                log.debug("lock released");\r
+\r
+                channel.close();\r
+                log.debug("channel closed");\r
+\r
+                // delete file\r
+                clientFile.delete();\r
+                log.debug("crashed application file deleted");\r
+\r
+              } else {\r
+                noOtherActiveClient = false;\r
+                log.debug("lock not obtained : another application is active");\r
+              }\r
+            } catch (OverlappingFileLockException e) {\r
+              // File is already locked in this thread or virtual machine\r
+              // that the expected behaviour\r
+              log.debug("lock not accessible ", e);\r
+            }\r
+          } catch (Exception e) {\r
+            log.debug("error during lock testing ", e);\r
+          }\r
+        }\r
+      }\r
+\r
+    } catch (Exception e) {\r
+      log.error("error during counting active clients");\r
+    }\r
+    return noOtherActiveClient;\r
+  }\r
+\r
+  /**\r
+   * Handler for the client watcher. after a client have been removed\r
+   * \r
+   * Checks if the client is not the last active one.\r
+   * \r
+   * If (the current client is not in the client list readd it;)\r
+   */\r
+  private class RemoveClientWatchCallBack implements WatcherCallBack {\r
+\r
+    private SimpleClient client;\r
+\r
+    private boolean manualCheckOfClientCount = false;\r
+\r
+    /**\r
+     * Inits the handler with the client to check in the list\r
+     * \r
+     * @param client\r
+     *          client to monitor in the client list\r
+     */\r
+    protected RemoveClientWatchCallBack(SimpleClient client) {\r
+      this.client = client;\r
+    }\r
+\r
+    /**\r
+     * If the client list is modified, checks if the current is still in the\r
+     * list. otherwise, readds ti.\r
+     * \r
+     * @return true to enable watcher, or false to disable it in future\r
+     *         WatcherThread cycles.\r
+     */\r
+    public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {\r
+      // if lock is null, no client has been added since last, clear.\r
+      // the client is then the last client\r
+      if (client != null) {\r
+\r
+        if (lock == null) {\r
+\r
+          // checks if the client is not already in the lists\r
+          // ClientHandle[] cl =\r
+          // clist.retrieveClientList();//lock);//clist.retrieveClientList();\r
+\r
+          boolean islastClient = true;\r
+          if (manualCheckOfClientCount) {\r
+            log.debug("manual checking of count of client");\r
+            // checks if the client is not already in the lists\r
+            ClientHandle[] cl = clist.retrieveClientList();// lock);//clist.retrieveClientList();\r
+            if (cl == null || cl.length < 1)\r
+            // {//no client has registered as active\r
+            {\r
+              islastClient = true;\r
+              log.debug("list is empty");\r
+            } else\r
+              islastClient = false;\r
+            log.debug("list is not empty");\r
+          }\r
+          // if(cl == null || cl.length<1 )\r
+          // {//no client has registered as active\r
+          if (islastClient) {\r
+            // the client is the last one, so close current session\r
+            log\r
+                .info("FROMCLIENTLIST WATCHER: last client removed: closing session");\r
+            closeSession(client);\r
+          }\r
+        } else {\r
+          log.debug("not the last client found ");\r
+          // ask to the client to cpoy application data into the document\r
+          // client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null,\r
+          // client,null);\r
+\r
+          // / }\r
+\r
+        }\r
+        log.debug("Stopping EventGenerator..");\r
+        // TODO: ensure ClientsFile lock is really released!!\r
+        // clist.unlockFile();\r
+        client.evgen.stopWatching();\r
+      }\r
+      watcher.setHandler(null);// Do not check if the client is the last\r
+      // client. watcher will shutdown anyway\r
+      // watcher.haltWatch();\r
+      // watcher.\r
+      return false;\r
+    }\r
+  }\r
+\r
+  /**\r
+   * closes the current session, and send an event to the last client to close\r
+   * the document\r
+   * \r
+   * @param client\r
+   *          the last client of the client\r
+   */\r
+  private void closeSession(SimpleClient client) {\r
+    // close document\r
+    client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);\r
+    log.debug("close document request done");\r
+    closeSession(client.getSessionHandle());\r
+  }\r
+\r
+  /**\r
+   * CLoses the current session\r
+   * \r
+   * @param sessionHandle\r
+   *          sessionHandle of the session to remove\r
+   */\r
+  private void closeSession(SessionHandle sessionHandle) {\r
+    getSessionManager().removeSession(sessionHandle);\r
+    log.debug("Session removed");\r
+  }\r
+\r
+  /**\r
+   * @return the sessionManager\r
+   */\r
+  protected SimpleSessionManager getSessionManager() {\r
+    return sessionManager;\r
+  }\r
+\r
+  /**\r
+   * @param sessionManager\r
+   *          the sessionManager to set\r
+   */\r
+  protected void setSessionManager(SimpleSessionManager sessionManager) {\r
+    this.sessionManager = sessionManager;\r
+  }\r
+\r
+  public ClientsFile getStoreDocFile() {\r
+    if (storedocfile == null) {\r
+\r
+    }\r
+    return storedocfile;\r
+  }\r
+\r
+  ClientSessionFileWatcherElement clistWatchElement = null;\r
+\r
+  /**\r
+   * get or create a watcher on clist.\r
+   * \r
+   * @return the contents of clistWatchElement or initialise it\r
+   */\r
+  public ClientSessionFileWatcherElement getClientWatcherElement() {\r
+    if (clistWatchElement == null) {\r
+      clistWatchElement = new ClientSessionFileWatcherElement(clist, null);\r
+    }\r
+    return clistWatchElement;\r
+  }\r
+}\r