more fixes for clientdocument/simpleclient. Testign the vamsasArchive reader and...
[vamsas.git] / src / org / vamsas / client / simpleclient / VamsasArchive.java
1 package org.vamsas.client.simpleclient;
2
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
5 import java.io.DataOutputStream;
6 import java.io.File;
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;
18
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.Vobject;
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;
35
36 /**
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
41  * @author jimp
42  *
43  */
44 public class VamsasArchive {
45   private static Log log = LogFactory.getLog(VamsasArchive.class);
46   /**
47    * Access original document if it exists, and get VAMSAS root objects.
48    * @return vector of vamsas roots from original document
49    * @throws IOException
50    */
51   public static Vobject[] getOriginalRoots(VamsasArchive ths) throws IOException, 
52   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
53     VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
54     if (oReader!=null) {
55       
56       if (oReader.isValid()) {
57         InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
58         VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
59         if (doc!=null) 
60           return doc.getVAMSAS();
61         // TODO ensure embedded appDatas are garbage collected to save memory
62       } else {
63         InputStream vxmlis = oReader.getVamsasXmlStream();
64         if (vxmlis!=null) { // Might be an old vamsas file.
65           BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
66           InputStreamReader vxml = new InputStreamReader(ixml);
67           VAMSAS root[] = new VAMSAS[1];
68           root[0] = VAMSAS.unmarshal(vxml);
69           if (root[0]!=null)
70             return root;
71         }
72       }
73     }
74     return null;
75   }
76   /**
77    * Access the original vamsas document for a VamsasArchive class, and return it.
78    * Users of the VamsasArchive class should use the getVamsasDocument method to retrieve
79    * the current document - only use this one if you want the 'backup' version.
80    * TODO: catch OutOfMemoryError - they are likely to occur here.
81    * NOTE: vamsas.xml datastreams are constructed as 'ALPHA_VERSION' vamsas documents.
82    * @param ths
83    * @return null if no document exists.
84    * @throws IOException
85    * @throws org.exolab.castor.xml.MarshalException
86    * @throws org.exolab.castor.xml.ValidationException
87    */
88   public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths) throws IOException, 
89   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
90     return VamsasArchive.getOriginalVamsasDocument(ths, null);
91   } 
92   /**
93    * Uses VorbaXmlBinder to retrieve the VamsasDocument from the original archive referred to by ths
94    * @param ths
95    * @param vorba
96    * @return
97    * @throws IOException
98    * @throws org.exolab.castor.xml.MarshalException
99    * @throws org.exolab.castor.xml.ValidationException
100    */
101   public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths, VorbaIdFactory vorba) throws IOException, 
102   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
103     VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
104     if (oReader!=null) {
105       ths.setVorba(vorba);
106       return ths.vorba.getVamsasDocument(oReader);
107     }
108     // otherwise - there was no valid original document to read.
109     return null;    
110   }
111   /**
112    * destination of new archive data (tempfile if virginarchive=true, original archive location otherwise)
113    */
114   java.io.File archive=null;
115   /**
116    * locked IO handler for new archive file
117    */
118   SessionFile rchive=null;
119   /**
120    * original archive file to be updated (or null if virgin) where new data will finally reside
121    */
122   java.io.File original=null;
123   /**
124    * original archive IO handler
125    */
126   SessionFile odoclock = null;
127   Lock destinationLock = null;
128   /**
129    * Original archive reader class
130    */
131   VamsasArchiveReader odoc = null;
132   /**
133    * true if a real vamsas document is being written.
134    */
135   boolean vamsasdocument=true;
136   /**
137    * Output stream for archived data
138    */
139   JarOutputStream newarchive=null;
140   /**
141    * JarEntries written to archive
142    */
143   Hashtable entries = null;
144   
145   /**
146    * true if we aren't just updating an archive
147    */
148   private boolean virginArchive=false;
149   
150   /**
151    * name of backup of existing archive that has been updated/overwritten.
152    * only one backup will be made - and this is it.
153    */
154   File originalBackup = null;
155   
156   boolean donotdeletebackup=false;
157   private final int _TRANSFER_BUFFER=4096*4;
158   protected SimpleDocument vorba = null;
159   /**
160    * LATER: ? CUT'n'Paste error ?
161    * Access and return current vamsas Document, if it exists, or create a new one 
162    * (without affecting VamsasArchive object state - so is NOT THREAD SAFE)
163    * _TODO: possibly modify internal state to lock low-level files 
164    * (like the IClientDocument interface instance constructer would do) 
165    * @see org.vamsas.simpleclient.VamsasArchive.getOriginalVamsasDocument for additional caveats
166    * 
167    * @return
168    * @throws IOException
169    * @throws org.exolab.castor.xml.MarshalException
170    * @throws org.exolab.castor.xml.ValidationException
171    * ????? where does this live JBPNote ?
172    */
173   private VamsasDocument _doc=null;
174   
175   /**
176    * Create a new vamsas archive
177    * File locks are made immediately to avoid contention
178    *  
179    * @param archive - file spec for new vamsas archive
180    * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
181    * @throws IOException if call to accessOriginal failed for updates, or openArchive failed.
182    */
183   public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {
184     this(archive, false, vamsasdocument, null);
185   }
186   public VamsasArchive(File archive, boolean vamsasdocument, boolean overwrite) throws IOException {
187     this(archive, overwrite, vamsasdocument, null);
188   }
189   /**
190    * 
191    * @param archive file to write
192    * @param overwrite true if original contents should be deleted
193    * @param vamsasdocument true if a proper VamsasDocument archive is to be written.
194    * @param extantLock SessionFile object holding a lock for the <object>archive</object> 
195    * @throws IOException
196    */
197   public VamsasArchive(File archive, boolean overwrite, boolean vamsasdocument, SessionFile extantLock) throws IOException {
198     super();
199     if (archive==null || (archive!=null && !(archive.getParentFile().canWrite() && (!archive.exists() || archive.canWrite())))) {
200       log.fatal("Expect Badness! -- Invalid parameters for VamsasArchive constructor:"+((archive!=null) 
201           ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
202       return;
203     }
204     
205     this.vamsasdocument = vamsasdocument;
206     if (archive.exists() && !overwrite) {
207       this.original = archive;
208       if (extantLock!=null)
209         this.odoclock = extantLock;
210       else 
211         this.odoclock = new SessionFile(archive); 
212       odoclock.lockFile(); // lock the file *immediatly*
213       this.archive = null;       // archive will be a temp file when the open method is called
214       virginArchive=false;
215       try {
216         this.accessOriginal();
217       } catch (IOException e)  {
218         throw new IOException("Lock failed for existing archive"+archive);
219       }
220     } else {
221       this.original = null;
222       this.archive = archive; // archive is written in place.
223       if (extantLock!=null)
224         rchive=extantLock;
225       else
226         rchive = new SessionFile(archive);
227       rchive.lockFile();
228       if (rchive.fileLock==null || rchive.fileLock.rafile==null)
229         throw new IOException("Lock failed for new archive"+archive);
230       rchive.fileLock.rafile.setLength(0); // empty the archive.
231       virginArchive = true;
232     }
233     this.openArchive(); // open archive
234   }
235   /**
236    * open original archive file for exclusive (locked) reading.
237    * @throws IOException
238    */
239   private void accessOriginal() throws IOException {
240     if (original!=null && original.exists()) {
241       if (odoclock==null) 
242         odoclock = new SessionFile(original);
243       odoclock.lockFile();
244       if (odoc == null) 
245         odoc = new VamsasArchiveReader(original);
246     }
247   }
248   
249   /**
250    * Add unique entry strings to internal JarEntries list.
251    * @param entry
252    * @return true if entry was unique and was added.
253    */
254   private boolean addEntry(String entry) {
255     if (entries!=null)
256       entries=new Hashtable();
257     if (entries.containsKey(entry))
258       return false;
259     entries.put(entry, new Integer(entries.size()));
260     return true;
261   }
262   /**
263    * adds named entry to newarchive or returns false.
264    * @param entry
265    * @return true if entry was unique and could be added
266    * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
267    */
268   private boolean addValidEntry(String entry) throws IOException {
269     JarEntry je = new JarEntry(entry);
270     if (!addEntry(entry))
271       return false;
272     newarchive.putNextEntry(je);
273     return true;
274   }
275   /**
276    * called by app to get name of backup if it was made.
277    * If this is called, the caller app *must* delete the backup themselves.
278    * @return null or a valid file object
279    */
280   public File backupFile() {
281     
282     if (!virginArchive) {
283       makeBackup();
284       donotdeletebackup=false; // external reference has been made.
285       return ((original!=null) ? originalBackup : null);
286     }
287     return null;
288   }
289   
290   /**
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.
293    */
294   public boolean cancelArchive() {
295     if (newarchive!=null) {
296       try { 
297         newarchive.close();
298         
299       } catch (Exception e) {
300         log.debug("Whilst closing newarchive",e);
301       };
302       if (!virginArchive) {
303         // then there is something to recover.
304         try {
305           recoverBackup();
306         }
307         catch (Exception e) {
308           log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
309           return false;
310         }
311       }
312       
313     } else {
314       log.warn("Client Error: cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
315     }
316     closeAndReset(); // tidy up and release locks.
317     return true;
318   }
319   
320   /**
321    * only do this if you want to destroy the current file output stream
322    *
323    */
324   private void closeAndReset() {
325     if (rchive!=null) {
326       rchive.unlockFile();
327       rchive=null;
328     }
329     if (original!=null) {
330       if (odoc!=null) {
331         odoc.close();
332         odoc=null;
333       }
334       if (archive!=null)
335         archive.delete();
336       if (odoclock!=null) {
337         odoclock.unlockFile();
338         odoclock = null;
339       }
340     }
341     removeBackup();
342     newarchive=null;
343     original=null;
344     entries=null;
345   }
346   /**
347    * Tidies up and closes archive, removing any backups that were created.
348    * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
349    * TODO: ensure all extant AppDataReference jar entries are transferred to new Jar
350    * TODO: provide convenient mechanism for generating new unique AppDataReferences and adding them to the document
351    */
352   public void closeArchive() throws IOException {
353     if (newarchive!=null) {
354       newarchive.closeEntry();
355       if (!isDocumentWritten())
356         log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
357       newarchive.close();
358       updateOriginal();
359       closeAndReset();
360     } else {
361       log.warn("Attempt to close archive that has not been opened for writing.");
362     }
363   }
364   /**
365    * Opens and returns the applicationData output stream for the appdataReference string.
366    * @param appdataReference
367    * @return Output stream to write to
368    * @throws IOException
369    */
370   public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
371     if (newarchive==null)
372       throw new IOException("Attempt to write to closed VamsasArchive object.");
373     if (addValidEntry(appdataReference)) {
374       return new AppDataOutputStream(newarchive);
375     }
376     return null;
377   }
378   
379   /**
380    * 
381    * @return JarEntry name for the vamsas XML stream in this archive
382    */
383   protected String getDocumentJarEntry() {
384     if (vamsasdocument)
385       return VamsasArchiveReader.VAMSASDOC;
386     return VamsasArchiveReader.VAMSASXML;
387   }
388   /**
389    * Safely initializes the VAMSAS XML document Jar Entry. 
390    * @return Writer to pass to the marshalling function.
391    * @throws IOException if a document entry has already been written. 
392    */
393   public PrintWriter getDocumentOutputStream() throws IOException {
394     if (newarchive==null)
395       openArchive();
396     if (!isDocumentWritten()) {
397       try {
398         if (addValidEntry(getDocumentJarEntry())) 
399           return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
400       } catch (Exception e) {
401         log.warn("Problems opening XML document JarEntry stream",e);
402       }
403     } else {
404       throw new IOException("Vamsas Document output stream is already written.");
405     }
406     return null;
407   }
408   
409   /**
410    * Access original archive if it exists, pass the reader to the client
411    * Note: this is NOT thread safe and a call to closeArchive() will by necessity 
412    * close and invalidate the VamsasArchiveReader object.
413    * @return null if no original archive exists.
414    */
415   public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
416     if (!virginArchive) {
417       accessOriginal();
418       return odoc;
419     }
420     return null;
421   }
422   /**
423    * returns original document's root vamsas elements.
424    * @return
425    * @throws IOException
426    * @throws org.exolab.castor.xml.MarshalException
427    * @throws org.exolab.castor.xml.ValidationException
428    */
429   public Vobject[] getOriginalRoots() throws IOException, 
430   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException  {
431     return VamsasArchive.getOriginalRoots(this);
432   }
433   /**
434    * @return original document or a new empty document (with default provenance)
435    * @throws IOException
436    * @throws org.exolab.castor.xml.MarshalException
437    * @throws org.exolab.castor.xml.ValidationException
438    */
439   public VamsasDocument getVamsasDocument() throws IOException, 
440   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
441     return getVamsasDocument("org.vamsas.simpleclient.VamsasArchive", "Created new empty document", null);
442   }
443   /**
444    * Return the original document or a new empty document with initial provenance entry.
445    * @param provenance_user (null sets user to be the class name)
446    * @param provenance_action (null sets action to be 'created new document')
447    * @param version (null means use latest version)
448    * @return (original document or a new vamsas document with supplied provenance and version info)
449    * @throws IOException
450    * @throws org.exolab.castor.xml.MarshalException
451    * @throws org.exolab.castor.xml.ValidationException
452    */
453   public VamsasDocument getVamsasDocument(String provenance_user, String provenance_action, String version) throws IOException, 
454   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
455     if (_doc!=null)
456       return _doc;
457     _doc = getOriginalVamsasDocument(this, getVorba());
458     if (_doc!=null)
459       return _doc;
460     // validate parameters
461     if (provenance_user==null)
462       provenance_user = "org.vamsas.simpleclient.VamsasArchive";
463     if (provenance_action == null)
464       provenance_action="Created new empty document";
465     if (version==null)
466       version = VersionEntries.latestVersion();
467     // Create a new document and return it
468     _doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()}, 
469         ProvenanceStuff.newProvenance(provenance_user, provenance_action), version);
470     return _doc;
471   }
472   /**
473    * @return Returns the current VorbaIdFactory for the archive.
474    */
475   public VorbaIdFactory getVorba() {
476     if (vorba==null)
477       vorba = new SimpleDocument("simpleclient.VamsasArchive");
478     return vorba.getVorba();
479   }
480   /**
481    * @return true if Vamsas Document has been written to archive
482    */
483   protected boolean isDocumentWritten() {
484     if (newarchive==null)
485       log.warn("isDocumentWritten() called for unopened archive.");
486     if (entries!=null) {
487       if (entries.containsKey(getDocumentJarEntry()))
488         return true;
489     }
490     return false;
491   }
492   private void makeBackup() {
493     if (!virginArchive) {
494       if (originalBackup==null && original!=null && original.exists()) {
495         try {
496           accessOriginal();
497           originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
498         }
499         catch (IOException e) {
500           log.warn("Problem whilst making a backup of original archive.",e);
501         }
502       }
503     }
504   }
505   /**
506    * opens the new archive ready for writing. If the new archive is replacing an existing one, 
507    * then the existing archive will be locked, and the new archive written to a temporary file. 
508    * The new archive will be put in place once close() is called.
509    * @param doclock LATER - pass existing lock on document, if it exists.... no need yet?
510    * @throws IOException
511    */
512   private void openArchive() throws IOException {
513     
514     if (newarchive!=null) {
515       log.warn("openArchive() called multiple times.");
516       throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
517     }
518     if (archive==null && (virginArchive || original==null)) {
519       log.warn("openArchive called on uninitialised VamsasArchive object.");
520       throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
521     }
522     if (!virginArchive) {
523       // lock the original
524       accessOriginal();
525       // make a temporary file to write to
526       archive = File.createTempFile(original.getName(), ".new",original.getParentFile());
527     } else {
528       if (archive.exists())
529         log.warn("New archive file name already in use! Possible lock failure imminent?");
530     }
531     
532     if (rchive==null)
533       rchive = new SessionFile(archive);
534     if (!rchive.lockFile()) 
535       throw new IOException("Failed to get lock on file "+archive);
536     newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));  
537     entries = new Hashtable();
538   }
539   public void putVamsasDocument(VamsasDocument doc) throws IOException, 
540   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
541     putVamsasDocument(doc, getVorba());
542   }
543   
544   public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba) throws IOException, 
545   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
546     VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);
547   }
548   
549   /**
550    * recovers the original file's contents from the (temporary) backup. 
551    * @throws Exception if any SessionFile or file removal operations fail.
552    */
553   private void recoverBackup() throws Exception {
554     if (originalBackup!=null) {
555       // backup has been made.
556       // revert from backup and delete it (changing backup filename)
557       if (rchive==null) {
558         rchive = new SessionFile(original);
559       }
560       SessionFile bckup = new SessionFile(originalBackup);
561       
562       rchive.updateFrom(null, bckup); // recover from backup file.
563       bckup.unlockFile();
564       bckup=null;
565       removeBackup();
566     }
567   }
568   
569   /**
570    * forget about any backup that was made - removing it first if it was only temporary.
571    */
572   private void removeBackup() {
573     if (originalBackup!=null) {
574       log.debug("Removing backup in "+originalBackup.getAbsolutePath());
575       if (!donotdeletebackup)
576         if (!originalBackup.delete())
577           log.info("VamsasArchive couldn't remove temporary backup "+originalBackup.getAbsolutePath());
578       originalBackup=null;
579     }
580   } 
581   /**
582    * @param vorba the VorbaIdFactory to use for accessing vamsas objects.
583    */
584   public void setVorba(VorbaIdFactory Vorba) {
585     if (Vorba!=null) {
586       if (vorba==null)
587         vorba = new SimpleDocument(Vorba);
588       else
589         vorba.setVorba(Vorba);
590     } else
591       getVorba();
592   }
593   /**
594    * Convenience method to copy over the referred entry from the backup to the new version.
595    * Warning messages are raised if no backup exists or the 
596    * entry doesn't exist in the backed-up original.
597    * Duplicate writes return true - but a warning message will also be raised.
598    * @param AppDataReference
599    * @return true if AppDataReference now exists in the new document
600    * @throws IOException
601    */
602   public boolean transferAppDataEntry(String AppDataReference) throws IOException {
603     return transferAppDataEntry(AppDataReference, AppDataReference);
604   }
605   /**
606    * Validates the AppDataReference: not null and not already written to archive.
607    * @param AppDataReference
608    * @return true if valid. false if not
609    * @throws IOException for really broken references!
610    */
611   protected boolean _validNewAppDataReference(String newAppDataReference) throws IOException {
612     // LATER: Specify valid AppDataReference form in all VamsasArchive handlers
613     if (newAppDataReference==null)
614       throw new IOException("null newAppDataReference!");
615     if (entries.containsKey(newAppDataReference)) {
616       log.warn("Attempt to write '"+newAppDataReference+"' twice! - IGNORED");
617       // LATER: fix me? warning message should raise an exception here.
618       return false;
619     }
620     return true;
621   }
622   /**
623    * Transfers an AppDataReference from old to new vamsas archive, with a name change.
624    * @see transferAppDataEntry(String AppDataReference)
625    * @param AppDataReference
626    * @param NewAppDataReference - AppDataReference in new Archive
627    * @return
628    * @throws IOException
629    */
630   public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
631     if (original==null || !original.exists()) {
632       log.warn("No backup archive exists.");
633       return false;
634     }
635     if (AppDataReference==null)
636       throw new IOException("null AppDataReference!");
637
638     if (!_validNewAppDataReference(NewAppDataReference))
639       return false;
640     
641     accessOriginal();
642     
643     java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
644     
645     if (adstream==null) {
646       log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
647       return false;
648     }
649     
650     java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
651     // copy over the bytes
652     int written=-1;
653     long count=0;
654     byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
655     do {
656       if ((written = adstream.read(buffer))>-1) {
657         adout.write(buffer, 0, written);
658         log.debug("Transferring "+written+".");
659         count+=written;
660       }
661     } while (written>-1);
662     log.debug("Sucessfully transferred AppData for '"
663         +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
664     return true;
665   }
666   /**
667    * write data from a stream into an appData reference.
668    * @param AppDataReference - New AppDataReference not already written to archive
669    * @param adstream Source of data for appData reference - read until .read(buffer) returns -1
670    * @return true on success.
671    * @throws IOException for file IO or invalid AppDataReference string
672    */
673   public boolean writeAppdataFromStream(String AppDataReference, java.io.InputStream adstream) throws IOException {
674     if (!_validNewAppDataReference(AppDataReference)) {
675       log.warn("Invalid AppDataReference passed to writeAppdataFromStream");
676       throw new IOException("Invalid AppDataReference! (null, or maybe non-unique)!");
677     }
678       
679     if (AppDataReference==null) {
680       log.warn("null appdata passed.");
681       throw new IOException("Null AppDataReference");
682     }
683     
684     java.io.OutputStream adout = getAppDataStream(AppDataReference);
685     // copy over the bytes
686     int written=-1;
687     long count=0;
688     byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
689     do {
690       if ((written = adstream.read(buffer))>-1) {
691         adout.write(buffer, 0, written);
692         log.debug("Transferring "+written+".");
693         count+=written;
694       }
695     } while (written>-1);
696     return true;
697   }
698   /**
699    * transfers any AppDataReferences existing in the old document 
700    * that haven't already been transferred to the new one
701    * LATER: do the same for transfers requiring a namechange - more document dependent.
702    *  @return true if data was transferred.
703    */
704   public boolean transferRemainingAppDatas() throws IOException {
705     boolean transfered=false;
706     if (original==null || !original.exists()) {
707       log.warn("No backup archive exists.");
708       return false;
709     }
710     accessOriginal();
711     
712     if (getVorba()!=null) {
713       Vector originalRefs=null;
714       try {
715         originalRefs = vorba.getReferencedEntries(getVamsasDocument(), getOriginalArchiveReader());
716       } catch (Exception e) {
717         log.warn("Problems accessing original document entries!",e);
718       }
719       if (originalRefs!=null) {
720         Iterator ref = originalRefs.iterator();
721         while (ref.hasNext()) {
722           String oldentry = (String) ref.next();
723           if (oldentry!=null && !entries.containsKey(oldentry)) {
724             log.debug("Transferring remaining entry '"+oldentry+"'");
725             transfered |= transferAppDataEntry(oldentry);
726           }
727         }
728       }
729     } 
730     return transfered;
731   }
732   /**
733    * called after archive is written to put file in its final place
734    */
735   private void updateOriginal() {
736     if (!virginArchive) {
737       // make sure original document really is backed up and then overwrite it.
738       if (odoc!=null) {
739         // try to shut the odoc reader.
740         odoc.close();
741         odoc = null;
742       }
743       // Make a backup if it isn't done already
744       makeBackup();
745       try {
746         // copy new Archive data that was writen to a temporary file
747         odoclock.updateFrom(null, rchive);
748       }
749       catch (IOException e) {
750         // LATER: decide if leaving nastily named backup files around is necessary. 
751         log.error("Problem updating archive from temporary file! - backup left in '"
752             +backupFile().getAbsolutePath()+"'",e);
753       }
754       // Tidy up if necessary.
755       removeBackup();
756     } else {
757       
758     }
759   }
760 }