package uk.ac.vamsas.client.simpleclient;
import java.io.File;
+import java.io.FileNotFoundException;
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 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.Appender;
-import org.apache.log4j.Logger;
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
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() {
* make a new watcher object for the clientFile
* @return new ClientFile watcher instance
*/
- public FileWatcher getClientWatcher() {
+ public FileWatcher getClientWatcher() {
return new FileWatcher(clist.sessionFile);
}
/**
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.
* @throws IOException if locks fail or vamsas document read fails.
*/
protected VamsasArchive getVamsasDocument() throws IOException {
- // TODO: check we haven't already done this once - probably should be done by caller
+ // 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) {
* @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
* 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 ");
log.debug("Adding client "+client.getClientHandle().getClientUrn());
getClientWatcherElement().haltWatch();
clist.addClient(client.getClientHandle());
- getClientWatcherElement().enableWatch();
+
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
* 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(IClient client)
+ protected void removeClient(SimpleClient client)//IClient client)
{
if (client == null)
{
log.error("Null client passed to removeClient");
return;
}
- SessionFileWatcherElement cwe=getClientWatcherElement();
+ ClientSessionFileWatcherElement cwe=getClientWatcherElement();
if (cwe!=null && cwe.isWatchEnabled()) {
cwe.haltWatch();
- } else {
- cwe=null;
+ };
+ //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");
+ clist.clearList();
+ 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)
+ {
+ log.debug("Raising request-to-save event");
+ client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
+ client.evgen._raise(Events.SESSION_SHUTDOWN, null, client.getSessionHandle(), null);
+ }
+ try
+ {
+ log.debug("Attempting to release active client locks");
+ client.releaseActiveClientFile();
+ }
+ catch (IOException e)
+ {
+ log.error("error during active file client release");
+ }
+ if (closeSession)
+ {
+ log.debug("Last active client: closing session");
+ log.info("Closing session");
+ getSessionManager().removeSession(client.getSessionHandle());
+ }
}
- 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
- slog.info("last client removed: removing session");
- log.debug("last client removed: removing session");
- this.getSessionManager().removeSession(client.getSessionHandle());
+ }
+
+
+ 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("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;
+ }
}
- else
+
+ /**
+ * 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)
{
- int active=clist.retrieveClientList().length;
- log.debug("Still "+active+" active clients");
- slog.info("Still "+active+" active clients");
+// close document
+ client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client,null);
+ log.debug("close document request done");
+ closeSession(client.getSessionHandle());
}
- if (cwe!=null) {
- cwe.enableWatch();
+
+ /**
+ * 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
*/
}
return storedocfile;
}
-SessionFileWatcherElement clistWatchElement=null;
-public SessionFileWatcherElement getClientWatcherElement() {
+
+ClientSessionFileWatcherElement clistWatchElement=null;
+public ClientSessionFileWatcherElement getClientWatcherElement() {
if (clistWatchElement==null) {
- clistWatchElement=new SessionFileWatcherElement(clist,null);
+ clistWatchElement=new ClientSessionFileWatcherElement(clist,null);
}
return clistWatchElement;
}
+/**
+ * writes a vector of vorba Ids to the session.
+ * @param modObjects
+public void setModObjectList(Vector modObjects) {
+ log.debug("Writing "+modObjects.size()+" ids to ModObjectList");
+ // TODO Auto-generated method stub
+}
+**
+ * get current list of modified objects.
+ * @return null or Vector of objects
+ *
+public Vector getModObjectList() {
+ log.debug("Reading modObjectList");
+ return null;
+}
+*/
}