-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