1 package org.vamsas.client.simpleclient;
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
5 import java.io.DataOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.InputStreamReader;
10 import java.io.OutputStream;
11 import java.io.OutputStreamWriter;
12 import java.io.PrintWriter;
13 import java.util.Hashtable;
14 import java.util.jar.JarEntry;
15 import java.util.jar.JarOutputStream;
17 import org.apache.commons.logging.Log;
18 import org.apache.commons.logging.LogFactory;
19 import org.vamsas.client.ClientHandle;
20 import org.vamsas.client.IVorbaIdFactory;
21 import org.vamsas.client.SessionHandle;
22 import org.vamsas.client.UserHandle;
23 import org.vamsas.client.VorbaIdFactory;
24 import org.vamsas.client.VorbaXmlBinder;
25 import org.vamsas.client.object;
26 import org.vamsas.objects.core.ApplicationData;
27 import org.vamsas.objects.core.VAMSAS;
28 import org.vamsas.objects.core.VamsasDocument;
29 import org.vamsas.objects.utils.DocumentStuff;
30 import org.vamsas.objects.utils.ProvenanceStuff;
31 import org.vamsas.objects.utils.document.VersionEntries;
34 * Class for high-level io and Jar manipulation involved in creating
35 * or updating a vamsas archive (with backups).
36 * Writes to a temporary file and then swaps new file for backup.
37 * uses the sessionFile locking mechanism for safe I/O
41 public class VamsasArchive {
42 private static Log log = LogFactory.getLog(VamsasArchive.class);
44 * destination of new archive data (tempfile if virginarchive=true, original archive location otherwise)
46 java.io.File archive=null;
48 * locked IO handler for new archive file
50 SessionFile rchive=null;
52 * original archive file to be updated (or null if virgin) where new data will finally reside
54 java.io.File original=null;
56 * original archive IO handler
58 SessionFile odoclock = null;
60 * Original archive reader class
62 VamsasArchiveReader odoc = null;
64 * true if a real vamsas document is being written.
66 boolean vamsasdocument=true;
68 * Output stream for archived data
70 JarOutputStream newarchive=null;
72 * JarEntries written to archive
74 Hashtable entries = null;
76 * true if we aren't just updating an archive
78 private boolean virginArchive=false;
80 * Create a new vamsas archive
81 * File locks are made immediately to avoid contention
83 * @param archive - file spec for new vamsas archive
84 * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
85 * @throws IOException if call to accessOriginal failed for updates, or openArchive failed.
87 public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {
89 if (archive==null || (archive!=null && !(archive.getParentFile().canWrite() && (!archive.exists() || archive.canWrite())))) {
90 log.fatal("Expect Badness! -- Invalid parameters for VamsasArchive constructor:"+((archive!=null)
91 ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
94 this.vamsasdocument = vamsasdocument;
95 if (archive.exists()) {
96 this.original = archive;
97 this.archive = null; // archive will be a temp file when the open method is called
100 this.accessOriginal();
101 } catch (IOException e) {
102 throw new IOException("Lock failed for existing archive"+archive);
105 this.original = null;
106 this.archive = archive; // archive is written in place.
107 virginArchive = true;
109 this.openArchive(); // open archive
112 * name of backup of existing archive that has been updated/overwritten.
113 * onlu one backup will be made - and this is it.
115 File originalBackup = null;
117 private void makeBackup() {
118 if (!virginArchive) {
119 if (originalBackup==null && original!=null && original.exists()) {
122 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
124 catch (IOException e) {
125 log.warn("Problem whilst making a backup of original archive.",e);
132 * called after archive is written to put file in its final place
133 * TODO: FINISH ?? original should have sessionFile, and archive should also have sessionFile
135 private void updateOriginal() {
136 if (!virginArchive) {
137 // make sure original document really is backed up and then overwrite it.
139 // try to shut the odoc reader.
143 // Make a backup if it isn't done already
146 // copy new Archive data that was writen to a temporary file
147 odoclock.updateFrom(null, rchive);
149 catch (IOException e) {
150 // TODO: LATER: decide if leaving nastily named backup files around is necessary.
151 log.error("Problem updating archive from temporary file! - backup left in '"
152 +backupFile().getAbsolutePath()+"'",e);
154 // Tidy up if necessary.
160 boolean donotdeletebackup=false;
162 * called by app to get name of backup if it was made.
163 * If this is called, the caller app *must* delete the backup themselves.
164 * @return null or a valid file object
166 public File backupFile() {
168 if (!virginArchive) {
170 donotdeletebackup=false; // external reference has been made.
171 return ((original!=null) ? originalBackup : null);
177 * @return JarEntry name for the vamsas XML stream in this archive
179 protected String getDocumentJarEntry() {
181 return VamsasArchiveReader.VAMSASDOC;
182 return VamsasArchiveReader.VAMSASXML;
186 * @return true if Vamsas Document has been written to archive
188 protected boolean isDocumentWritten() {
189 if (newarchive==null)
190 log.warn("isDocumentWritten() called for unopened archive.");
192 if (entries.containsKey(getDocumentJarEntry()))
198 * Add unique entry strings to internal JarEntries list.
200 * @return true if entry was unique and was added.
202 private boolean addEntry(String entry) {
204 entries=new Hashtable();
205 if (entries.containsKey(entry))
207 entries.put(entry, new Integer(entries.size()));
211 * adds named entry to newarchive or returns false.
213 * @return true if entry was unique and could be added
214 * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
216 private boolean addValidEntry(String entry) throws IOException {
217 JarEntry je = new JarEntry(entry);
218 if (!addEntry(entry))
220 newarchive.putNextEntry(je);
225 * opens the new archive ready for writing. If the new archive is replacing an existing one,
226 * then the existing archive will be locked, and the new archive written to a temporary file.
227 * The new archive will be put in place once close() is called.
228 * @throws IOException
230 private void openArchive() throws IOException {
232 if (newarchive!=null) {
233 log.warn("openArchive() called multiple times.");
234 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
236 if (archive==null && (virginArchive || original==null)) {
237 log.warn("openArchive called on uninitialised VamsasArchive object.");
238 throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
240 if (!virginArchive) {
243 // make a temporary file to write to
244 archive = File.createTempFile(original.getName(), ".new",original.getParentFile());
246 if (archive.exists())
247 log.warn("New archive file name already in use! Possible lock failure imminent?");
250 rchive = new SessionFile(archive);
252 newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
253 entries = new Hashtable();
256 * Safely initializes the VAMSAS XML document Jar Entry.
257 * @return Writer to pass to the marshalling function.
258 * @throws IOException if a document entry has already been written.
260 public PrintWriter getDocumentOutputStream() throws IOException {
261 if (newarchive==null)
263 if (!isDocumentWritten()) {
265 if (addValidEntry(getDocumentJarEntry()))
266 return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
267 } catch (Exception e) {
268 log.warn("Problems opening XML document JarEntry stream",e);
271 throw new IOException("Vamsas Document output stream is already written.");
276 * Opens and returns the applicationData output stream for the appdataReference string.
277 * @param appdataReference
278 * @return Output stream to write to
279 * @throws IOException
281 public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
282 if (newarchive==null)
284 if (addValidEntry(appdataReference)) {
285 return new AppDataOutputStream(newarchive);
291 * Stops any current write to archive, and reverts to the backup if it exists.
292 * All existing locks on the original will be released. All backup files are removed.
294 public boolean cancelArchive() {
295 if (newarchive!=null) {
299 } catch (Exception e) {
300 log.debug("Whilst closing newarchive",e);
302 if (!virginArchive) {
303 // then there is something to recover.
307 catch (Exception e) {
308 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
314 log.warn("Client Error: cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
316 closeAndReset(); // tidy up and release locks.
321 * recovers the original file's contents from the (temporary) backup.
322 * @throws Exception if any SessionFile or file removal operations fail.
324 private void recoverBackup() throws Exception {
325 if (originalBackup!=null) {
326 // backup has been made.
327 // revert from backup and delete it (changing backup filename)
329 rchive = new SessionFile(original);
331 SessionFile bckup = new SessionFile(originalBackup);
333 rchive.updateFrom(null, bckup); // recover from backup file.
340 * forget about any backup that was made - removing it first if it was only temporary.
342 private void removeBackup() {
343 if (originalBackup!=null) {
344 log.debug("Removing backup in "+originalBackup.getAbsolutePath());
345 if (!donotdeletebackup)
346 if (!originalBackup.delete())
347 log.info("VamsasArchive couldn't remove temporary backup "+originalBackup.getAbsolutePath());
352 * only do this if you want to destroy the current file output stream
355 private void closeAndReset() {
360 if (original!=null) {
367 if (odoclock!=null) {
368 odoclock.unlockFile();
378 private final int _TRANSFER_BUFFER=4096*4;
380 * open original archive file for exclusive (locked) reading.
381 * @throws IOException
383 private void accessOriginal() throws IOException {
384 if (original!=null && original.exists()) {
386 odoclock = new SessionFile(original);
389 odoc = new VamsasArchiveReader(original);
394 * Convenience method to copy over the referred entry from the backup to the new version.
395 * Warning messages are raised if no backup exists or the
396 * entry doesn't exist in the backed-up original.
397 * Duplicate writes return true - but a warning message will also be raised.
398 * @param AppDataReference
399 * @return true if AppDataReference now exists in the new document
400 * @throws IOException
402 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
403 return transferAppDataEntry(AppDataReference, AppDataReference);
406 * Transfers an AppDataReference from old to new vamsas archive, with a name change.
407 * @see transferAppDataEntry(String AppDataReference)
408 * @param AppDataReference
409 * @param NewAppDataReference - AppDataReference in new Archive
411 * @throws IOException
413 public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
414 // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
415 if (AppDataReference==null)
416 throw new IOException("null AppDataReference!");
417 if (original==null || !original.exists()) {
418 log.warn("No backup archive exists.");
421 if (entries.containsKey(NewAppDataReference)) {
422 log.warn("Attempt to write '"+NewAppDataReference+"' twice! - IGNORED");
428 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
430 if (adstream==null) {
431 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
435 java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
436 // copy over the bytes
439 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
441 if ((written = adstream.read(buffer))>-1) {
442 adout.write(buffer, 0, written);
443 log.debug("Transferring "+written+".");
446 } while (written>-1);
447 log.debug("Sucessfully transferred AppData for '"
448 +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
452 * Tidies up and closes archive, removing any backups that were created.
453 * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
454 * TODO: ensure all extant AppDataReference jar entries are transferred to new Jar
455 * TODO: provide convenient mechanism for generating new unique AppDataReferences and adding them to the document
457 public void closeArchive() throws IOException {
458 if (newarchive!=null) {
459 newarchive.closeEntry();
460 if (!isDocumentWritten())
461 log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
466 log.warn("Attempt to close archive that has not been opened for writing.");
470 * Access original archive if it exists, pass the reader to the client
471 * Note: this is NOT thread safe and a call to closeArchive() will by necessity
472 * close and invalidate the VamsasArchiveReader object.
473 * @return null if no original archive exists.
475 public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
476 if (!virginArchive) {
483 * returns original document's root vamsas elements.
485 * @throws IOException
486 * @throws org.exolab.castor.xml.MarshalException
487 * @throws org.exolab.castor.xml.ValidationException
489 public object[] getOriginalRoots() throws IOException,
490 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
491 return VamsasArchive.getOriginalRoots(this);
494 * Access original document if it exists, and get VAMSAS root objects.
495 * @return vector of vamsas roots from original document
496 * @throws IOException
498 public static object[] getOriginalRoots(VamsasArchive ths) throws IOException,
499 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
500 VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
503 if (oReader.isValid()) {
504 InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
505 VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
507 return doc.getVAMSAS();
508 // TODO ensure embedded appDatas are garbage collected to save memory
510 InputStream vxmlis = oReader.getVamsasXmlStream();
511 if (vxmlis!=null) { // Might be an old vamsas file.
512 BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
513 InputStreamReader vxml = new InputStreamReader(ixml);
514 VAMSAS root[] = new VAMSAS[1];
515 root[0] = VAMSAS.unmarshal(vxml);
523 protected VorbaIdFactory vorba = null;
526 * @return Returns the current VorbaIdFactory for the archive.
528 public VorbaIdFactory getVorba() {
533 * @param vorba the VorbaIdFactory to use for accessing vamsas objects.
535 public void setVorba(VorbaIdFactory vorba) {
540 * Access and return current vamsas Document, if it exists, or create a new one
541 * (without affecting VamsasArchive object state - so is NOT THREAD SAFE)
542 * TODO: possibly modify internal state to lock low-level files
543 * (like the IClientDocument interface instance constructer would do)
544 * @see org.vamsas.simpleclient.VamsasArchive.getOriginalVamsasDocument for additional caveats
547 * @throws IOException
548 * @throws org.exolab.castor.xml.MarshalException
549 * @throws org.exolab.castor.xml.ValidationException
551 public VamsasDocument getVamsasDocument() throws IOException,
552 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
553 VamsasDocument doc = getOriginalVamsasDocument(this, getVorba());
556 // Create a new document and return it
557 doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()},
558 ProvenanceStuff.newProvenance("org.vamsas.simpleclient.VamsasArchive", "Created new empty document")
559 , VersionEntries.latestVersion());
563 * Access the original vamsas document for a VamsasArchive class, and return it.
564 * Users of the VamsasArchive class should use the getVamsasDocument method to retrieve
565 * the current document - only use this one if you want the 'backup' version.
566 * TODO: catch OutOfMemoryError - they are likely to occur here.
567 * NOTE: vamsas.xml datastreams are constructed as 'ALPHA_VERSION' vamsas documents.
569 * @return null if no document exists.
570 * @throws IOException
571 * @throws org.exolab.castor.xml.MarshalException
572 * @throws org.exolab.castor.xml.ValidationException
574 public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths) throws IOException,
575 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
576 return VamsasArchive.getOriginalVamsasDocument(ths, null);
578 private VorbaIdFactory makeDefaultFactory(VorbaIdFactory vorba) {
582 vorba = IdFactory.getDummyFactory("simpleclient.VamsasArchive");
583 setVorba(vorba); // save for later use
589 * Uses VorbaXmlBinder to retrieve the VamsasDocument from the original archive referred to by ths
593 * @throws IOException
594 * @throws org.exolab.castor.xml.MarshalException
595 * @throws org.exolab.castor.xml.ValidationException
597 public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths, VorbaIdFactory vorba) throws IOException,
598 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
599 VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
602 vorba = ths.makeDefaultFactory(vorba);
603 if (oReader.isValid()) {
604 InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
605 Object unmarsh[] = VorbaXmlBinder.getVamsasObjects(vdoc, vorba, new VamsasDocument());
607 log.fatal("Couldn't unmarshall document!");
609 object vobjs = (object) unmarsh[0];
611 VamsasDocument doc=(VamsasDocument) vobjs;
615 log.debug("Found no VamsasDocument object in properly formatted Vamsas Archive.");
617 // deprecated data handler
618 InputStream vxmlis = oReader.getVamsasXmlStream();
619 if (vxmlis!=null) { // Might be an old vamsas file.
620 BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
621 InputStreamReader vxml = new InputStreamReader(ixml);
622 Object unmarsh[] = VorbaXmlBinder.getVamsasObjects(vxml, vorba, new VAMSAS());
625 log.fatal("Couldn't unmarshall document!");
627 VAMSAS root[]= new VAMSAS[] { null};
628 root[0] = (VAMSAS) unmarsh[0];
631 log.debug("Found no VAMSAS object in VamsasXML stream.");
633 log.debug("Making new VamsasDocument from VamsasXML stream.");
634 VamsasDocument doc = DocumentStuff.newVamsasDocument(root,
635 ProvenanceStuff.newProvenance(
636 "org.vamsas.simpleclient.VamsasArchive", // TODO: VAMSAS: decide on 'system' operations provenance form
637 "Vamsas Document constructed from vamsas.xml in <file>"
638 // TODO: VAMSAS: decide on machine readable info embedding in provenance should be done
639 +ths.original+"</file>"), VersionEntries.ALPHA_VERSION);
647 // otherwise - there was no valid original document to read.
650 public void putVamsasDocument(VamsasDocument doc) throws IOException,
651 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
652 VorbaIdFactory vorba = makeDefaultFactory(getVorba());
653 VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);