Added to mechanism to determine if a client closing is the last one of the session.
authorpmarguerite <pmarguerite@issues.jalview.org>
Tue, 12 Jun 2007 10:40:53 +0000 (10:40 +0000)
committerpmarguerite <pmarguerite@issues.jalview.org>
Tue, 12 Jun 2007 10:40:53 +0000 (10:40 +0000)
Each application creates a file with a lock on it in a subdirectory of the session.
When a client is closing, it checks if there is no other locked file.
If no file, it is the last one and closes the sessions.

git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@403 be28352e-c001-0410-b1a7-c7978e42abec

src/uk/ac/vamsas/client/simpleclient/ClientSessionFileWatcherElement.java
src/uk/ac/vamsas/client/simpleclient/SimpleClient.java
src/uk/ac/vamsas/client/simpleclient/SimpleClientFactory.java
src/uk/ac/vamsas/client/simpleclient/VamsasSession.java

index 9d2b2d9..f8e4f12 100644 (file)
@@ -53,6 +53,7 @@ public class ClientSessionFileWatcherElement extends SessionFileWatcherElement {
      catch (Exception e) {
         log.error("Whilst watching "+watcher.getSubject(), e);
       }
+    // log.debug("got lock watcher");
      if (doclock==null)
        {//no change detected
           this.cycleCountSinceModif ++;
@@ -67,7 +68,7 @@ public class ClientSessionFileWatcherElement extends SessionFileWatcherElement {
         //log.debug("no modification");
           return false;
        }
-     this.cycleCountSinceModif =0; //change detected
+     if (this.isTimeOutEnable) this.cycleCountSinceModif =0; //change detected
      if(this.handler != null )
        synchronized (this.handler)
        {
index 455a0f2..ca68e54 100644 (file)
@@ -10,6 +10,10 @@ import java.beans.PropertyChangeListener;
 import java.beans.PropertyChangeSupport;
 import java.io.File;
 import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+
+import java.nio.channels.OverlappingFileLockException;
 import java.util.Hashtable;
 import java.util.Vector;
 
@@ -43,6 +47,15 @@ public class SimpleClient implements IClient {
   protected ClientHandle client = null;
   protected EventGeneratorThread evgen = null;
   protected ClientDocument cdocument = null;
+  
+  
+  
+  
+  private java.nio.channels.FileLock activeClientFilelock = null;
+  
+  private FileChannel activeClientFileChannel = null;
+  private  File clientlockFile = null;
+  
   /**
    * object hash table that persists in each client holding vorbaIds and hash values after a document write
    */
@@ -445,7 +458,7 @@ public class SimpleClient implements IClient {
     // more complex if data is already present in document. Could have a 'clearSession' method, too, or dump and overwrite instead.
     log.error("importDocument is not yet implemented for a SimpleClient Session.");
     
-   /* try {
+    /*try {
       this._session.setVamsasDocument(location);
     } catch (IOException e) {
       log.error("importDocument failed.");
@@ -495,4 +508,102 @@ public class SimpleClient implements IClient {
       pickmanager = new SimplePickManager(new uk.ac.vamsas.client.picking.SocketManager());
     }
   }
+  
+  
+  protected void releaseActiveClientFile() throws IOException
+  {
+   
+    log.debug("Releasing active client file");
+    if( activeClientFilelock != null)
+    // Release the lock
+      activeClientFilelock.release();
+          
+    if (activeClientFileChannel != null)
+        // Close the file
+        activeClientFileChannel.close();
+    if (this.clientlockFile != null)
+    {
+      this.clientlockFile.delete();
+      log.debug("deleted active client lock file");
+    }
+
+  }
+  
+  protected void  createActiveClientFile() throws IOException
+  {
+    if(this.clientlockFile != null )return; 
+   log.debug("createActiveClientFile");
+    //create, if need,  subdirectory to contain client files
+   File clientlockFileDir = new File ( this.get_session().sessionDir, this.get_session().clientFileDirectory);
+    if( !clientlockFileDir.exists())
+      {//the directory does not exist, create it
+        if (! clientlockFileDir.mkdirs())
+        {
+          throw new IOException("Failed to create sub directory to session directory  for client lock files'"+clientlockFileDir.getAbsolutePath()+"'");
+        }
+      }
+    else
+    {
+      if (!(clientlockFileDir.isDirectory() && clientlockFileDir.canWrite()))
+      {
+        throw new IOException("Directory  for client lock files is not a directory or is not accessibl: '"+clientlockFileDir.getAbsolutePath()+"'");
+       }
+    }
+    this.clientlockFile = new File (clientlockFileDir, this.getClientHandle().getClientUrn().replaceAll(File.separator, "").replaceAll(":", "").replaceAll(";", ""));
+   
+    log.debug("Creating active client lock file "+ this.clientlockFile.getAbsolutePath());
+    if (clientlockFile.exists())
+     {//should not happen, file should be deleted when the application closes or when a crashed application has been detected
+       log.error("client lock file already exits");
+     }
+   try {
+     //create the empty file
+     if(! clientlockFile.createNewFile())
+       {
+       log.error("Unable to create active client lock file");
+       
+         return;
+       }
+     else
+       log.debug("file created");
+       // Get a file channel for the file
+       FileChannel channel = new RandomAccessFile(clientlockFile, "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
+          {
+           activeClientFilelock = channel.tryLock();
+           log.debug("Got lock");
+          }
+       catch (OverlappingFileLockException e) 
+       {
+         // File is already locked in this thread or virtual machine
+         log.error("Oups the file is already locked",e);
+         
+       }
+     // Release the lock
+ /*    lock.release();
+       
+     // Close the file
+     channel.close();*/
+ } catch (Exception e) {
+   log.error("Error  during lock file creation",e);
+ }
+
+
+    
+  }
+
+  /**
+   * @return the clientlockFile
+   */
+  protected File getClientlockFile() {
+    return clientlockFile;
+  }
 }
index b4a74d3..74650b0 100644 (file)
@@ -239,7 +239,7 @@ public class SimpleClientFactory implements IClientFactory {
     
   //create simple client
      client = new SimpleClient(userId,  clientHandle,  vamsasSession);
-     vamsasSession.addClient(client);
+     vamsasSession.addClient((SimpleClient) client);
      vamsasSession.setSessionManager(this.getSessionManager());
      return client;
   }
@@ -294,6 +294,8 @@ public class SimpleClientFactory implements IClientFactory {
           //only one session available, open it.
             return this.getIClient(clientHandle,  availableSessions[0]);
           }
+        else
+          log.debug("No active session found");
       }
     //no session available  - create a new one
     
index 61cf0fb..58f442a 100644 (file)
@@ -1,8 +1,11 @@
 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;
@@ -13,6 +16,7 @@ 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 
@@ -83,14 +87,14 @@ public class VamsasSession {
   /**
    * 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 = 1100 ;
+  private final int watchCycleCountBeforeLastClient = 1220 ;
   
   /**
    * time between checking 
    */
   public int WATCH_SLEEP=30; 
   
-  //protected String clientFileDirectory = "clients";
+  protected String clientFileDirectory = "clients";
   
   /**
    * called to clear update flag after a successful offline storage event
@@ -361,7 +365,7 @@ public class VamsasSession {
    * add the client to the client list file
    * @param client client to add to the session
    */
-  protected void addClient(IClient client)
+  protected void addClient(SimpleClient client)
   {
     if (client == null)
       slog.error("Try to add a null client to the session ");
@@ -372,14 +376,15 @@ public class VamsasSession {
      
       log.debug("Added.");
       log.debug("Register Client as Active.");
-     /* try {
+      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();
+     
     }
   }
   
@@ -391,13 +396,13 @@ public class VamsasSession {
   private class AddClientWatchCallBack  implements WatcherCallBack
   {
    
-    private IClient client ;
+    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 (IClient client)
+    protected  AddClientWatchCallBack (SimpleClient client)
       {
         this.client = client;
       }
@@ -411,7 +416,7 @@ public class VamsasSession {
           boolean isWatchEnable = watcher.isWatchEnabled();
           if (lock== null)//no update on the list
             return isWatchEnable;
-        //  log.debug("change on the client list ");
+          log.debug("change on the client list ");
           if (client != null)
             {
         
@@ -436,7 +441,7 @@ public class VamsasSession {
                 log.debug("client is in the list");
 
             }
-          //log.debug("isWatchEnable "+isWatchEnable);
+          log.debug("isWatchEnable "+isWatchEnable);
           return isWatchEnable;
         }
       }
@@ -468,21 +473,38 @@ public class VamsasSession {
     //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().setHandler(new RemoveClientWatchCallBack (client));
     getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);
     log.info("remove client from list");
     clist.clearList();
-    log.info("client list cleared");
+    log.info("list cleared");
     if (cwe!=null) {
       cwe.enableWatch();
       
       
-     /* try {
-        log.debug("Releasing  active client file");
-        client.releaseActiveClientFile();
-      } catch (IOException e) {
-        log.error("error during active file client release");
-      }*/
+      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);
+      
+      if ( this.isLastActiveClient(client))
+        {
+        log.debug("Last active client: closing session");
+        log.info("Closing session");
+          getSessionManager().removeSession(client.getSessionHandle());
+        }
+      
+      try 
+        {
+          log.debug("Releasing  active client file");
+          client.releaseActiveClientFile();
+        }
+      catch (IOException e)
+        {
+          log.error("error during active file client release");
+         }
     }
    
   
@@ -503,6 +525,98 @@ public class VamsasSession {
    
   }
   
+  
+  private boolean isLastActiveClient(SimpleClient client)
+    {
+    log.debug("Testing if current client is the last one.");
+      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.getClientlockFile().equals(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
    * 
@@ -540,8 +654,7 @@ public class VamsasSession {
             
             //checks if the client is not already in the lists
            //   ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
-//            ask to the client to cpoy application data into the document
-              client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null);
+
               boolean islastClient =  true;
               if (manualCheckOfClientCount)
                 {
@@ -564,13 +677,7 @@ public class VamsasSession {
               {
                 //the client is the last one, so close current session
                   log.info("last client removed: closing session");
-
-//                close document 
-                  client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client,null);
-                  log.debug("close document request done");
-                  
-                  getSessionManager().removeSession(client.getSessionHandle());
-                  log.debug("Session removed");
+                  closeSession(client);
               }
             }
               else
@@ -592,6 +699,28 @@ public class VamsasSession {
         }
       }
 
+  /**
+   * 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");
+      this.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
  */