renamed base class for all vamsas document objects (now org.vamsas.client.Vobject)
[vamsas.git] / src / org / vamsas / client / simpleclient / VamsasSession.java
index e74c458..6f41fb7 100644 (file)
 package org.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.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 org.vamsas.client.ClientHandle;
+import org.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 {
   /**
-   * Holds the file handlers for a session.
+   * 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("org.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()));
+  }
+  
+  /**
+   * the sessionDir is given as the session location for new clients.
    */
   File sessionDir;
+  /**
+   * holds the list of attached clients
+   */
   ClientsFile clist;
-  File vamArchive;
+  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 sessionDir
+   */
+  protected VamsasSession(File sessionDir) throws IOException {
+    if (sessionDir==null)
+      throw new Error("Null directory for VamsasSession.");
+    if (sessionDir.exists()) {
+      if (!sessionDir.isDirectory() || !sessionDir.canWrite() || sessionDir.canRead())
+        throw new IOException("Cannot access '"+sessionDir+"' as a read/writable Directory.");
+      if (checkSessionFiles(sessionDir)) {
+        // session files exist in the directory
+        this.sessionDir = sessionDir;
+        initSessionObjects();
+        slog.debug("Initialising additional VamsasSession instance");
+        log.debug("Attached to VamsasSession in "+sessionDir);
+      } 
+    } else {
+      // start from scratch
+      if (!sessionDir.mkdir())
+        throw new IOException("Failed to make VamsasSession directory in "+sessionDir);
+      this.sessionDir = sessionDir; 
+      createSessionFiles();
+      initSessionObjects();
+      slog.debug("Session directory created.");
+      log.debug("Initialised VamsasSession in "+sessionDir);
+    }
+  }
+  /**
+   * 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);
+    sfw.fileLock.rafile.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?)
+    sfw.fileLock.rafile.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
+   */
+  protected 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;
+  }
   
 }
+
+