X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fuk%2Fac%2Fvamsas%2Fclient%2Fsimpleclient%2FVamsasArchive.java;h=0747a6f9b521dab494897a9ef6e0de96fa0ff77a;hb=1eea4b639911330e7cd65e17c5421cc584f0d22e;hp=0cb89091e669b4fa7355cd71d35fe6cbcd422772;hpb=7a8700f3ff8fb1789955bd692f4c187ce4f653cd;p=vamsas.git diff --git a/src/uk/ac/vamsas/client/simpleclient/VamsasArchive.java b/src/uk/ac/vamsas/client/simpleclient/VamsasArchive.java index 0cb8909..0747a6f 100644 --- a/src/uk/ac/vamsas/client/simpleclient/VamsasArchive.java +++ b/src/uk/ac/vamsas/client/simpleclient/VamsasArchive.java @@ -1,815 +1,991 @@ -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 - */ - JarOutputStream 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 archive - * @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 { - JarEntry je = new JarEntry(entry); - 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 JarEntry("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 JarOutputStream(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 { - - - } - } -} +/* + * This file is part of the Vamsas Client version 0.2. + * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, + * Andrew Waterhouse and Dominik Lindner. + * + * Earlier versions have also been incorporated into Jalview version 2.4 + * since 2008, and TOPALi version 2 since 2007. + * + * The Vamsas Client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Vamsas Client is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the Vamsas Client. If not, see . + */ +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 archive + * @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 { + + } + } +}