package org.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.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;
+import org.vamsas.client.ClientHandle;
+import org.vamsas.client.IVorbaIdFactory;
+import org.vamsas.client.SessionHandle;
+import org.vamsas.client.UserHandle;
+import org.vamsas.client.VorbaIdFactory;
+import org.vamsas.client.VorbaXmlBinder;
+import org.vamsas.client.object;
+import org.vamsas.objects.core.ApplicationData;
+import org.vamsas.objects.core.VAMSAS;
+import org.vamsas.objects.core.VamsasDocument;
+import org.vamsas.objects.utils.DocumentStuff;
+import org.vamsas.objects.utils.ProvenanceStuff;
+import org.vamsas.objects.utils.document.VersionEntries;
/**
- * Class for creating a vamsas archive
- * (with backups)
+ * 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);
/**
- * destination of new archive data
+ * destination of new archive data (tempfile if virginarchive=true, original archive location otherwise)
*/
java.io.File archive=null;
/**
*/
SessionFile rchive=null;
/**
- * original archive file that is to be updated
+ * original archive file to be updated (or null if virgin) where new data will finally reside
*/
java.io.File original=null;
/**
private boolean virginArchive=false;
/**
* Create a new vamsas archive
- * nb. No file locks are made until open() is called.
+ * 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) {
+ public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {
super();
- if (archive==null || (archive!=null && archive.canWrite())) {
+ 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.original = archive;
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;
+ this.archive = archive; // archive is written in place.
virginArchive = true;
}
+ this.openArchive(); // open archive
}
/**
* name of backup of existing archive that has been updated/overwritten.
+ * onlu one backup will be made - and this is it.
*/
File originalBackup = null;
if (!virginArchive) {
if (originalBackup==null && original!=null && original.exists()) {
try {
- accessBackup();
+ accessOriginal();
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);
}
}
}
+
/**
* called after archive is written to put file in its final place
- * TODO: FINISH original should have sessionFile, and archive should also have sessionFile
+ * TODO: FINISH ?? original should have sessionFile, and archive should also have sessionFile
*/
private void updateOriginal() {
- if (original!=null) {
- if (!virginArchive) {
- if (!archive.getAbsolutePath().equals(original)) {
- if (originalBackup==null)
- makeBackup();
- }
- } else {
- archive.renameTo(original);
+ 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
+ if (originalBackup==null)
+ makeBackup();
+ try {
+ // copy new Archive data that was writen to a temporary file
+ odoclock.updateFrom(null, rchive);
+ }
+ catch (IOException e) {
+ log.error("Problem updating archive from temporary file! - backup in '"
+ +backupFile().getAbsolutePath()+"'",e);
}
+ } else {
+ // don't need to do anything.
}
}
/**
*/
public File backupFile() {
- if (virginArchive) {
+ if (!virginArchive) {
makeBackup();
- return ((original==null) ? originalBackup : null);
-
+ return ((original!=null) ? originalBackup : null);
}
return null;
}
-
+ /**
+ *
+ * @return JarEntry name for the vamsas XML stream in this archive
+ */
protected String getDocumentJarEntry() {
if (vamsasdocument)
return VamsasArchiveReader.VAMSASDOC;
return VamsasArchiveReader.VAMSASXML;
}
+
/**
* @return true if Vamsas Document has been written to archive
*/
protected boolean isDocumentWritten() {
if (newarchive==null)
- log.warn("isDocumentWritten called for unopened archive.");
+ log.warn("isDocumentWritten() called for unopened archive.");
if (entries!=null) {
if (entries.containsKey(getDocumentJarEntry()))
- return true;
+ return true;
}
return false;
}
return true;
}
- File tempoutput = null;
- SessionFile trchive = null;
+ /**
+ * 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.
+ * @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) {
- if (original==null) {
- log.warn("openArchive called on uninitialised VamsasArchive object.");
- throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
- }
+ 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
- accessBackup();
- // make a temporary file to write to
- archive = File.createTempFile(original.getName(), "new");
-
+ 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?");
}
rchive = new SessionFile(archive);
newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
entries = new Hashtable();
}
-
/**
* Safely initializes the VAMSAS XML document Jar Entry.
* @return Writer to pass to the marshalling function.
}
/**
* 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)
+ public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
+ if (newarchive==null)
openArchive();
if (addValidEntry(appdataReference)) {
- return new DataOutputStream(newarchive);
+ return new AppDataOutputStream(newarchive);
}
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 existing locks on the original will be released. All backup files are removed.
*/
public boolean cancelArchive() {
if (newarchive!=null) {
try {
newarchive.close();
+
} catch (Exception e) {};
if (!virginArchive) {
// then there is something to recover.
}
SessionFile bckup = new SessionFile(originalBackup);
- try {
- rchive.updateFrom(null,bckup); // recover from backup file.
- bckup.unlockFile();
- bckup=null;
- originalBackup.delete();
- }
- catch (Exception e) {
- log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
- return false;
- }
- }
- // original is untouched
- // just delete temp files
-
+ try {
+ rchive.updateFrom(null, bckup); // recover from backup file.
+ bckup.unlockFile();
+ bckup=null;
+ originalBackup.delete();
+ originalBackup=null;
+ }
+ catch (Exception e) {
+ log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
+ return false;
+ }
}
+ }
} else {
log.info("cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
}
- closeAndReset();
+ closeAndReset(); // tidy up and release locks.
return true;
}
*
*/
private void closeAndReset() {
- rchive.unlockFile();
- rchive = null;
+ if (rchive!=null) {
+ rchive.unlockFile();
+ rchive = null;
+ }
if (original!=null) {
if (odoc!=null) {
odoc.close();
odoc=null;
}
- archive.delete();
+ if (archive!=null)
+ archive.delete();
if (odoclock!=null) {
odoclock.unlockFile();
odoclock = null;
original=null;
entries=null;
}
-
+
private final int _TRANSFER_BUFFER=4096*4;
/**
- * open backup for exclusive (locked) reading.
+ * open original archive file for exclusive (locked) reading.
* @throws IOException
*/
- private void accessBackup() throws IOException {
+ private void accessOriginal() throws IOException {
if (original!=null && original.exists()) {
if (odoclock==null)
odoclock = new SessionFile(original);
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
* @throws IOException
*/
public boolean transferAppDataEntry(String AppDataReference) throws IOException {
+ return transferAppDataEntry(AppDataReference, AppDataReference);
+ }
+ /**
+ * 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 {
// TODO: Specify valid AppDataReference form in all VamsasArchive handlers
if (AppDataReference==null)
- throw new IOException("Invalid AppData Reference!");
+ throw new IOException("null AppDataReference!");
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");
+ if (entries.containsKey(NewAppDataReference)) {
+ log.warn("Attempt to write '"+NewAppDataReference+"' twice! - IGNORED");
return true;
}
- accessBackup();
+ accessOriginal();
java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
return false;
}
- java.io.OutputStream adout = getAppDataStream(AppDataReference);
+ java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
// copy over the bytes
int written=-1;
long count=0;
count+=written;
}
} while (written>-1);
- log.debug("Sucessfully transferred AppData for "+AppDataReference+" ("+count+" bytes)");
+ log.debug("Sucessfully transferred AppData for '"
+ +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
return true;
}
-
/**
* 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) {
if (!isDocumentWritten())
log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
newarchive.close();
+ updateOriginal();
closeAndReset();
} else {
log.warn("Attempt to close archive that has not been opened for writing.");
}
}
+ /**
+ * 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 object[] getOriginalRoots() throws IOException,
+ org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
+ return VamsasArchive.getOriginalRoots(this);
+ }
+ /**
+ * Access original document if it exists, and get VAMSAS root objects.
+ * @return vector of vamsas roots from original document
+ * @throws IOException
+ */
+ public static object[] 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;
+ }
+ protected VorbaIdFactory vorba = null;
+
+ /**
+ * @return Returns the current VorbaIdFactory for the archive.
+ */
+ public VorbaIdFactory getVorba() {
+ return vorba;
+ }
+
+ /**
+ * @param vorba the VorbaIdFactory to use for accessing vamsas objects.
+ */
+ public void setVorba(VorbaIdFactory vorba) {
+ this.vorba = vorba;
+ }
+
+ /**
+ * 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
+ */
+ public VamsasDocument getVamsasDocument() throws IOException,
+ org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
+ VamsasDocument doc = getOriginalVamsasDocument(this, getVorba());
+ if (doc!=null)
+ return doc;
+ // Create a new document and return it
+ doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()},
+ ProvenanceStuff.newProvenance("org.vamsas.simpleclient.VamsasArchive", "Created new empty document")
+ , VersionEntries.latestVersion());
+ return doc;
+ }
+ /**
+ * 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);
+ }
+ private VorbaIdFactory makeDefaultFactory(VorbaIdFactory vorba) {
+ if (vorba==null) {
+ vorba = getVorba();
+ if (vorba==null) {
+ vorba = IdFactory.getDummyFactory("simpleclient.VamsasArchive");
+ setVorba(vorba); // save for later use
+ }
+ }
+ return vorba;
+ }
+ /**
+ * 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) {
+ // check the factory
+ vorba = ths.makeDefaultFactory(vorba);
+ if (oReader.isValid()) {
+ InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
+ Object unmarsh[] = VorbaXmlBinder.getVamsasObjects(vdoc, vorba, new VamsasDocument());
+ if (unmarsh==null)
+ log.fatal("Couldn't unmarshall document!");
+
+ object vobjs = (object) unmarsh[0];
+ if (vobjs!=null) {
+ VamsasDocument doc=(VamsasDocument) vobjs;
+ if (doc!=null)
+ return doc;
+ }
+ log.debug("Found no VamsasDocument object in properly formatted Vamsas Archive.");
+ } else {
+ // deprecated data handler
+ InputStream vxmlis = oReader.getVamsasXmlStream();
+ if (vxmlis!=null) { // Might be an old vamsas file.
+ BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
+ InputStreamReader vxml = new InputStreamReader(ixml);
+ Object unmarsh[] = VorbaXmlBinder.getVamsasObjects(vxml, vorba, new VAMSAS());
+
+ if (unmarsh==null)
+ log.fatal("Couldn't unmarshall document!");
+
+ VAMSAS root[]= new VAMSAS[] { null};
+ root[0] = (VAMSAS) unmarsh[0];
+
+ if (root[0]==null) {
+ log.debug("Found no VAMSAS object in VamsasXML stream.");
+ } else {
+ log.debug("Making new VamsasDocument from VamsasXML stream.");
+ VamsasDocument doc = DocumentStuff.newVamsasDocument(root,
+ ProvenanceStuff.newProvenance(
+ "org.vamsas.simpleclient.VamsasArchive", // TODO: VAMSAS: decide on 'system' operations provenance form
+ "Vamsas Document constructed from vamsas.xml in <file>"
+ // TODO: VAMSAS: decide on machine readable info embedding in provenance should be done
+ +ths.original+"</file>"), VersionEntries.ALPHA_VERSION);
+ root[0]=null;
+ root=null;
+ return doc;
+ }
+ }
+ }
+ }
+ // otherwise - there was no valid original document to read.
+ return null;
+ }
+ public void putVamsasDocument(VamsasDocument doc) throws IOException,
+ org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
+ VorbaIdFactory vorba = makeDefaultFactory(getVorba());
+ VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);
+ }
}