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.Iterator;
15 import java.util.Vector;
16 import java.util.jar.JarEntry;
17 import java.util.jar.JarOutputStream;
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.vamsas.client.ClientHandle;
22 import org.vamsas.client.IVorbaIdFactory;
23 import org.vamsas.client.SessionHandle;
24 import org.vamsas.client.UserHandle;
25 import org.vamsas.client.VorbaIdFactory;
26 import org.vamsas.client.VorbaXmlBinder;
27 import org.vamsas.client.object;
28 import org.vamsas.objects.core.ApplicationData;
29 import org.vamsas.objects.core.VAMSAS;
30 import org.vamsas.objects.core.VamsasDocument;
31 import org.vamsas.objects.utils.AppDataReference;
32 import org.vamsas.objects.utils.DocumentStuff;
33 import org.vamsas.objects.utils.ProvenanceStuff;
34 import org.vamsas.objects.utils.document.VersionEntries;
37 * Class for high-level io and Jar manipulation involved in creating
38 * or updating a vamsas archive (with backups).
39 * Writes to a temporary file and then swaps new file for backup.
40 * uses the sessionFile locking mechanism for safe I/O
44 public class VamsasArchive {
45 private static Log log = LogFactory.getLog(VamsasArchive.class);
47 * destination of new archive data (tempfile if virginarchive=true, original archive location otherwise)
49 java.io.File archive=null;
51 * locked IO handler for new archive file
53 SessionFile rchive=null;
55 * original archive file to be updated (or null if virgin) where new data will finally reside
57 java.io.File original=null;
59 * original archive IO handler
61 SessionFile odoclock = null;
63 * Original archive reader class
65 VamsasArchiveReader odoc = null;
67 * true if a real vamsas document is being written.
69 boolean vamsasdocument=true;
71 * Output stream for archived data
73 JarOutputStream newarchive=null;
75 * JarEntries written to archive
77 Hashtable entries = null;
79 * true if we aren't just updating an archive
81 private boolean virginArchive=false;
83 * Create a new vamsas archive
84 * File locks are made immediately to avoid contention
86 * @param archive - file spec for new vamsas archive
87 * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
88 * @throws IOException if call to accessOriginal failed for updates, or openArchive failed.
90 public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {
91 this(archive, vamsasdocument, null);
93 public VamsasArchive(File archive, boolean vamsasdocument, Lock extantLock) throws IOException {
96 if (archive==null || (archive!=null && !(archive.getParentFile().canWrite() && (!archive.exists() || archive.canWrite())))) {
97 log.fatal("Expect Badness! -- Invalid parameters for VamsasArchive constructor:"+((archive!=null)
98 ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
101 this.vamsasdocument = vamsasdocument;
102 if (archive.exists()) {
103 this.original = archive;
104 this.odoclock = new SessionFile(archive); // lock the file *immediatly*
105 odoclock.lockFile(extantLock);
106 this.archive = null; // archive will be a temp file when the open method is called
109 this.accessOriginal();
110 } catch (IOException e) {
111 throw new IOException("Lock failed for existing archive"+archive);
114 this.original = null;
115 this.archive = archive; // archive is written in place.
117 archive.createNewFile();
118 rchive=new SessionFile(archive); // lock the file *immediatly*
119 rchive.lockFile(extantLock);
121 virginArchive = true;
123 this.openArchive(); // open archive
126 * name of backup of existing archive that has been updated/overwritten.
127 * onlu one backup will be made - and this is it.
129 File originalBackup = null;
131 private void makeBackup() {
132 if (!virginArchive) {
133 if (originalBackup==null && original!=null && original.exists()) {
136 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
138 catch (IOException e) {
139 log.warn("Problem whilst making a backup of original archive.",e);
146 * called after archive is written to put file in its final place
148 private void updateOriginal() {
149 if (!virginArchive) {
150 // make sure original document really is backed up and then overwrite it.
152 // try to shut the odoc reader.
156 // Make a backup if it isn't done already
159 // copy new Archive data that was writen to a temporary file
160 odoclock.updateFrom(null, rchive);
162 catch (IOException e) {
163 // TODO: LATER: decide if leaving nastily named backup files around is necessary.
164 log.error("Problem updating archive from temporary file! - backup left in '"
165 +backupFile().getAbsolutePath()+"'",e);
167 // Tidy up if necessary.
173 boolean donotdeletebackup=false;
175 * called by app to get name of backup if it was made.
176 * If this is called, the caller app *must* delete the backup themselves.
177 * @return null or a valid file object
179 public File backupFile() {
181 if (!virginArchive) {
183 donotdeletebackup=false; // external reference has been made.
184 return ((original!=null) ? originalBackup : null);
190 * @return JarEntry name for the vamsas XML stream in this archive
192 protected String getDocumentJarEntry() {
194 return VamsasArchiveReader.VAMSASDOC;
195 return VamsasArchiveReader.VAMSASXML;
199 * @return true if Vamsas Document has been written to archive
201 protected boolean isDocumentWritten() {
202 if (newarchive==null)
203 log.warn("isDocumentWritten() called for unopened archive.");
205 if (entries.containsKey(getDocumentJarEntry()))
211 * Add unique entry strings to internal JarEntries list.
213 * @return true if entry was unique and was added.
215 private boolean addEntry(String entry) {
217 entries=new Hashtable();
218 if (entries.containsKey(entry))
220 entries.put(entry, new Integer(entries.size()));
224 * adds named entry to newarchive or returns false.
226 * @return true if entry was unique and could be added
227 * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
229 private boolean addValidEntry(String entry) throws IOException {
230 JarEntry je = new JarEntry(entry);
231 if (!addEntry(entry))
233 newarchive.putNextEntry(je);
238 * opens the new archive ready for writing. If the new archive is replacing an existing one,
239 * then the existing archive will be locked, and the new archive written to a temporary file.
240 * The new archive will be put in place once close() is called.
241 * @param doclock TODO
242 * @throws IOException
244 private void openArchive() throws IOException {
246 if (newarchive!=null) {
247 log.warn("openArchive() called multiple times.");
248 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
250 if (archive==null && (virginArchive || original==null)) {
251 log.warn("openArchive called on uninitialised VamsasArchive object.");
252 throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
254 if (!virginArchive) {
257 // make a temporary file to write to
258 archive = File.createTempFile(original.getName(), ".new",original.getParentFile());
260 if (archive.exists())
261 log.warn("New archive file name already in use! Possible lock failure imminent?");
265 rchive = new SessionFile(archive);
266 archive.createNewFile();
267 if (!rchive.lockFile())
268 throw new IOException("Failed to get lock on file "+archive);
270 newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
271 entries = new Hashtable();
274 * Safely initializes the VAMSAS XML document Jar Entry.
275 * @return Writer to pass to the marshalling function.
276 * @throws IOException if a document entry has already been written.
278 public PrintWriter getDocumentOutputStream() throws IOException {
279 if (newarchive==null)
281 if (!isDocumentWritten()) {
283 if (addValidEntry(getDocumentJarEntry()))
284 return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
285 } catch (Exception e) {
286 log.warn("Problems opening XML document JarEntry stream",e);
289 throw new IOException("Vamsas Document output stream is already written.");
294 * Opens and returns the applicationData output stream for the appdataReference string.
295 * @param appdataReference
296 * @return Output stream to write to
297 * @throws IOException
299 public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
300 if (newarchive==null)
301 throw new IOException("Attempt to write to closed VamsasArchive object.");
302 if (addValidEntry(appdataReference)) {
303 return new AppDataOutputStream(newarchive);
309 * Stops any current write to archive, and reverts to the backup if it exists.
310 * All existing locks on the original will be released. All backup files are removed.
312 public boolean cancelArchive() {
313 if (newarchive!=null) {
317 } catch (Exception e) {
318 log.debug("Whilst closing newarchive",e);
320 if (!virginArchive) {
321 // then there is something to recover.
325 catch (Exception e) {
326 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
332 log.warn("Client Error: cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
334 closeAndReset(); // tidy up and release locks.
339 * recovers the original file's contents from the (temporary) backup.
340 * @throws Exception if any SessionFile or file removal operations fail.
342 private void recoverBackup() throws Exception {
343 if (originalBackup!=null) {
344 // backup has been made.
345 // revert from backup and delete it (changing backup filename)
347 rchive = new SessionFile(original);
349 SessionFile bckup = new SessionFile(originalBackup);
351 rchive.updateFrom(null, bckup); // recover from backup file.
358 * forget about any backup that was made - removing it first if it was only temporary.
360 private void removeBackup() {
361 if (originalBackup!=null) {
362 log.debug("Removing backup in "+originalBackup.getAbsolutePath());
363 if (!donotdeletebackup)
364 if (!originalBackup.delete())
365 log.info("VamsasArchive couldn't remove temporary backup "+originalBackup.getAbsolutePath());
370 * only do this if you want to destroy the current file output stream
373 private void closeAndReset() {
378 if (original!=null) {
385 if (odoclock!=null) {
386 odoclock.unlockFile();
396 private final int _TRANSFER_BUFFER=4096*4;
398 * open original archive file for exclusive (locked) reading.
399 * @throws IOException
401 private void accessOriginal() throws IOException {
402 if (original!=null && original.exists()) {
404 odoclock = new SessionFile(original);
407 odoc = new VamsasArchiveReader(original);
412 * Convenience method to copy over the referred entry from the backup to the new version.
413 * Warning messages are raised if no backup exists or the
414 * entry doesn't exist in the backed-up original.
415 * Duplicate writes return true - but a warning message will also be raised.
416 * @param AppDataReference
417 * @return true if AppDataReference now exists in the new document
418 * @throws IOException
420 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
421 return transferAppDataEntry(AppDataReference, AppDataReference);
424 * Transfers an AppDataReference from old to new vamsas archive, with a name change.
425 * @see transferAppDataEntry(String AppDataReference)
426 * @param AppDataReference
427 * @param NewAppDataReference - AppDataReference in new Archive
429 * @throws IOException
431 public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
432 // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
433 if (AppDataReference==null)
434 throw new IOException("null AppDataReference!");
435 if (original==null || !original.exists()) {
436 log.warn("No backup archive exists.");
439 if (entries.containsKey(NewAppDataReference)) {
440 log.warn("Attempt to write '"+NewAppDataReference+"' twice! - IGNORED");
446 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
448 if (adstream==null) {
449 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
453 java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
454 // copy over the bytes
457 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
459 if ((written = adstream.read(buffer))>-1) {
460 adout.write(buffer, 0, written);
461 log.debug("Transferring "+written+".");
464 } while (written>-1);
465 log.debug("Sucessfully transferred AppData for '"
466 +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
470 * transfers any AppDataReferences existing in the old document
471 * that haven't already been transferred to the new one
472 * TODO: LATER: do the same for transfers requiring a namechange - more document dependent.
473 * @return true if data was transferred.
475 public boolean transferRemainingAppDatas() throws IOException {
476 boolean transfered=false;
477 if (original==null || !original.exists()) {
478 log.warn("No backup archive exists.");
483 if (getVorba()!=null) {
484 Vector originalRefs=null;
486 originalRefs = vorba.getReferencedEntries(getVamsasDocument(), getOriginalArchiveReader());
487 } catch (Exception e) {
488 log.warn("Problems accessing original document entries!",e);
490 if (originalRefs!=null) {
491 Iterator ref = originalRefs.iterator();
492 while (ref.hasNext()) {
493 String oldentry = (String) ref.next();
494 if (oldentry!=null && !entries.containsKey(oldentry)) {
495 log.debug("Transferring remaining entry '"+oldentry+"'");
496 transfered |= transferAppDataEntry(oldentry);
504 * Tidies up and closes archive, removing any backups that were created.
505 * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
506 * TODO: ensure all extant AppDataReference jar entries are transferred to new Jar
507 * TODO: provide convenient mechanism for generating new unique AppDataReferences and adding them to the document
509 public void closeArchive() throws IOException {
510 if (newarchive!=null) {
511 newarchive.closeEntry();
512 if (!isDocumentWritten())
513 log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
518 log.warn("Attempt to close archive that has not been opened for writing.");
522 * Access original archive if it exists, pass the reader to the client
523 * Note: this is NOT thread safe and a call to closeArchive() will by necessity
524 * close and invalidate the VamsasArchiveReader object.
525 * @return null if no original archive exists.
527 public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
528 if (!virginArchive) {
535 * returns original document's root vamsas elements.
537 * @throws IOException
538 * @throws org.exolab.castor.xml.MarshalException
539 * @throws org.exolab.castor.xml.ValidationException
541 public object[] getOriginalRoots() throws IOException,
542 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
543 return VamsasArchive.getOriginalRoots(this);
546 * Access original document if it exists, and get VAMSAS root objects.
547 * @return vector of vamsas roots from original document
548 * @throws IOException
550 public static object[] getOriginalRoots(VamsasArchive ths) throws IOException,
551 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
552 VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
555 if (oReader.isValid()) {
556 InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
557 VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
559 return doc.getVAMSAS();
560 // TODO ensure embedded appDatas are garbage collected to save memory
562 InputStream vxmlis = oReader.getVamsasXmlStream();
563 if (vxmlis!=null) { // Might be an old vamsas file.
564 BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
565 InputStreamReader vxml = new InputStreamReader(ixml);
566 VAMSAS root[] = new VAMSAS[1];
567 root[0] = VAMSAS.unmarshal(vxml);
575 protected SimpleDocument vorba = null;
578 * @return Returns the current VorbaIdFactory for the archive.
580 public VorbaIdFactory getVorba() {
582 vorba = new SimpleDocument("simpleclient.VamsasArchive");
583 return vorba.getVorba();
587 * @param vorba the VorbaIdFactory to use for accessing vamsas objects.
589 public void setVorba(VorbaIdFactory Vorba) {
592 vorba = new SimpleDocument(Vorba);
594 vorba.setVorba(Vorba);
600 * Access and return current vamsas Document, if it exists, or create a new one
601 * (without affecting VamsasArchive object state - so is NOT THREAD SAFE)
602 * TODO: possibly modify internal state to lock low-level files
603 * (like the IClientDocument interface instance constructer would do)
604 * @see org.vamsas.simpleclient.VamsasArchive.getOriginalVamsasDocument for additional caveats
607 * @throws IOException
608 * @throws org.exolab.castor.xml.MarshalException
609 * @throws org.exolab.castor.xml.ValidationException
611 private VamsasDocument _doc=null;
612 public VamsasDocument getVamsasDocument() throws IOException,
613 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
616 _doc = getOriginalVamsasDocument(this, getVorba());
619 // Create a new document and return it
620 _doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()},
621 ProvenanceStuff.newProvenance("org.vamsas.simpleclient.VamsasArchive", "Created new empty document")
622 , VersionEntries.latestVersion());
626 * Access the original vamsas document for a VamsasArchive class, and return it.
627 * Users of the VamsasArchive class should use the getVamsasDocument method to retrieve
628 * the current document - only use this one if you want the 'backup' version.
629 * TODO: catch OutOfMemoryError - they are likely to occur here.
630 * NOTE: vamsas.xml datastreams are constructed as 'ALPHA_VERSION' vamsas documents.
632 * @return null if no document exists.
633 * @throws IOException
634 * @throws org.exolab.castor.xml.MarshalException
635 * @throws org.exolab.castor.xml.ValidationException
637 public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths) throws IOException,
638 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
639 return VamsasArchive.getOriginalVamsasDocument(ths, null);
643 * Uses VorbaXmlBinder to retrieve the VamsasDocument from the original archive referred to by ths
647 * @throws IOException
648 * @throws org.exolab.castor.xml.MarshalException
649 * @throws org.exolab.castor.xml.ValidationException
651 public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths, VorbaIdFactory vorba) throws IOException,
652 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
653 VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
656 return ths.vorba.getVamsasDocument(oReader);
658 // otherwise - there was no valid original document to read.
662 public void putVamsasDocument(VamsasDocument doc) throws IOException,
663 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
664 putVamsasDocument(doc, getVorba());
666 public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba) throws IOException,
667 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
668 VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);