-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