package uk.ac.vamsas.client.simpleclient; import java.io.BufferedWriter; 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 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 uk.ac.vamsas.client.ClientHandle; 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"; /** * 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 { // LATER: make dedicated appender format for session log. Appender app = slog.getAppender("SESSION_LOG"); // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath())); //Appender app = slog.getAppender("SESSION_LOG"); if (app == null) log.info("No appender for SESSION_LOG"); if (slog!= null && app != null) { if (app instanceof FileAppender) { File sessionLogFile = new File(this.sessionDir, ((FileAppender)app).getFile()); slog.addAppender(new FileAppender(app.getLayout(), sessionLogFile.getAbsolutePath())); } // slog.removeAppender("SESSION_LOG"); } } /** * 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)) { createSessionFiles(); } // session files exist in the directory 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); this.sessionDir = 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.createNewFile()) log.debug("Created new ClientFile "+c_file); // don't care if this works or not if (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 { 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)); initLog(); } /** * make a new watcher object for the clientFile * @return new ClientFile watcher instance */ public FileWatcher getClientWatcher() { return new FileWatcher(clist.sessionFile); } FileWatcher session_doc_watcher=null; /** * make a new watcher object for the vamsas Document * @return new ClientFile watcher instance */ public FileWatcher getDocWatcher() { if (session_doc_watcher==null) return session_doc_watcher = new FileWatcher(vamArchive.sessionFile); return new FileWatcher(vamArchive.sessionFile); } FileWatcher store_doc_file=null; /** * make a new watcher object for the messages file * (thread safe - keeps a reference to the first watcher) * @return new watcher instance */ public FileWatcher getStoreWatcher() { if (store_doc_file==null) return store_doc_file = new FileWatcher(new File(CLOSEANDSAVE_FILE)); return new FileWatcher(new File(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 { 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 session archive provenance should be written in vamsasDocument file for this export. 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."); } /** * 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 { // TODO: check we haven't already done this once if (!vamArchive.lockFile()) throw new IOException("Failed to get lock for vamsas archive."); VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive); return va; } /** * create a uniquely named file 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; } }