reorganising vamsasArchive (write to temp file, rather than write to original filename)
authorjprocter <jprocter@compbio.dundee.ac.uk>
Mon, 21 Nov 2005 17:49:19 +0000 (17:49 +0000)
committerjprocter <jprocter@compbio.dundee.ac.uk>
Mon, 21 Nov 2005 17:49:19 +0000 (17:49 +0000)
Added File constructor for sessionFile for convenience.

git-svn-id: https://svn.lifesci.dundee.ac.uk/svn/repository/trunk@94 be28352e-c001-0410-b1a7-c7978e42abec

src/org/vamsas/client/simpleclient/Lock.java
src/org/vamsas/client/simpleclient/SessionFile.java
src/org/vamsas/client/simpleclient/VamsasArchive.java
src/org/vamsas/client/simpleclient/VamsasArchiveReader.java

index 175728e..c840440 100644 (file)
@@ -55,6 +55,7 @@ public class Lock {
   }
   public void release() {
     try {
+      // TODO: verify that channel.close should be called after release() for rigourous locking. 
       if (lock!=null && lock.isValid())
         lock.release();
       if (rafile!=null && rafile.getChannel().isOpen())
index b2538ab..b43b1a9 100644 (file)
@@ -16,6 +16,11 @@ public class SessionFile {
   protected File sessionFile;
   protected Lock fileLock = null;
 
+  protected SessionFile(File file) {
+    super();
+    sessionFile = file;
+  }
+
   protected boolean lockFile(Lock extantlock) {
     if (fileLock!=null && !fileLock.isLocked()) {
       fileLock.release();// tidy up invalid lock
@@ -51,7 +56,7 @@ public class SessionFile {
       }
     } else
       throw new Error(
-          "org.vamsas.client.simpleclient.ClientsFile.lockList called for non-initialised ClientsFile!");
+          "org.vamsas.client.simpleclient.SessionFile.lockFile called for non-initialised SessionFile!");
   
     // no lock possible
     return false;
index e008464..c3dc225 100644 (file)
@@ -1,19 +1,39 @@
 package org.vamsas.client.simpleclient;
 
+import java.io.BufferedOutputStream;
+import java.io.DataOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
 import java.util.jar.JarOutputStream;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 /**
  * Class for creating a vamsas archive
- * (with backups) 
+ * (with backups)
+ * Writes to a temporary file and then swaps new file for backup.
+ * uses the sessionFile locking mechanism for safe I/O
  * @author jimp
  *
  */
 public class VamsasArchive {
+  private static Log log = LogFactory.getLog(VamsasArchive.class);
   java.io.File archive;
-  java.io.File backup=null;
+  SessionFile rchive=null; 
+  java.io.File original=null;
+  SessionFile odoclock = null;
+  VamsasArchiveReader odoc = null;
   boolean vamsasdocument=true;  // make a document archive (rather than a vamsas.xml archive)
   JarOutputStream newarchive=null;
+  Hashtable entries = null;
+  
   /**
    * Create a new vamsas archive
    * @param archive - file spec for new vamsas archive
@@ -21,9 +41,296 @@ public class VamsasArchive {
    */
   public VamsasArchive(File archive, boolean vamsasdocument) {
     super();
-    this.archive = archive;
-    
+    if (archive==null || (archive!=null && archive.canWrite())) {
+      log.fatal("Invalid parameters for VamsasArchive constructor:"+((archive!=null) 
+          ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
+    }
     this.vamsasdocument = vamsasdocument;
+    if (archive.exists()) {
+      this.original = archive;
+      this.archive = null;
+      // make the archive temp file when the open method is called
+    } else {
+      this.original = null;
+      this.archive = archive; // write directly to the new archive
+    }
+  }
+  /**
+   * called by app to determine if a backup will be made or not.
+   * @return
+   */
+  public File backupFile() {
+    
+    if (original!=null) {
+      makeBackup();
+      return ((original==null) ? originalBackup : null);
+    
+    }
+    return null;
+  }
+  
+  protected String getDocumentJarEntry() {
+    if (vamsasdocument)
+      return VamsasArchiveReader.VAMSASDOC;
+    return VamsasArchiveReader.VAMSASXML;
+  }
+  protected boolean isDocumentWritten() {
+    if (newarchive==null)
+      log.warn("isDocumentWritten called for unopened archive.");
+    if (entries!=null) {
+      if (entries.containsKey(getDocumentJarEntry()))
+          return true;
+    }
+    return false;
+  }
+  /**
+   * Add unique entry strings to internal JarEntries list.
+   * @param entry
+   * @return true if entry was unique and was added.
+   */
+  private boolean addEntry(String entry) {
+    if (entries!=null)
+      entries=new Hashtable();
+    if (entries.containsKey(entry))
+      return false;
+    entries.put(entry, new Integer(entries.size()));
+    return true;
+  }
+  /**
+   * adds named entry to newarchive or returns false.
+   * @param entry
+   * @return true if entry was unique and could be added
+   * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
+   */
+  private boolean addValidEntry(String entry) throws IOException {
+    JarEntry je = new JarEntry(entry);
+    if (!addEntry(entry))
+      return false;
+    newarchive.putNextEntry(je);
+    return true;
+  }
+  
+  File originalBackup = null;
+  
+  private void makeBackup() {
+    if (originalBackup!=null && original!=null && original.exists()) {
+      try {
+        accessBackup();
+        originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
+        // rchive.fileLock.rafile.getChannel().truncate(0);
+      }
+      catch (IOException e) {
+        log.warn("Problem whilst making a backup of original archive.",e);
+      }
+    }
+  }
+  File tempoutput = null;
+  SessionFile trchive = null;
+  private void openArchive() throws IOException {
+    
+    if (newarchive!=null)
+      throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
+
+    if (archive==null) {
+      if (original==null) {
+        throw new IOException("Badly initialised VamsasArchive object - null archive file.");
+      }
+      // make a temporary file to write to 
+      // TODO: finish
+        
+    } else {
+      // tempfile is real archive
+    }
+    
+    /* if (archive.exists()) 
+      if (original==null) {
+        original = rchive.backupSessionFile(null, archive.getName(), ".bak", archive.getParentFile());
+        rchive.fileLock.rafile.getChannel().truncate(0);
+      } else
+        throw new IOException("Backup of current VamsasArchive already exists. Fix this BUG");
+    */
+    
+    rchive.lockFile();
+    newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));  
+    entries = new Hashtable();
   }
   
+  /**
+    if (archive.exists()) 
+      if (original==null) {
+        original = rchive.backupSessionFile(null, archive.getName(), ".bak", archive.getParentFile());
+        rchive.fileLock.rafile.getChannel().truncate(0);
+      } else
+    
+   * Safely initializes the VAMSAS XML document Jar Entry. 
+   * @return Writer to pass to the marshalling function.
+   * @throws IOException if a document entry has already been written. 
+   */
+  public PrintWriter getDocumentOutputStream() throws IOException {
+    if (newarchive==null)
+      openArchive();
+    if (!isDocumentWritten()) {
+      try {
+        if (addValidEntry(getDocumentJarEntry())) 
+          return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
+      } catch (Exception e) {
+        log.warn("Problems opening XML document JarEntry stream",e);
+      }
+    } else {
+      throw new IOException("Vamsas Document output stream is already written.");
+    }
+    return null;
+  }
+  /**
+   * Opens and returns the applicationData output stream for the appdataReference string.
+   * TODO: Make a wrapper class to catch calls to OutputStream.close() which normally close the Jar output stream.
+   * @param appdataReference
+   * @return Output stream to write to
+   * @throws IOException
+   */
+  public OutputStream getAppDataStream(String appdataReference) throws IOException {
+    if (newarchive!=null)
+      openArchive();
+    if (addValidEntry(appdataReference)) {
+      return new DataOutputStream(newarchive);
+    }
+    return null;
+  }
+  
+  /**
+   * Stops any current write to archive, and reverts to the backup if it exists.
+   * 
+   */
+  public boolean cancelArchive() {
+    if (newarchive!=null) {
+      try { 
+        newarchive.close();
+      } catch (Exception e) {};
+      if (original!=null) {
+        if (rchive!=null) {
+          try {
+            // recover from backup file.
+            rchive.fileLock.rafile.getChannel().truncate(0);
+            SessionFile bck = new SessionFile(original);
+            if (bck.lockFile()) {
+              rchive.fileLock.rafile.getChannel().transferFrom(
+                  bck.fileLock.rafile.getChannel(), 
+                  0, bck.fileLock.rafile.getChannel().size());
+              bck.unlockFile();
+              closeAndReset();
+              return true;
+            } else {
+              log.warn("Could not get lock on backup file to recover");
+            }
+          }
+          catch (Exception e) {
+            log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
+            return false;
+          }
+        }
+        log.fatal("Attempt to backup from Archive Backup without valid write lock! ('"+archive.getAbsolutePath()+"') - BUG!");
+      }
+    }
+    return false;
+  }
+  
+  /**
+   * only do this if you want to destroy the current file output stream
+   *
+   */
+  private void closeAndReset() {
+    rchive.unlockFile();
+    rchive = null;
+    if (original!=null) {
+      if (odoc!=null) {
+        odoc.close();
+        odoc=null;
+      }
+      original.delete();
+      if (odoclock!=null) {
+        odoclock.unlockFile();
+        odoclock = null;
+      }
+    }
+    newarchive=null;
+    original=null;
+    entries=null;
+  }
+
+  private final int _TRANSFER_BUFFER=4096*4;
+  /**
+   * open backup for exclusive (locked) reading.
+   * @throws IOException
+   */
+  private void accessBackup() throws IOException {
+    if (original!=null && original.exists()) {
+      if (odoclock==null) 
+        odoclock = new SessionFile(original);
+      odoclock.lockFile();
+      if (odoc == null) 
+        odoc = new VamsasArchiveReader(original);
+    }
+  }
+  /**
+   * Convenience method to copy over the referred entry from the backup to the new version.
+   * Warning messages are raised if no backup exists or the 
+   * entry doesn't exist in the backed-up original.
+   * Duplicate writes return true - but a warning message will also be raised.
+   * @param AppDataReference
+   * @return true if AppDataReference now exists in the new document
+   * @throws IOException
+   */
+  public boolean transferAppDataEntry(String AppDataReference) throws IOException {
+    // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
+    if (AppDataReference==null)
+      throw new IOException("Invalid AppData Reference!");
+    if (original==null || !original.exists()) {
+      log.warn("No backup archive exists.");
+      return false;
+    }
+    if (entries.containsKey(AppDataReference)) {
+      log.warn("Attempt to write '"+AppDataReference+"' twice! - IGNORED");
+      return true;
+    }
+    
+    accessBackup();
+    
+    java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
+    
+    if (adstream==null) {
+      log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
+      return false;
+    }
+    
+    java.io.OutputStream adout = getAppDataStream(AppDataReference);
+    // copy over the bytes
+    int written=-1;
+    long count=0;
+    byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
+    do {
+      if ((written = adstream.read(buffer))>-1) {
+        adout.write(buffer, 0, written);
+        log.debug("Transferring "+written+".");
+        count+=written;
+      }
+    } while (written>-1);
+    log.debug("Sucessfully transferred AppData for "+AppDataReference+" ("+count+" bytes)");
+    return true;
+  }
+  
+  /**
+   * Tidies up and closes archive, removing any backups that were created.
+   * NOTE: It is up to the caller to 
+   */
+  public void closeArchive() throws IOException {
+    if (newarchive!=null) {
+      newarchive.closeEntry();
+      if (!isDocumentWritten())
+        log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"'");
+      newarchive.close();
+      closeAndReset();
+    } else {
+      log.warn("Attempt to close archive that has not been opened for writing.");
+    }
+  }
 }
index e04e7c9..f56ed40 100644 (file)
@@ -8,6 +8,9 @@ import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.jar.JarInputStream;
 import java.util.jar.JarOutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 /**
  * Basic methods for accessing an existing Vamsas Archive, 
  * and Jar entry names for creating new vamsas archives.
@@ -16,6 +19,7 @@ import java.util.jar.JarOutputStream;
  *
  */
 public class VamsasArchiveReader {
+  private static Log log = LogFactory.getLog(VamsasArchiveReader.class);
   JarFile jfile;
   public VamsasArchiveReader(File vamsasfile) {
     jfile=null;
@@ -118,6 +122,7 @@ public class VamsasArchiveReader {
    */
   
   public InputStream getVamsasXmlStream() {
+    // log.warn("Deprecated call");
     JarEntry xmle=getVamsasXmlEntry();
     InputStream vdoc;
     if (xmle==null)