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