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
*/
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.");
+ }
+ }
}