applied LGPLv3 and source code formatting.
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / VamsasArchive.java
index c01ac26..9306ca7 100644 (file)
-package uk.ac.vamsas.client.simpleclient;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.Hashtable;
-import java.util.Iterator;
-import java.util.Vector;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-import uk.ac.vamsas.client.AppDataOutputStream;
-import uk.ac.vamsas.client.ClientHandle;
-import uk.ac.vamsas.client.IVorbaIdFactory;
-import uk.ac.vamsas.client.SessionHandle;
-import uk.ac.vamsas.client.UserHandle;
-import uk.ac.vamsas.client.Vobject;
-import uk.ac.vamsas.client.VorbaIdFactory;
-import uk.ac.vamsas.client.VorbaXmlBinder;
-import uk.ac.vamsas.objects.core.ApplicationData;
-import uk.ac.vamsas.objects.core.VAMSAS;
-import uk.ac.vamsas.objects.core.VamsasDocument;
-import uk.ac.vamsas.objects.utils.AppDataReference;
-import uk.ac.vamsas.objects.utils.DocumentStuff;
-import uk.ac.vamsas.objects.utils.ProvenanceStuff;
-import uk.ac.vamsas.objects.utils.document.VersionEntries;
-
-/**
- * Class for high-level io and Jar manipulation involved in creating 
- * or updating a vamsas archive (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);
-  /**
-   * Access original document if it exists, and get VAMSAS root objects.
-   * @return vector of vamsas roots from original document
-   * @throws IOException
-   */
-  public static Vobject[] getOriginalRoots(VamsasArchive ths) throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
-    VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
-    if (oReader!=null) {
-      
-      if (oReader.isValid()) {
-        InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
-        VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
-        if (doc!=null) 
-          return doc.getVAMSAS();
-        // TODO ensure embedded appDatas are garbage collected to save memory
-      } else {
-        InputStream vxmlis = oReader.getVamsasXmlStream();
-        if (vxmlis!=null) { // Might be an old vamsas file.
-          BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
-          InputStreamReader vxml = new InputStreamReader(ixml);
-          VAMSAS root[] = new VAMSAS[1];
-          root[0] = VAMSAS.unmarshal(vxml);
-          if (root[0]!=null)
-            return root;
-        }
-      }
-    }
-    return null;
-  }
-  /**
-   * Access the original vamsas document for a VamsasArchive class, and return it.
-   * Users of the VamsasArchive class should use the getVamsasDocument method to retrieve
-   * the current document - only use this one if you want the 'backup' version.
-   * TODO: catch OutOfMemoryError - they are likely to occur here.
-   * NOTE: vamsas.xml datastreams are constructed as 'ALPHA_VERSION' vamsas documents.
-   * @param ths
-   * @return null if no document exists.
-   * @throws IOException
-   * @throws org.exolab.castor.xml.MarshalException
-   * @throws org.exolab.castor.xml.ValidationException
-   */
-  public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths) throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
-    return VamsasArchive.getOriginalVamsasDocument(ths, null);
-  } 
-  /**
-   * Uses VorbaXmlBinder to retrieve the VamsasDocument from the original archive referred to by ths
-   * @param ths
-   * @param vorba
-   * @return
-   * @throws IOException
-   * @throws org.exolab.castor.xml.MarshalException
-   * @throws org.exolab.castor.xml.ValidationException
-   */
-  public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths, VorbaIdFactory vorba) throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
-    VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
-    if (oReader!=null) {
-      ths.setVorba(vorba);
-      return ths.vorba.getVamsasDocument(oReader);
-    }
-    // otherwise - there was no valid original document to read.
-    return null;    
-  }
-  /**
-   * destination of new archive data (tempfile if virginarchive=true, original archive location otherwise)
-   */
-  java.io.File archive=null;
-  /**
-   * locked IO handler for new archive file
-   */
-  SessionFile rchive=null;
-  /**
-   * original archive file to be updated (or null if virgin) where new data will finally reside
-   */
-  java.io.File original=null;
-  /**
-   * original archive IO handler
-   */
-  SessionFile odoclock = null;
-  Lock destinationLock = null;
-  /**
-   * Original archive reader class
-   */
-  VamsasArchiveReader odoc = null;
-  /**
-   * true if a real vamsas document is being written.
-   */
-  boolean vamsasdocument=true;
-  /**
-   * Output stream for archived data
-   */
-  org.apache.tools.zip.ZipOutputStream newarchive=null;
-  /**
-   * JarEntries written to archive
-   */
-  Hashtable entries = null;
-  
-  /**
-   * true if we aren't just updating an archive
-   */
-  private boolean virginArchive=false;
-  
-  /**
-   * name of backup of existing archive that has been updated/overwritten.
-   * only one backup will be made - and this is it.
-   */
-  File originalBackup = null;
-  
-  boolean donotdeletebackup=false;
-  private final int _TRANSFER_BUFFER=4096*4;
-  protected SimpleDocument vorba = null;
-  /**
-   * LATER: ? CUT'n'Paste error ?
-   * Access and return current vamsas Document, if it exists, or create a new one 
-   * (without affecting VamsasArchive object state - so is NOT THREAD SAFE)
-   * _TODO: possibly modify internal state to lock low-level files 
-   * (like the IClientDocument interface instance constructer would do) 
-   * @see org.vamsas.simpleclient.VamsasArchive.getOriginalVamsasDocument for additional caveats
-   * 
-   * @return
-   * @throws IOException
-   * @throws org.exolab.castor.xml.MarshalException
-   * @throws org.exolab.castor.xml.ValidationException
-   * ????? where does this live JBPNote ?
-   */
-  private VamsasDocument _doc=null;
-  
-  /**
-   * Create a new vamsas archive
-   * File locks are made immediately to avoid contention
-   *  
-   * @param archive - file spec for new vamsas archive
-   * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
-   * @throws IOException if call to accessOriginal failed for updates, or openArchive failed.
-   */
-  public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {
-    this(archive, false, vamsasdocument, null);
-  }
-  public VamsasArchive(File archive, boolean vamsasdocument, boolean overwrite) throws IOException {
-    this(archive, overwrite, vamsasdocument, null);
-  }
-  /**
-   * Constructor for accessing Files under file-lock management (ie a session file)
-   * @param archive
-   * @param vamsasdocument
-   * @param overwrite
-   * @throws IOException
-   */
-  public VamsasArchive(VamsasFile archive, boolean vamsasdocument, boolean overwrite) throws IOException {
-    this(archive.sessionFile, overwrite, vamsasdocument, archive);
-    // log.debug("using non-functional lock-IO stream jar access constructor");
-  }
-  /**
-   * read and write to archive - will not overwrite original contents, and will always write an up to date vamsas document structure.
-   * @param archive
-   * @throws IOException
-   */
-  public VamsasArchive(VamsasFile archive) throws IOException {
-    this(archive, true, false); 
-  }
-  /**
-   * 
-   * @param archive file to write
-   * @param overwrite true if original contents should be deleted
-   * @param vamsasdocument true if a proper VamsasDocument archive is to be written.
-   * @param extantLock SessionFile object holding a lock for the <object>archive</object> 
-   * @throws IOException
-   */
-  public VamsasArchive(File archive, boolean overwrite, boolean vamsasdocument, SessionFile extantLock) throws IOException {
-    super();
-    if (archive==null || (archive!=null && !(archive.getAbsoluteFile().getParentFile().canWrite() && (!archive.exists() || archive.canWrite())))) {
-      log.fatal("Expect Badness! -- Invalid parameters for VamsasArchive constructor:"+((archive!=null) 
-          ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
-      return;
-    }
-    
-    this.vamsasdocument = vamsasdocument;
-    if (archive.exists() && !overwrite) {
-      this.original = archive;
-      if (extantLock!=null) {
-        this.odoclock = extantLock;
-        if (odoclock.fileLock==null || !odoclock.fileLock.isLocked())
-          odoclock.lockFile();
-      } else { 
-        this.odoclock = new SessionFile(archive);
-      }
-      odoclock.lockFile(); // lock the file *immediatly*
-      this.archive = null;       // archive will be a temp file when the open method is called
-      virginArchive=false;
-      try {
-        this.accessOriginal();
-      } catch (IOException e)  {
-        throw new IOException("Lock failed for existing archive"+archive);
-      }
-    } else {
-      this.original = null;
-      this.archive = archive; // archive is written in place.
-      if (extantLock!=null)
-        rchive=extantLock;
-      else
-        rchive = new SessionFile(archive);
-      rchive.lockFile();
-      if (rchive.fileLock==null || !rchive.fileLock.isLocked())
-        throw new IOException("Lock failed for new archive"+archive);
-      rchive.fileLock.getRaFile().setLength(0); // empty the archive.
-      virginArchive = true;
-    }
-    this.openArchive(); // open archive
-  }
-  /**
-   * open original archive file for exclusive (locked) reading.
-   * @throws IOException
-   */
-  private void accessOriginal() throws IOException {
-    if (original!=null && original.exists()) {
-      if (odoclock==null) 
-        odoclock = new SessionFile(original);
-      odoclock.lockFile();
-      if (odoc == null) 
-        odoc = new VamsasArchiveReader(original);
-        // this constructor is not implemented yet odoc = new VamsasArchiveReader(odoclock.fileLock);
-    }
-  }
-  
-  /**
-   * 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 (log.isDebugEnabled())
-    {   
-      log.debug("validating '"+entry+"' in hash for "+this);
-    }
-    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 {
-    org.apache.tools.zip.ZipEntry je = new org.apache.tools.zip.ZipEntry(entry);
-    // je.setExsetExtra(null);
-    if (!addEntry(entry))
-      return false;
-    newarchive.flush();
-    newarchive.putNextEntry(je);
-    return true;
-  }
-  /**
-   * called by app to get name of backup if it was made.
-   * If this is called, the caller app *must* delete the backup themselves.
-   * @return null or a valid file object
-   */
-  public File backupFile() {
-    
-    if (!virginArchive) {
-      makeBackup();
-      donotdeletebackup=true; // external reference has been made.
-      return ((original!=null) ? originalBackup : null);
-    }
-    return null;
-  }
-  
-  /**
-   * Stops any current write to archive, and reverts to the backup if it exists.
-   * All existing locks on the original will be released. All backup files are removed.
-   */
-  public boolean cancelArchive() {
-    if (newarchive!=null) {
-      try { 
-        newarchive.closeEntry();
-        newarchive.putNextEntry(new org.apache.tools.zip.ZipEntry("deleted"));
-        newarchive.closeEntry();
-        newarchive.close();
-        
-      } catch (Exception e) {
-        log.debug("Whilst closing newarchive",e);
-      };
-      if (!virginArchive) {
-        // then there is something to recover.
-        try {
-          recoverBackup();
-        }
-        catch (Exception e) {
-          log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
-          return false;
-        }
-      }
-      
-    } else {
-      log.warn("Client Error: cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
-    }
-    closeAndReset(); // tidy up and release locks.
-    return true;
-  }
-  
-  /**
-   * only do this if you want to destroy the current file output stream
-   *
-   */
-  private void closeAndReset() {
-    if (rchive!=null) {
-      rchive.unlockFile();
-      rchive=null;
-    }
-    if (original!=null) {
-      if (odoc!=null) {
-        odoc.close();
-        odoc=null;
-      }
-      if (archive!=null)
-        archive.delete();
-      if (odoclock!=null) {
-        odoclock.unlockFile();
-        odoclock = null;
-      }
-    }
-    removeBackup();
-    newarchive=null;
-    original=null;
-    entries=null;
-  }
-  /**
-   * Tidies up and closes archive, removing any backups that were created.
-   * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
-   * TODO: ensure all extant AppDataReference jar entries are transferred to new Jar
-   * TODO: provide convenient mechanism for generating new unique AppDataReferences and adding them to the document
-   */
-  public void closeArchive() throws IOException {
-    if (newarchive!=null) {
-      newarchive.flush();
-      newarchive.closeEntry();
-      if (!isDocumentWritten())
-        log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
-      newarchive.finish();// close(); // use newarchive.finish(); for a stream IO
-      newarchive.flush();
-      //
-      updateOriginal();
-      closeAndReset();
-    } else {
-      log.warn("Attempt to close archive that has not been opened for writing.");
-    }
-  }
-  /**
-   * Opens and returns the applicationData output stream for the appdataReference string.
-   * @param appdataReference
-   * @return Output stream to write to
-   * @throws IOException
-   */
-  public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
-    if (newarchive==null)
-      throw new IOException("Attempt to write to closed VamsasArchive object.");
-    if (addValidEntry(appdataReference)) {
-      return new AppDataOutputStream(newarchive);
-    }
-    return null;
-  }
-  
-  /**
-   * 
-   * @return JarEntry name for the vamsas XML stream in this archive
-   */
-  protected String getDocumentJarEntry() {
-    if (vamsasdocument)
-      return VamsasArchiveReader.VAMSASDOC;
-    return VamsasArchiveReader.VAMSASXML;
-  }
-  /**
-   * 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;
-  }
-  
-  /**
-   * Access original archive if it exists, pass the reader to the client
-   * Note: this is NOT thread safe and a call to closeArchive() will by necessity 
-   * close and invalidate the VamsasArchiveReader object.
-   * @return null if no original archive exists.
-   */
-  public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
-    if (!virginArchive) {
-      accessOriginal();
-      return odoc;
-    }
-    return null;
-  }
-  /**
-   * returns original document's root vamsas elements.
-   * @return
-   * @throws IOException
-   * @throws org.exolab.castor.xml.MarshalException
-   * @throws org.exolab.castor.xml.ValidationException
-   */
-  public Vobject[] getOriginalRoots() throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException  {
-    return VamsasArchive.getOriginalRoots(this);
-  }
-  /**
-   * @return original document or a new empty document (with default provenance)
-   * @throws IOException
-   * @throws org.exolab.castor.xml.MarshalException
-   * @throws org.exolab.castor.xml.ValidationException
-   */
-  public VamsasDocument getVamsasDocument() throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
-    return getVamsasDocument("org.vamsas.simpleclient.VamsasArchive", "Created new empty document", null);
-  }
-  /**
-   * Return the original document or a new empty document with initial provenance entry.
-   * @param provenance_user (null sets user to be the class name)
-   * @param provenance_action (null sets action to be 'created new document')
-   * @param version (null means use latest version)
-   * @return (original document or a new vamsas document with supplied provenance and version info)
-   * @throws IOException
-   * @throws org.exolab.castor.xml.MarshalException
-   * @throws org.exolab.castor.xml.ValidationException
-   */
-  public VamsasDocument getVamsasDocument(String provenance_user, String provenance_action, String version) throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
-    if (_doc!=null)
-      return _doc;
-    _doc = getOriginalVamsasDocument(this, getVorba());
-    if (_doc!=null)
-      return _doc;
-    // validate parameters
-    if (provenance_user==null)
-      provenance_user = "org.vamsas.simpleclient.VamsasArchive";
-    if (provenance_action == null)
-      provenance_action="Created new empty document";
-    if (version==null)
-      version = VersionEntries.latestVersion();
-    // Create a new document and return it
-    _doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()}, 
-        ProvenanceStuff.newProvenance(provenance_user, provenance_action), version);
-    return _doc;
-  }
-  /**
-   * @return Returns the current VorbaIdFactory for the archive.
-   */
-  public VorbaIdFactory getVorba() {
-    if (vorba==null)
-      vorba = new SimpleDocument("simpleclient.VamsasArchive");
-    return vorba.getVorba();
-  }
-  /**
-   * @return true if Vamsas Document has been written to archive
-   */
-  protected boolean isDocumentWritten() {
-    if (newarchive==null)
-      log.warn("isDocumentWritten() called for unopened archive.");
-    if (entries!=null) {
-      if (entries.containsKey(getDocumentJarEntry()))
-        return true;
-    }
-    return false;
-  }
-  private void makeBackup() {
-    if (!virginArchive) {
-      if (originalBackup==null && original!=null && original.exists()) {
-        try {
-          accessOriginal();
-          originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
-        }
-        catch (IOException e) {
-          log.warn("Problem whilst making a backup of original archive.",e);
-        }
-      }
-    }
-  }
-  /**
-   * opens the new archive ready for writing. If the new archive is replacing an existing one, 
-   * then the existing archive will be locked, and the new archive written to a temporary file. 
-   * The new archive will be put in place once close() is called.
-   * @param doclock LATER - pass existing lock on document, if it exists.... no need yet?
-   * @throws IOException
-   */
-  private void openArchive() throws IOException {
-    
-    if (newarchive!=null) {
-      log.warn("openArchive() called multiple times.");
-      throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
-    }
-    if (archive==null && (virginArchive || original==null)) {
-      log.warn("openArchive called on uninitialised VamsasArchive object.");
-      throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
-    }
-    if (!virginArchive) {
-      // lock the original
-      accessOriginal();
-      // make a temporary file to write to
-      archive = File.createTempFile(original.getName(), ".new",original.getParentFile());
-    } else {
-      if (archive.exists())
-        log.warn("New archive file name already in use! Possible lock failure imminent?");
-    }
-    
-    if (rchive==null)
-      rchive = new SessionFile(archive);
-    if (!rchive.lockFile()) 
-      throw new IOException("Failed to get lock on file "+archive);
-    // LATER: locked IO stream based access.
-    // Manifest newmanifest = new Manifest();
-    newarchive = new org.apache.tools.zip.ZipOutputStream(rchive.fileLock.getBufferedOutputStream(true));// , newmanifest);  
-    //newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));  
-    entries = new Hashtable();
-  }
-  public void putVamsasDocument(VamsasDocument doc) throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
-    putVamsasDocument(doc, getVorba());
-  }
-  /**
-   * 
-   * @param doc
-   * @param vorba
-   * @return (vorbaId string, Vobjhash) pairs for last hash of each object in document
-   * @throws IOException
-   * @throws org.exolab.castor.xml.MarshalException
-   * @throws org.exolab.castor.xml.ValidationException
-   */
-  public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba) throws IOException, 
-  org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
-    if (vamsasdocument)
-      doc.setVersion(VersionEntries.latestVersion()); // LATER: ensure this does the correct thing.
-    VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);
-  }
-  
-  /**
-   * recovers the original file's contents from the (temporary) backup. 
-   * @throws Exception if any SessionFile or file removal operations fail.
-   */
-  private void recoverBackup() throws Exception {
-    if (originalBackup!=null) {
-      // backup has been made.
-      // revert from backup and delete it (changing backup filename)
-      if (rchive==null) {
-        rchive = new SessionFile(original);
-      }
-      SessionFile bckup = new SessionFile(originalBackup);
-      
-      rchive.updateFrom(null, bckup); // recover from backup file.
-      bckup.unlockFile();
-      bckup=null;
-      removeBackup();
-    }
-  }
-  
-  /**
-   * forget about any backup that was made - removing it first if it was only temporary.
-   */
-  private void removeBackup() {
-    if (originalBackup!=null) {
-      log.debug("Removing backup in "+originalBackup.getAbsolutePath());
-      if (!donotdeletebackup)
-        if (!originalBackup.delete())
-          log.info("VamsasArchive couldn't remove temporary backup "+originalBackup.getAbsolutePath());
-      originalBackup=null;
-    }
-  } 
-  /**
-   * @param vorba the VorbaIdFactory to use for accessing vamsas objects.
-   */
-  public void setVorba(VorbaIdFactory Vorba) {
-    if (Vorba!=null) {
-      if (vorba==null)
-        vorba = new SimpleDocument(Vorba);
-      else
-        vorba.setVorba(Vorba);
-    } else
-      getVorba();
-  }
-  /**
-   * 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 {
-    return transferAppDataEntry(AppDataReference, AppDataReference);
-  }
-  /**
-   * Validates the AppDataReference: not null and not already written to archive.
-   * @param AppDataReference
-   * @return true if valid. false if not
-   * @throws IOException for really broken references!
-   */
-  protected boolean _validNewAppDataReference(String newAppDataReference) throws IOException {
-    // LATER: Specify valid AppDataReference form in all VamsasArchive handlers
-    if (newAppDataReference==null)
-      throw new IOException("null newAppDataReference!");
-    if (entries.containsKey(newAppDataReference)) {
-      log.warn("Attempt to write '"+newAppDataReference+"' twice! - IGNORED");
-      // LATER: fix me? warning message should raise an exception here.
-      return false;
-    }
-    return true;
-  }
-  /**
-   * Transfers an AppDataReference from old to new vamsas archive, with a name change.
-   * @see transferAppDataEntry(String AppDataReference)
-   * @param AppDataReference
-   * @param NewAppDataReference - AppDataReference in new Archive
-   * @return
-   * @throws IOException
-   */
-  public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
-    if (original==null || !original.exists()) {
-      log.warn("No backup archive exists.");
-      return false;
-    }
-    if (AppDataReference==null)
-      throw new IOException("null AppDataReference!");
-
-    if (!_validNewAppDataReference(NewAppDataReference))
-      return false;
-    
-    accessOriginal();
-    
-    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(NewAppDataReference);
-    // 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+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
-    return true;
-  }
-  /**
-   * write data from a stream into an appData reference.
-   * @param AppDataReference - New AppDataReference not already written to archive
-   * @param adstream Source of data for appData reference - read until .read(buffer) returns -1
-   * @return true on success.
-   * @throws IOException for file IO or invalid AppDataReference string
-   */
-  public boolean writeAppdataFromStream(String AppDataReference, java.io.InputStream adstream) throws IOException {
-    if (!_validNewAppDataReference(AppDataReference)) {
-      log.warn("Invalid AppDataReference passed to writeAppdataFromStream");
-      throw new IOException("Invalid AppDataReference! (null, or maybe non-unique)!");
-    }
-      
-    if (AppDataReference==null) {
-      log.warn("null appdata passed.");
-      throw new IOException("Null AppDataReference");
-    }
-    
-    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);
-    return true;
-  }
-  /**
-   * transfers any AppDataReferences existing in the old document 
-   * that haven't already been transferred to the new one
-   * LATER: do the same for transfers requiring a namechange - more document dependent.
-   *  @return true if data was transferred.
-   */
-  public boolean transferRemainingAppDatas() throws IOException {
-    boolean transfered=false;
-    if (original==null || !original.exists()) {
-      log.warn("No backup archive exists.");
-      return false;
-    }
-    accessOriginal();
-    
-    if (getVorba()!=null) {
-      Vector originalRefs=null;
-      try {
-        originalRefs = vorba.getReferencedEntries(getVamsasDocument(), getOriginalArchiveReader());
-      } catch (Exception e) {
-        log.warn("Problems accessing original document entries!",e);
-      }
-      if (originalRefs!=null) {
-        Iterator ref = originalRefs.iterator();
-        while (ref.hasNext()) {
-          String oldentry = (String) ref.next();
-          if (oldentry!=null && !entries.containsKey(oldentry)) {
-            log.debug("Transferring remaining entry '"+oldentry+"'");
-            transfered |= transferAppDataEntry(oldentry);
-          }
-        }
-      }
-    } 
-    return transfered;
-  }
-  /**
-   * called after archive is written to put file in its final place
-   */
-  private void updateOriginal() {
-    if (!virginArchive) {
-      // make sure original document really is backed up and then overwrite it.
-      if (odoc!=null) {
-        // try to shut the odoc reader.
-        odoc.close();
-        odoc = null;
-      }
-      // Make a backup if it isn't done already
-      makeBackup();
-      try {
-        // copy new Archive data that was writen to a temporary file
-        odoclock.updateFrom(null, rchive);
-      }
-      catch (IOException e) {
-        // LATER: decide if leaving nastily named backup files around is necessary.
-        File backupFile=backupFile();
-        if (backupFile!=null)
-          log.error("Problem updating archive from temporary file! - backup left in '"
-            +backupFile().getAbsolutePath()+"'",e);
-        else
-          log.error("Problems updating, and failed to even make a backup file. Ooops!", e);
-      }
-      // Tidy up if necessary.
-      removeBackup();
-    } else {
-      
-      
-    }
-  }
-}
+/*\r
+ * This file is part of the Vamsas Client version 0.1. \r
+ * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, \r
+ *  Andrew Waterhouse and Dominik Lindner.\r
+ * \r
+ * Earlier versions have also been incorporated into Jalview version 2.4 \r
+ * since 2008, and TOPALi version 2 since 2007.\r
+ * \r
+ * The Vamsas Client is free software: you can redistribute it and/or modify\r
+ * it under the terms of the GNU Lesser General Public License as published by\r
+ * the Free Software Foundation, either version 3 of the License, or\r
+ * (at your option) any later version.\r
+ *  \r
+ * The Vamsas Client is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU Lesser General Public License for more details.\r
+ * \r
+ * You should have received a copy of the GNU Lesser General Public License\r
+ * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.\r
+ */\r
+package uk.ac.vamsas.client.simpleclient;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.BufferedOutputStream;\r
+import java.io.DataOutputStream;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.InputStreamReader;\r
+import java.io.OutputStream;\r
+import java.io.OutputStreamWriter;\r
+import java.io.PrintWriter;\r
+import java.util.Hashtable;\r
+import java.util.Iterator;\r
+import java.util.Vector;\r
+import java.util.jar.JarEntry;\r
+import java.util.jar.JarOutputStream;\r
+import java.util.jar.Manifest;\r
+\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+\r
+import uk.ac.vamsas.client.AppDataOutputStream;\r
+import uk.ac.vamsas.client.ClientHandle;\r
+import uk.ac.vamsas.client.IVorbaIdFactory;\r
+import uk.ac.vamsas.client.SessionHandle;\r
+import uk.ac.vamsas.client.UserHandle;\r
+import uk.ac.vamsas.client.Vobject;\r
+import uk.ac.vamsas.client.VorbaIdFactory;\r
+import uk.ac.vamsas.client.VorbaXmlBinder;\r
+import uk.ac.vamsas.objects.core.ApplicationData;\r
+import uk.ac.vamsas.objects.core.VAMSAS;\r
+import uk.ac.vamsas.objects.core.VamsasDocument;\r
+import uk.ac.vamsas.objects.utils.AppDataReference;\r
+import uk.ac.vamsas.objects.utils.DocumentStuff;\r
+import uk.ac.vamsas.objects.utils.ProvenanceStuff;\r
+import uk.ac.vamsas.objects.utils.document.VersionEntries;\r
+\r
+/**\r
+ * Class for high-level io and Jar manipulation involved in creating or updating\r
+ * a vamsas archive (with backups). Writes to a temporary file and then swaps\r
+ * new file for backup. uses the sessionFile locking mechanism for safe I/O\r
+ * \r
+ * @author jimp\r
+ * \r
+ */\r
+public class VamsasArchive {\r
+  private static Log log = LogFactory.getLog(VamsasArchive.class);\r
+\r
+  /**\r
+   * Access original document if it exists, and get VAMSAS root objects.\r
+   * \r
+   * @return vector of vamsas roots from original document\r
+   * @throws IOException\r
+   */\r
+  public static Vobject[] getOriginalRoots(VamsasArchive ths)\r
+      throws IOException, org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    VamsasArchiveReader oReader = ths.getOriginalArchiveReader();\r
+    if (oReader != null) {\r
+\r
+      if (oReader.isValid()) {\r
+        InputStreamReader vdoc = new InputStreamReader(oReader\r
+            .getVamsasDocumentStream());\r
+        VamsasDocument doc = VamsasDocument.unmarshal(vdoc);\r
+        if (doc != null)\r
+          return doc.getVAMSAS();\r
+        // TODO ensure embedded appDatas are garbage collected to save memory\r
+      } else {\r
+        InputStream vxmlis = oReader.getVamsasXmlStream();\r
+        if (vxmlis != null) { // Might be an old vamsas file.\r
+          BufferedInputStream ixml = new BufferedInputStream(oReader\r
+              .getVamsasXmlStream());\r
+          InputStreamReader vxml = new InputStreamReader(ixml);\r
+          VAMSAS root[] = new VAMSAS[1];\r
+          root[0] = VAMSAS.unmarshal(vxml);\r
+          if (root[0] != null)\r
+            return root;\r
+        }\r
+      }\r
+    }\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * Access the original vamsas document for a VamsasArchive class, and return\r
+   * it. Users of the VamsasArchive class should use the getVamsasDocument\r
+   * method to retrieve the current document - only use this one if you want the\r
+   * 'backup' version. TODO: catch OutOfMemoryError - they are likely to occur\r
+   * here. NOTE: vamsas.xml datastreams are constructed as 'ALPHA_VERSION'\r
+   * vamsas documents.\r
+   * \r
+   * @param ths\r
+   * @return null if no document exists.\r
+   * @throws IOException\r
+   * @throws org.exolab.castor.xml.MarshalException\r
+   * @throws org.exolab.castor.xml.ValidationException\r
+   */\r
+  public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths)\r
+      throws IOException, org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    return VamsasArchive.getOriginalVamsasDocument(ths, null);\r
+  }\r
+\r
+  /**\r
+   * Uses VorbaXmlBinder to retrieve the VamsasDocument from the original\r
+   * archive referred to by ths\r
+   * \r
+   * @param ths\r
+   * @param vorba\r
+   * @return\r
+   * @throws IOException\r
+   * @throws org.exolab.castor.xml.MarshalException\r
+   * @throws org.exolab.castor.xml.ValidationException\r
+   */\r
+  public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths,\r
+      VorbaIdFactory vorba) throws IOException,\r
+      org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    VamsasArchiveReader oReader = ths.getOriginalArchiveReader();\r
+    if (oReader != null) {\r
+      ths.setVorba(vorba);\r
+      return ths.vorba.getVamsasDocument(oReader);\r
+    }\r
+    // otherwise - there was no valid original document to read.\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * destination of new archive data (tempfile if virginarchive=true, original\r
+   * archive location otherwise)\r
+   */\r
+  java.io.File archive = null;\r
+\r
+  /**\r
+   * locked IO handler for new archive file\r
+   */\r
+  SessionFile rchive = null;\r
+\r
+  /**\r
+   * original archive file to be updated (or null if virgin) where new data will\r
+   * finally reside\r
+   */\r
+  java.io.File original = null;\r
+\r
+  /**\r
+   * original archive IO handler\r
+   */\r
+  SessionFile odoclock = null;\r
+\r
+  Lock destinationLock = null;\r
+\r
+  /**\r
+   * Original archive reader class\r
+   */\r
+  VamsasArchiveReader odoc = null;\r
+\r
+  /**\r
+   * true if a real vamsas document is being written.\r
+   */\r
+  boolean vamsasdocument = true;\r
+\r
+  /**\r
+   * Output stream for archived data\r
+   */\r
+  org.apache.tools.zip.ZipOutputStream newarchive = null;\r
+\r
+  /**\r
+   * JarEntries written to archive\r
+   */\r
+  Hashtable entries = null;\r
+\r
+  /**\r
+   * true if we aren't just updating an archive\r
+   */\r
+  private boolean virginArchive = false;\r
+\r
+  /**\r
+   * name of backup of existing archive that has been updated/overwritten. only\r
+   * one backup will be made - and this is it.\r
+   */\r
+  File originalBackup = null;\r
+\r
+  boolean donotdeletebackup = false;\r
+\r
+  private final int _TRANSFER_BUFFER = 4096 * 4;\r
+\r
+  protected SimpleDocument vorba = null;\r
+\r
+  /**\r
+   * LATER: ? CUT'n'Paste error ? Access and return current vamsas Document, if\r
+   * it exists, or create a new one (without affecting VamsasArchive object\r
+   * state - so is NOT THREAD SAFE) _TODO: possibly modify internal state to\r
+   * lock low-level files (like the IClientDocument interface instance\r
+   * constructer would do)\r
+   * \r
+   * @see org.vamsas.simpleclient.VamsasArchive.getOriginalVamsasDocument for\r
+   *      additional caveats\r
+   * \r
+   * @return\r
+   * @throws IOException\r
+   * @throws org.exolab.castor.xml.MarshalException\r
+   * @throws org.exolab.castor.xml.ValidationException\r
+   *           ????? where does this live JBPNote ?\r
+   */\r
+  private VamsasDocument _doc = null;\r
+\r
+  /**\r
+   * Create a new vamsas archive File locks are made immediately to avoid\r
+   * contention\r
+   * \r
+   * @param archive\r
+   *          - file spec for new vamsas archive\r
+   * @param vamsasdocument\r
+   *          true if archive is to be a fully fledged vamsas document archive\r
+   * @throws IOException\r
+   *           if call to accessOriginal failed for updates, or openArchive\r
+   *           failed.\r
+   */\r
+  public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {\r
+    this(archive, false, vamsasdocument, null);\r
+  }\r
+\r
+  public VamsasArchive(File archive, boolean vamsasdocument, boolean overwrite)\r
+      throws IOException {\r
+    this(archive, overwrite, vamsasdocument, null);\r
+  }\r
+\r
+  /**\r
+   * Constructor for accessing Files under file-lock management (ie a session\r
+   * file)\r
+   * \r
+   * @param archive\r
+   * @param vamsasdocument\r
+   * @param overwrite\r
+   * @throws IOException\r
+   */\r
+  public VamsasArchive(VamsasFile archive, boolean vamsasdocument,\r
+      boolean overwrite) throws IOException {\r
+    this(archive.sessionFile, overwrite, vamsasdocument, archive);\r
+    // log.debug("using non-functional lock-IO stream jar access constructor");\r
+  }\r
+\r
+  /**\r
+   * read and write to archive - will not overwrite original contents, and will\r
+   * always write an up to date vamsas document structure.\r
+   * \r
+   * @param archive\r
+   * @throws IOException\r
+   */\r
+  public VamsasArchive(VamsasFile archive) throws IOException {\r
+    this(archive, true, false);\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @param archive\r
+   *          file to write\r
+   * @param overwrite\r
+   *          true if original contents should be deleted\r
+   * @param vamsasdocument\r
+   *          true if a proper VamsasDocument archive is to be written.\r
+   * @param extantLock\r
+   *          SessionFile object holding a lock for the <object>archive</object>\r
+   * @throws IOException\r
+   */\r
+  public VamsasArchive(File archive, boolean overwrite, boolean vamsasdocument,\r
+      SessionFile extantLock) throws IOException {\r
+    super();\r
+    if (archive == null\r
+        || (archive != null && !(archive.getAbsoluteFile().getParentFile()\r
+            .canWrite() && (!archive.exists() || archive.canWrite())))) {\r
+      log\r
+          .fatal("Expect Badness! -- Invalid parameters for VamsasArchive constructor:"\r
+              + ((archive != null) ? "File cannot be overwritten."\r
+                  : "Null Object not valid constructor parameter"));\r
+      return;\r
+    }\r
+\r
+    this.vamsasdocument = vamsasdocument;\r
+    if (archive.exists() && !overwrite) {\r
+      this.original = archive;\r
+      if (extantLock != null) {\r
+        this.odoclock = extantLock;\r
+        if (odoclock.fileLock == null || !odoclock.fileLock.isLocked())\r
+          odoclock.lockFile();\r
+      } else {\r
+        this.odoclock = new SessionFile(archive);\r
+      }\r
+      odoclock.lockFile(); // lock the file *immediatly*\r
+      this.archive = null; // archive will be a temp file when the open method\r
+                           // is called\r
+      virginArchive = false;\r
+      try {\r
+        this.accessOriginal();\r
+      } catch (IOException e) {\r
+        throw new IOException("Lock failed for existing archive" + archive);\r
+      }\r
+    } else {\r
+      this.original = null;\r
+      this.archive = archive; // archive is written in place.\r
+      if (extantLock != null)\r
+        rchive = extantLock;\r
+      else\r
+        rchive = new SessionFile(archive);\r
+      rchive.lockFile();\r
+      if (rchive.fileLock == null || !rchive.fileLock.isLocked())\r
+        throw new IOException("Lock failed for new archive" + archive);\r
+      rchive.fileLock.getRaFile().setLength(0); // empty the archive.\r
+      virginArchive = true;\r
+    }\r
+    this.openArchive(); // open archive\r
+  }\r
+\r
+  /**\r
+   * open original archive file for exclusive (locked) reading.\r
+   * \r
+   * @throws IOException\r
+   */\r
+  private void accessOriginal() throws IOException {\r
+    if (original != null && original.exists()) {\r
+      if (odoclock == null)\r
+        odoclock = new SessionFile(original);\r
+      odoclock.lockFile();\r
+      if (odoc == null)\r
+        odoc = new VamsasArchiveReader(original);\r
+      // this constructor is not implemented yet odoc = new\r
+      // VamsasArchiveReader(odoclock.fileLock);\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Add unique entry strings to internal JarEntries list.\r
+   * \r
+   * @param entry\r
+   * @return true if entry was unique and was added.\r
+   */\r
+  private boolean addEntry(String entry) {\r
+    if (entries == null)\r
+      entries = new Hashtable();\r
+    if (log.isDebugEnabled()) {\r
+      log.debug("validating '" + entry + "' in hash for " + this);\r
+    }\r
+    if (entries.containsKey(entry))\r
+      return false;\r
+    entries.put(entry, new Integer(entries.size()));\r
+    return true;\r
+  }\r
+\r
+  /**\r
+   * adds named entry to newarchive or returns false.\r
+   * \r
+   * @param entry\r
+   * @return true if entry was unique and could be added\r
+   * @throws IOException\r
+   *           if entry name was invalid or a new entry could not be made on\r
+   *           newarchive\r
+   */\r
+  private boolean addValidEntry(String entry) throws IOException {\r
+    org.apache.tools.zip.ZipEntry je = new org.apache.tools.zip.ZipEntry(entry);\r
+    // je.setExsetExtra(null);\r
+    if (!addEntry(entry))\r
+      return false;\r
+    newarchive.flush();\r
+    newarchive.putNextEntry(je);\r
+    return true;\r
+  }\r
+\r
+  /**\r
+   * called by app to get name of backup if it was made. If this is called, the\r
+   * caller app *must* delete the backup themselves.\r
+   * \r
+   * @return null or a valid file object\r
+   */\r
+  public File backupFile() {\r
+\r
+    if (!virginArchive) {\r
+      makeBackup();\r
+      donotdeletebackup = true; // external reference has been made.\r
+      return ((original != null) ? originalBackup : null);\r
+    }\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * Stops any current write to archive, and reverts to the backup if it exists.\r
+   * All existing locks on the original will be released. All backup files are\r
+   * removed.\r
+   */\r
+  public boolean cancelArchive() {\r
+    if (newarchive != null) {\r
+      try {\r
+        newarchive.closeEntry();\r
+        newarchive.putNextEntry(new org.apache.tools.zip.ZipEntry("deleted"));\r
+        newarchive.closeEntry();\r
+        newarchive.close();\r
+\r
+      } catch (Exception e) {\r
+        log.debug("Whilst closing newarchive", e);\r
+      }\r
+      ;\r
+      if (!virginArchive) {\r
+        // then there is something to recover.\r
+        try {\r
+          recoverBackup();\r
+        } catch (Exception e) {\r
+          log.warn("Problems when trying to cancel Archive "\r
+              + archive.getAbsolutePath(), e);\r
+          return false;\r
+        }\r
+      }\r
+\r
+    } else {\r
+      log.warn("Client Error: cancelArchive called before archive("\r
+          + original.getAbsolutePath() + ") has been opened!");\r
+    }\r
+    closeAndReset(); // tidy up and release locks.\r
+    return true;\r
+  }\r
+\r
+  /**\r
+   * only do this if you want to destroy the current file output stream\r
+   * \r
+   */\r
+  private void closeAndReset() {\r
+    if (rchive != null) {\r
+      rchive.unlockFile();\r
+      rchive = null;\r
+    }\r
+    if (original != null) {\r
+      if (odoc != null) {\r
+        odoc.close();\r
+        odoc = null;\r
+      }\r
+      if (archive != null)\r
+        archive.delete();\r
+      if (odoclock != null) {\r
+        odoclock.unlockFile();\r
+        odoclock = null;\r
+      }\r
+    }\r
+    removeBackup();\r
+    newarchive = null;\r
+    original = null;\r
+    entries = null;\r
+  }\r
+\r
+  /**\r
+   * Tidies up and closes archive, removing any backups that were created. NOTE:\r
+   * It is up to the caller to delete the original archive backup obtained from\r
+   * backupFile() TODO: ensure all extant AppDataReference jar entries are\r
+   * transferred to new Jar TODO: provide convenient mechanism for generating\r
+   * new unique AppDataReferences and adding them to the document\r
+   */\r
+  public void closeArchive() throws IOException {\r
+    if (newarchive != null) {\r
+      newarchive.flush();\r
+      newarchive.closeEntry();\r
+      if (!isDocumentWritten())\r
+        log.warn("Premature closure of archive '" + archive.getAbsolutePath()\r
+            + "': No document has been written.");\r
+      newarchive.finish();// close(); // use newarchive.finish(); for a stream\r
+                          // IO\r
+      newarchive.flush();\r
+      //\r
+      updateOriginal();\r
+      closeAndReset();\r
+    } else {\r
+      log\r
+          .warn("Attempt to close archive that has not been opened for writing.");\r
+    }\r
+  }\r
+\r
+  /**\r
+   * Opens and returns the applicationData output stream for the\r
+   * appdataReference string.\r
+   * \r
+   * @param appdataReference\r
+   * @return Output stream to write to\r
+   * @throws IOException\r
+   */\r
+  public AppDataOutputStream getAppDataStream(String appdataReference)\r
+      throws IOException {\r
+    if (newarchive == null)\r
+      throw new IOException("Attempt to write to closed VamsasArchive object.");\r
+    if (addValidEntry(appdataReference)) {\r
+      return new AppDataOutputStream(newarchive);\r
+    }\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @return JarEntry name for the vamsas XML stream in this archive\r
+   */\r
+  protected String getDocumentJarEntry() {\r
+    if (vamsasdocument)\r
+      return VamsasArchiveReader.VAMSASDOC;\r
+    return VamsasArchiveReader.VAMSASXML;\r
+  }\r
+\r
+  /**\r
+   * Safely initializes the VAMSAS XML document Jar Entry.\r
+   * \r
+   * @return Writer to pass to the marshalling function.\r
+   * @throws IOException\r
+   *           if a document entry has already been written.\r
+   */\r
+  public PrintWriter getDocumentOutputStream() throws IOException {\r
+    if (newarchive == null)\r
+      openArchive();\r
+    if (!isDocumentWritten()) {\r
+      try {\r
+        if (addValidEntry(getDocumentJarEntry()))\r
+          return new PrintWriter(new java.io.OutputStreamWriter(newarchive,\r
+              "UTF-8"));\r
+      } catch (Exception e) {\r
+        log.warn("Problems opening XML document JarEntry stream", e);\r
+      }\r
+    } else {\r
+      throw new IOException("Vamsas Document output stream is already written.");\r
+    }\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * Access original archive if it exists, pass the reader to the client Note:\r
+   * this is NOT thread safe and a call to closeArchive() will by necessity\r
+   * close and invalidate the VamsasArchiveReader object.\r
+   * \r
+   * @return null if no original archive exists.\r
+   */\r
+  public VamsasArchiveReader getOriginalArchiveReader() throws IOException {\r
+    if (!virginArchive) {\r
+      accessOriginal();\r
+      return odoc;\r
+    }\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * returns original document's root vamsas elements.\r
+   * \r
+   * @return\r
+   * @throws IOException\r
+   * @throws org.exolab.castor.xml.MarshalException\r
+   * @throws org.exolab.castor.xml.ValidationException\r
+   */\r
+  public Vobject[] getOriginalRoots() throws IOException,\r
+      org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    return VamsasArchive.getOriginalRoots(this);\r
+  }\r
+\r
+  /**\r
+   * @return original document or a new empty document (with default provenance)\r
+   * @throws IOException\r
+   * @throws org.exolab.castor.xml.MarshalException\r
+   * @throws org.exolab.castor.xml.ValidationException\r
+   */\r
+  public VamsasDocument getVamsasDocument() throws IOException,\r
+      org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    return getVamsasDocument("org.vamsas.simpleclient.VamsasArchive",\r
+        "Created new empty document", null);\r
+  }\r
+\r
+  /**\r
+   * Return the original document or a new empty document with initial\r
+   * provenance entry.\r
+   * \r
+   * @param provenance_user\r
+   *          (null sets user to be the class name)\r
+   * @param provenance_action\r
+   *          (null sets action to be 'created new document')\r
+   * @param version\r
+   *          (null means use latest version)\r
+   * @return (original document or a new vamsas document with supplied\r
+   *         provenance and version info)\r
+   * @throws IOException\r
+   * @throws org.exolab.castor.xml.MarshalException\r
+   * @throws org.exolab.castor.xml.ValidationException\r
+   */\r
+  public VamsasDocument getVamsasDocument(String provenance_user,\r
+      String provenance_action, String version) throws IOException,\r
+      org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    if (_doc != null)\r
+      return _doc;\r
+    _doc = getOriginalVamsasDocument(this, getVorba());\r
+    if (_doc != null)\r
+      return _doc;\r
+    // validate parameters\r
+    if (provenance_user == null)\r
+      provenance_user = "org.vamsas.simpleclient.VamsasArchive";\r
+    if (provenance_action == null)\r
+      provenance_action = "Created new empty document";\r
+    if (version == null)\r
+      version = VersionEntries.latestVersion();\r
+    // Create a new document and return it\r
+    _doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS() },\r
+        ProvenanceStuff.newProvenance(provenance_user, provenance_action),\r
+        version);\r
+    return _doc;\r
+  }\r
+\r
+  /**\r
+   * @return Returns the current VorbaIdFactory for the archive.\r
+   */\r
+  public VorbaIdFactory getVorba() {\r
+    if (vorba == null)\r
+      vorba = new SimpleDocument("simpleclient.VamsasArchive");\r
+    return vorba.getVorba();\r
+  }\r
+\r
+  /**\r
+   * @return true if Vamsas Document has been written to archive\r
+   */\r
+  protected boolean isDocumentWritten() {\r
+    if (newarchive == null)\r
+      log.warn("isDocumentWritten() called for unopened archive.");\r
+    if (entries != null) {\r
+      if (entries.containsKey(getDocumentJarEntry()))\r
+        return true;\r
+    }\r
+    return false;\r
+  }\r
+\r
+  private void makeBackup() {\r
+    if (!virginArchive) {\r
+      if (originalBackup == null && original != null && original.exists()) {\r
+        try {\r
+          accessOriginal();\r
+          originalBackup = odoclock.backupSessionFile(null, original.getName(),\r
+              ".bak", original.getParentFile());\r
+        } catch (IOException e) {\r
+          log.warn("Problem whilst making a backup of original archive.", e);\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * opens the new archive ready for writing. If the new archive is replacing an\r
+   * existing one, then the existing archive will be locked, and the new archive\r
+   * written to a temporary file. The new archive will be put in place once\r
+   * close() is called.\r
+   * \r
+   * @param doclock\r
+   *          LATER - pass existing lock on document, if it exists.... no need\r
+   *          yet?\r
+   * @throws IOException\r
+   */\r
+  private void openArchive() throws IOException {\r
+\r
+    if (newarchive != null) {\r
+      log.warn("openArchive() called multiple times.");\r
+      throw new IOException("Vamsas Archive '" + archive.getAbsolutePath()\r
+          + "' is already open.");\r
+    }\r
+    if (archive == null && (virginArchive || original == null)) {\r
+      log.warn("openArchive called on uninitialised VamsasArchive object.");\r
+      throw new IOException(\r
+          "Badly initialised VamsasArchive object - no archive file specified.");\r
+    }\r
+    if (!virginArchive) {\r
+      // lock the original\r
+      accessOriginal();\r
+      // make a temporary file to write to\r
+      archive = File.createTempFile(original.getName(), ".new", original\r
+          .getParentFile());\r
+    } else {\r
+      if (archive.exists())\r
+        log\r
+            .warn("New archive file name already in use! Possible lock failure imminent?");\r
+    }\r
+\r
+    if (rchive == null)\r
+      rchive = new SessionFile(archive);\r
+    if (!rchive.lockFile())\r
+      throw new IOException("Failed to get lock on file " + archive);\r
+    // LATER: locked IO stream based access.\r
+    // Manifest newmanifest = new Manifest();\r
+    newarchive = new org.apache.tools.zip.ZipOutputStream(rchive.fileLock\r
+        .getBufferedOutputStream(true));// , newmanifest);\r
+    // newarchive = new JarOutputStream(new BufferedOutputStream(new\r
+    // java.io.FileOutputStream(archive)));\r
+    entries = new Hashtable();\r
+  }\r
+\r
+  public void putVamsasDocument(VamsasDocument doc) throws IOException,\r
+      org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    putVamsasDocument(doc, getVorba());\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @param doc\r
+   * @param vorba\r
+   * @return (vorbaId string, Vobjhash) pairs for last hash of each object in\r
+   *         document\r
+   * @throws IOException\r
+   * @throws org.exolab.castor.xml.MarshalException\r
+   * @throws org.exolab.castor.xml.ValidationException\r
+   */\r
+  public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba)\r
+      throws IOException, org.exolab.castor.xml.MarshalException,\r
+      org.exolab.castor.xml.ValidationException {\r
+    if (vamsasdocument)\r
+      doc.setVersion(VersionEntries.latestVersion()); // LATER: ensure this does\r
+                                                      // the correct thing.\r
+    VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);\r
+  }\r
+\r
+  /**\r
+   * recovers the original file's contents from the (temporary) backup.\r
+   * \r
+   * @throws Exception\r
+   *           if any SessionFile or file removal operations fail.\r
+   */\r
+  private void recoverBackup() throws Exception {\r
+    if (originalBackup != null) {\r
+      // backup has been made.\r
+      // revert from backup and delete it (changing backup filename)\r
+      if (rchive == null) {\r
+        rchive = new SessionFile(original);\r
+      }\r
+      SessionFile bckup = new SessionFile(originalBackup);\r
+\r
+      rchive.updateFrom(null, bckup); // recover from backup file.\r
+      bckup.unlockFile();\r
+      bckup = null;\r
+      removeBackup();\r
+    }\r
+  }\r
+\r
+  /**\r
+   * forget about any backup that was made - removing it first if it was only\r
+   * temporary.\r
+   */\r
+  private void removeBackup() {\r
+    if (originalBackup != null) {\r
+      log.debug("Removing backup in " + originalBackup.getAbsolutePath());\r
+      if (!donotdeletebackup)\r
+        if (!originalBackup.delete())\r
+          log.info("VamsasArchive couldn't remove temporary backup "\r
+              + originalBackup.getAbsolutePath());\r
+      originalBackup = null;\r
+    }\r
+  }\r
+\r
+  /**\r
+   * @param vorba\r
+   *          the VorbaIdFactory to use for accessing vamsas objects.\r
+   */\r
+  public void setVorba(VorbaIdFactory Vorba) {\r
+    if (Vorba != null) {\r
+      if (vorba == null)\r
+        vorba = new SimpleDocument(Vorba);\r
+      else\r
+        vorba.setVorba(Vorba);\r
+    } else\r
+      getVorba();\r
+  }\r
+\r
+  /**\r
+   * Convenience method to copy over the referred entry from the backup to the\r
+   * new version. Warning messages are raised if no backup exists or the entry\r
+   * doesn't exist in the backed-up original. Duplicate writes return true - but\r
+   * a warning message will also be raised.\r
+   * \r
+   * @param AppDataReference\r
+   * @return true if AppDataReference now exists in the new document\r
+   * @throws IOException\r
+   */\r
+  public boolean transferAppDataEntry(String AppDataReference)\r
+      throws IOException {\r
+    return transferAppDataEntry(AppDataReference, AppDataReference);\r
+  }\r
+\r
+  /**\r
+   * Validates the AppDataReference: not null and not already written to\r
+   * archive.\r
+   * \r
+   * @param AppDataReference\r
+   * @return true if valid. false if not\r
+   * @throws IOException\r
+   *           for really broken references!\r
+   */\r
+  protected boolean _validNewAppDataReference(String newAppDataReference)\r
+      throws IOException {\r
+    // LATER: Specify valid AppDataReference form in all VamsasArchive handlers\r
+    if (newAppDataReference == null)\r
+      throw new IOException("null newAppDataReference!");\r
+    if (entries.containsKey(newAppDataReference)) {\r
+      log.warn("Attempt to write '" + newAppDataReference\r
+          + "' twice! - IGNORED");\r
+      // LATER: fix me? warning message should raise an exception here.\r
+      return false;\r
+    }\r
+    return true;\r
+  }\r
+\r
+  /**\r
+   * Transfers an AppDataReference from old to new vamsas archive, with a name\r
+   * change.\r
+   * \r
+   * @see transferAppDataEntry(String AppDataReference)\r
+   * @param AppDataReference\r
+   * @param NewAppDataReference\r
+   *          - AppDataReference in new Archive\r
+   * @return\r
+   * @throws IOException\r
+   */\r
+  public boolean transferAppDataEntry(String AppDataReference,\r
+      String NewAppDataReference) throws IOException {\r
+    if (original == null || !original.exists()) {\r
+      log.warn("No backup archive exists.");\r
+      return false;\r
+    }\r
+    if (AppDataReference == null)\r
+      throw new IOException("null AppDataReference!");\r
+\r
+    if (!_validNewAppDataReference(NewAppDataReference))\r
+      return false;\r
+\r
+    accessOriginal();\r
+\r
+    java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);\r
+\r
+    if (adstream == null) {\r
+      log.warn("AppDataReference '" + AppDataReference\r
+          + "' doesn't exist in backup archive.");\r
+      return false;\r
+    }\r
+\r
+    java.io.OutputStream adout = getAppDataStream(NewAppDataReference);\r
+    // copy over the bytes\r
+    int written = -1;\r
+    long count = 0;\r
+    byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a\r
+                                                // sensible buffer\r
+    do {\r
+      if ((written = adstream.read(buffer)) > -1) {\r
+        adout.write(buffer, 0, written);\r
+        log.debug("Transferring " + written + ".");\r
+        count += written;\r
+      }\r
+    } while (written > -1);\r
+    log.debug("Sucessfully transferred AppData for '" + AppDataReference\r
+        + "' as '" + NewAppDataReference + "' (" + count + " bytes)");\r
+    return true;\r
+  }\r
+\r
+  /**\r
+   * write data from a stream into an appData reference.\r
+   * \r
+   * @param AppDataReference\r
+   *          - New AppDataReference not already written to archive\r
+   * @param adstream\r
+   *          Source of data for appData reference - read until .read(buffer)\r
+   *          returns -1\r
+   * @return true on success.\r
+   * @throws IOException\r
+   *           for file IO or invalid AppDataReference string\r
+   */\r
+  public boolean writeAppdataFromStream(String AppDataReference,\r
+      java.io.InputStream adstream) throws IOException {\r
+    if (!_validNewAppDataReference(AppDataReference)) {\r
+      log.warn("Invalid AppDataReference passed to writeAppdataFromStream");\r
+      throw new IOException(\r
+          "Invalid AppDataReference! (null, or maybe non-unique)!");\r
+    }\r
+\r
+    if (AppDataReference == null) {\r
+      log.warn("null appdata passed.");\r
+      throw new IOException("Null AppDataReference");\r
+    }\r
+\r
+    java.io.OutputStream adout = getAppDataStream(AppDataReference);\r
+    // copy over the bytes\r
+    int written = -1;\r
+    long count = 0;\r
+    byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a\r
+                                                // sensible buffer\r
+    do {\r
+      if ((written = adstream.read(buffer)) > -1) {\r
+        adout.write(buffer, 0, written);\r
+        log.debug("Transferring " + written + ".");\r
+        count += written;\r
+      }\r
+    } while (written > -1);\r
+    return true;\r
+  }\r
+\r
+  /**\r
+   * transfers any AppDataReferences existing in the old document that haven't\r
+   * already been transferred to the new one LATER: do the same for transfers\r
+   * requiring a namechange - more document dependent.\r
+   * \r
+   * @return true if data was transferred.\r
+   */\r
+  public boolean transferRemainingAppDatas() throws IOException {\r
+    boolean transfered = false;\r
+    if (original == null || !original.exists()) {\r
+      log.warn("No backup archive exists.");\r
+      return false;\r
+    }\r
+    accessOriginal();\r
+\r
+    if (getVorba() != null) {\r
+      Vector originalRefs = null;\r
+      try {\r
+        originalRefs = vorba.getReferencedEntries(getVamsasDocument(),\r
+            getOriginalArchiveReader());\r
+      } catch (Exception e) {\r
+        log.warn("Problems accessing original document entries!", e);\r
+      }\r
+      if (originalRefs != null) {\r
+        Iterator ref = originalRefs.iterator();\r
+        while (ref.hasNext()) {\r
+          String oldentry = (String) ref.next();\r
+          if (oldentry != null && !entries.containsKey(oldentry)) {\r
+            log.debug("Transferring remaining entry '" + oldentry + "'");\r
+            transfered |= transferAppDataEntry(oldentry);\r
+          }\r
+        }\r
+      }\r
+    }\r
+    return transfered;\r
+  }\r
+\r
+  /**\r
+   * called after archive is written to put file in its final place\r
+   */\r
+  private void updateOriginal() {\r
+    if (!virginArchive) {\r
+      // make sure original document really is backed up and then overwrite it.\r
+      if (odoc != null) {\r
+        // try to shut the odoc reader.\r
+        odoc.close();\r
+        odoc = null;\r
+      }\r
+      // Make a backup if it isn't done already\r
+      makeBackup();\r
+      try {\r
+        // copy new Archive data that was writen to a temporary file\r
+        odoclock.updateFrom(null, rchive);\r
+      } catch (IOException e) {\r
+        // LATER: decide if leaving nastily named backup files around is\r
+        // necessary.\r
+        File backupFile = backupFile();\r
+        if (backupFile != null)\r
+          log.error(\r
+              "Problem updating archive from temporary file! - backup left in '"\r
+                  + backupFile().getAbsolutePath() + "'", e);\r
+        else\r
+          log\r
+              .error(\r
+                  "Problems updating, and failed to even make a backup file. Ooops!",\r
+                  e);\r
+      }\r
+      // Tidy up if necessary.\r
+      removeBackup();\r
+    } else {\r
+\r
+    }\r
+  }\r
+}\r