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 java.util.Vector; 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; } /** * writes a vector of vorba Ids to the session. * @param modObjects public void setModObjectList(Vector modObjects) { log.debug("Writing "+modObjects.size()+" ids to ModObjectList"); // TODO Auto-generated method stub } ** * get current list of modified objects. * @return null or Vector of objects * public Vector getModObjectList() { log.debug("Reading modObjectList"); return null; } */ }