X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fuk%2Fac%2Fvamsas%2Fclient%2Fsimpleclient%2FVamsasSession.java;h=af5ab37b25821d5db58cf4f73c5d280722cff5c0;hb=844ccad5a3fcbedec17b2af66d460f31abc7cff1;hp=497431db9fc6c22c49bbad5a59c665d7e4226bd4;hpb=0330a8e70848ce136eada4778f72981d9c958166;p=vamsas.git diff --git a/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java b/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java index 497431d..af5ab37 100644 --- a/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java +++ b/src/uk/ac/vamsas/client/simpleclient/VamsasSession.java @@ -1,433 +1,902 @@ -package uk.ac.vamsas.client.simpleclient; - -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.io.RandomAccessFile; -import java.io.Writer; -import java.util.Enumeration; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.apache.log4j.Appender; -import org.apache.log4j.Logger; -import org.apache.log4j.FileAppender; -import org.apache.log4j.PatternLayout; - -import uk.ac.vamsas.client.ClientHandle; -import uk.ac.vamsas.client.IClient; -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: - * - .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; - - /** - * 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"); - /** - * 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(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true)); - } else { - log.info("No appender for SessionLog"); - } - } - - /** - * 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 sessionDir - * @param sessionDir1 - */ - protected VamsasSession(File sessionDir1) throws IOException { - if (sessionDir1==null) - throw new Error("Null directory for VamsasSession."); - if (sessionDir1.exists()) { - if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead()) - throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory."); - if (!checkSessionFiles(sessionDir1)) - log.warn("checkSessionFiles() returned false. Possible client implementation error"); - this.sessionDir = sessionDir1; - initSessionObjects(); - slog.debug("Initialising additional VamsasSession instance"); - log.debug("Attached to VamsasSession in "+sessionDir1); - //} - } else { - // start from scratch - if (!sessionDir1.mkdir()) - throw new IOException("Failed to make VamsasSession directory in "+sessionDir1); - createSessionFiles(); - initSessionObjects(); - 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() && v_doc.createNewFile()) - log.debug("Created new Vamsas Session Document File "+v_doc); - } - /** - * construct SessionFile objects and watchers for each - */ - private void initSessionObjects() throws IOException { - createSessionFiles(); - if (clist!=null || vamArchive!=null) - throw new IOException("initSessionObjects called for initialised VamsasSession object."); - clist = new ClientsFile(new File(sessionDir,CLIENT_LIST)); - vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ)); - storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE)); - initLog(); - } - /** - * 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); - vamArchive.updateFrom(null, xtantdoc); - // LATER: decide if session archive provenance should be updated to reflect access. - // TODO: soon! do a proper import objects from external file - 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(extlock, vamArchive); - // LATER: LATER: fix use of updateFrom for file systems where locks cannot be made (because they don't have a lockManager, ie NFS/Unix, etc). - vamArchive.unLock(); - 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(IClient 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()); - getClientWatcherElement().enableWatch(); - log.debug("Added."); - } - } - -/** - * - * 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. - * - * @param client client to remove - */ - protected void removeClient(IClient client) - { - if (client == null) - { - log.error("Null client passed to removeClient"); - return; - } - SessionFileWatcherElement cwe=getClientWatcherElement(); - if (cwe!=null && cwe.isWatchEnabled()) { - cwe.haltWatch(); - }; - 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()); - } - else - { - int active=clist.retrieveClientList().length; - log.debug("Still "+active+" active clients"); - slog.info("Still "+active+" active clients"); - } - if (cwe!=null) { - cwe.enableWatch(); - } - } -/** - * @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; -} -SessionFileWatcherElement clistWatchElement=null; -public SessionFileWatcherElement getClientWatcherElement() { - if (clistWatchElement==null) { - clistWatchElement=new SessionFileWatcherElement(clist,null); - } - return clistWatchElement; -} -} - - +/* + * This file is part of the Vamsas Client version 0.1. + * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, + * Andrew Waterhouse and Dominik Lindner. + * + * Earlier versions have also been incorporated into Jalview version 2.4 + * since 2008, and TOPALi version 2 since 2007. + * + * The Vamsas Client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Vamsas Client is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the Vamsas Client. If not, see . + */ +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: - + * .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; + } +}