d510d51218da61ef1b350f6a3ae67f8710fba9ac
[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       rchive.fileLock.rafile.setLength(0); // empty the archive.
229       virginArchive = true;
230     }
231     this.openArchive(); // open archive
232   }
233   /**
234    * open original archive file for exclusive (locked) reading.
235    * @throws IOException
236    */
237   private void accessOriginal() throws IOException {
238     if (original!=null && original.exists()) {
239       if (odoclock==null) 
240         odoclock = new SessionFile(original);
241       odoclock.lockFile();
242       if (odoc == null) 
243         odoc = new VamsasArchiveReader(original);
244     }
245   }
246   
247   /**
248    * Add unique entry strings to internal JarEntries list.
249    * @param entry
250    * @return true if entry was unique and was added.
251    */
252   private boolean addEntry(String entry) {
253     if (entries!=null)
254       entries=new Hashtable();
255     if (entries.containsKey(entry))
256       return false;
257     entries.put(entry, new Integer(entries.size()));
258     return true;
259   }
260   /**
261    * adds named entry to newarchive or returns false.
262    * @param entry
263    * @return true if entry was unique and could be added
264    * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
265    */
266   private boolean addValidEntry(String entry) throws IOException {
267     JarEntry je = new JarEntry(entry);
268     if (!addEntry(entry))
269       return false;
270     newarchive.putNextEntry(je);
271     return true;
272   }
273   /**
274    * called by app to get name of backup if it was made.
275    * If this is called, the caller app *must* delete the backup themselves.
276    * @return null or a valid file object
277    */
278   public File backupFile() {
279     
280     if (!virginArchive) {
281       makeBackup();
282       donotdeletebackup=false; // external reference has been made.
283       return ((original!=null) ? originalBackup : null);
284     }
285     return null;
286   }
287   
288   /**
289    * Stops any current write to archive, and reverts to the backup if it exists.
290    * All existing locks on the original will be released. All backup files are removed.
291    */
292   public boolean cancelArchive() {
293     if (newarchive!=null) {
294       try { 
295         newarchive.close();
296         
297       } catch (Exception e) {
298         log.debug("Whilst closing newarchive",e);
299       };
300       if (!virginArchive) {
301         // then there is something to recover.
302         try {
303           recoverBackup();
304         }
305         catch (Exception e) {
306           log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
307           return false;
308         }
309       }
310       
311     } else {
312       log.warn("Client Error: cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
313     }
314     closeAndReset(); // tidy up and release locks.
315     return true;
316   }
317   
318   /**
319    * only do this if you want to destroy the current file output stream
320    *
321    */
322   private void closeAndReset() {
323     if (rchive!=null) {
324       rchive.unlockFile();
325       rchive=null;
326     }
327     if (original!=null) {
328       if (odoc!=null) {
329         odoc.close();
330         odoc=null;
331       }
332       if (archive!=null)
333         archive.delete();
334       if (odoclock!=null) {
335         odoclock.unlockFile();
336         odoclock = null;
337       }
338     }
339     removeBackup();
340     newarchive=null;
341     original=null;
342     entries=null;
343   }
344   /**
345    * Tidies up and closes archive, removing any backups that were created.
346    * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
347    * TODO: ensure all extant AppDataReference jar entries are transferred to new Jar
348    * TODO: provide convenient mechanism for generating new unique AppDataReferences and adding them to the document
349    */
350   public void closeArchive() throws IOException {
351     if (newarchive!=null) {
352       newarchive.closeEntry();
353       if (!isDocumentWritten())
354         log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
355       newarchive.close();
356       updateOriginal();
357       closeAndReset();
358     } else {
359       log.warn("Attempt to close archive that has not been opened for writing.");
360     }
361   }
362   /**
363    * Opens and returns the applicationData output stream for the appdataReference string.
364    * @param appdataReference
365    * @return Output stream to write to
366    * @throws IOException
367    */
368   public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
369     if (newarchive==null)
370       throw new IOException("Attempt to write to closed VamsasArchive object.");
371     if (addValidEntry(appdataReference)) {
372       return new AppDataOutputStream(newarchive);
373     }
374     return null;
375   }
376   
377   /**
378    * 
379    * @return JarEntry name for the vamsas XML stream in this archive
380    */
381   protected String getDocumentJarEntry() {
382     if (vamsasdocument)
383       return VamsasArchiveReader.VAMSASDOC;
384     return VamsasArchiveReader.VAMSASXML;
385   }
386   /**
387    * Safely initializes the VAMSAS XML document Jar Entry. 
388    * @return Writer to pass to the marshalling function.
389    * @throws IOException if a document entry has already been written. 
390    */
391   public PrintWriter getDocumentOutputStream() throws IOException {
392     if (newarchive==null)
393       openArchive();
394     if (!isDocumentWritten()) {
395       try {
396         if (addValidEntry(getDocumentJarEntry())) 
397           return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
398       } catch (Exception e) {
399         log.warn("Problems opening XML document JarEntry stream",e);
400       }
401     } else {
402       throw new IOException("Vamsas Document output stream is already written.");
403     }
404     return null;
405   }
406   
407   /**
408    * Access original archive if it exists, pass the reader to the client
409    * Note: this is NOT thread safe and a call to closeArchive() will by necessity 
410    * close and invalidate the VamsasArchiveReader object.
411    * @return null if no original archive exists.
412    */
413   public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
414     if (!virginArchive) {
415       accessOriginal();
416       return odoc;
417     }
418     return null;
419   }
420   /**
421    * returns original document's root vamsas elements.
422    * @return
423    * @throws IOException
424    * @throws org.exolab.castor.xml.MarshalException
425    * @throws org.exolab.castor.xml.ValidationException
426    */
427   public Vobject[] getOriginalRoots() throws IOException, 
428   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException  {
429     return VamsasArchive.getOriginalRoots(this);
430   }
431   /**
432    * @return original document or a new empty document (with default provenance)
433    * @throws IOException
434    * @throws org.exolab.castor.xml.MarshalException
435    * @throws org.exolab.castor.xml.ValidationException
436    */
437   public VamsasDocument getVamsasDocument() throws IOException, 
438   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
439     return getVamsasDocument("org.vamsas.simpleclient.VamsasArchive", "Created new empty document", null);
440   }
441   /**
442    * Return the original document or a new empty document with initial provenance entry.
443    * @param provenance_user (null sets user to be the class name)
444    * @param provenance_action (null sets action to be 'created new document')
445    * @param version (null means use latest version)
446    * @return (original document or a new vamsas document with supplied provenance and version info)
447    * @throws IOException
448    * @throws org.exolab.castor.xml.MarshalException
449    * @throws org.exolab.castor.xml.ValidationException
450    */
451   public VamsasDocument getVamsasDocument(String provenance_user, String provenance_action, String version) throws IOException, 
452   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
453     if (_doc!=null)
454       return _doc;
455     _doc = getOriginalVamsasDocument(this, getVorba());
456     if (_doc!=null)
457       return _doc;
458     // validate parameters
459     if (provenance_user==null)
460       provenance_user = "org.vamsas.simpleclient.VamsasArchive";
461     if (provenance_action == null)
462       provenance_action="Created new empty document";
463     if (version==null)
464       version = VersionEntries.latestVersion();
465     // Create a new document and return it
466     _doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()}, 
467         ProvenanceStuff.newProvenance(provenance_user, provenance_action), version);
468     return _doc;
469   }
470   /**
471    * @return Returns the current VorbaIdFactory for the archive.
472    */
473   public VorbaIdFactory getVorba() {
474     if (vorba==null)
475       vorba = new SimpleDocument("simpleclient.VamsasArchive");
476     return vorba.getVorba();
477   }
478   /**
479    * @return true if Vamsas Document has been written to archive
480    */
481   protected boolean isDocumentWritten() {
482     if (newarchive==null)
483       log.warn("isDocumentWritten() called for unopened archive.");
484     if (entries!=null) {
485       if (entries.containsKey(getDocumentJarEntry()))
486         return true;
487     }
488     return false;
489   }
490   private void makeBackup() {
491     if (!virginArchive) {
492       if (originalBackup==null && original!=null && original.exists()) {
493         try {
494           accessOriginal();
495           originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
496         }
497         catch (IOException e) {
498           log.warn("Problem whilst making a backup of original archive.",e);
499         }
500       }
501     }
502   }
503   /**
504    * opens the new archive ready for writing. If the new archive is replacing an existing one, 
505    * then the existing archive will be locked, and the new archive written to a temporary file. 
506    * The new archive will be put in place once close() is called.
507    * @param doclock LATER - pass existing lock on document, if it exists.... no need yet?
508    * @throws IOException
509    */
510   private void openArchive() throws IOException {
511     
512     if (newarchive!=null) {
513       log.warn("openArchive() called multiple times.");
514       throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
515     }
516     if (archive==null && (virginArchive || original==null)) {
517       log.warn("openArchive called on uninitialised VamsasArchive object.");
518       throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
519     }
520     if (!virginArchive) {
521       // lock the original
522       accessOriginal();
523       // make a temporary file to write to
524       archive = File.createTempFile(original.getName(), ".new",original.getParentFile());
525     } else {
526       if (archive.exists())
527         log.warn("New archive file name already in use! Possible lock failure imminent?");
528     }
529     
530     if (rchive==null)
531       rchive = new SessionFile(archive);
532     if (!rchive.lockFile()) 
533       throw new IOException("Failed to get lock on file "+archive);
534     newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));  
535     entries = new Hashtable();
536   }
537   public void putVamsasDocument(VamsasDocument doc) throws IOException, 
538   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
539     putVamsasDocument(doc, getVorba());
540   }
541   
542   public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba) throws IOException, 
543   org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
544     VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);
545   }
546   
547   /**
548    * recovers the original file's contents from the (temporary) backup. 
549    * @throws Exception if any SessionFile or file removal operations fail.
550    */
551   private void recoverBackup() throws Exception {
552     if (originalBackup!=null) {
553       // backup has been made.
554       // revert from backup and delete it (changing backup filename)
555       if (rchive==null) {
556         rchive = new SessionFile(original);
557       }
558       SessionFile bckup = new SessionFile(originalBackup);
559       
560       rchive.updateFrom(null, bckup); // recover from backup file.
561       bckup.unlockFile();
562       bckup=null;
563       removeBackup();
564     }
565   }
566   
567   /**
568    * forget about any backup that was made - removing it first if it was only temporary.
569    */
570   private void removeBackup() {
571     if (originalBackup!=null) {
572       log.debug("Removing backup in "+originalBackup.getAbsolutePath());
573       if (!donotdeletebackup)
574         if (!originalBackup.delete())
575           log.info("VamsasArchive couldn't remove temporary backup "+originalBackup.getAbsolutePath());
576       originalBackup=null;
577     }
578   } 
579   /**
580    * @param vorba the VorbaIdFactory to use for accessing vamsas objects.
581    */
582   public void setVorba(VorbaIdFactory Vorba) {
583     if (Vorba!=null) {
584       if (vorba==null)
585         vorba = new SimpleDocument(Vorba);
586       else
587         vorba.setVorba(Vorba);
588     } else
589       getVorba();
590   }
591   /**
592    * Convenience method to copy over the referred entry from the backup to the new version.
593    * Warning messages are raised if no backup exists or the 
594    * entry doesn't exist in the backed-up original.
595    * Duplicate writes return true - but a warning message will also be raised.
596    * @param AppDataReference
597    * @return true if AppDataReference now exists in the new document
598    * @throws IOException
599    */
600   public boolean transferAppDataEntry(String AppDataReference) throws IOException {
601     return transferAppDataEntry(AppDataReference, AppDataReference);
602   }
603   /**
604    * Validates the AppDataReference: not null and not already written to archive.
605    * @param AppDataReference
606    * @return true if valid. false if not
607    * @throws IOException for really broken references!
608    */
609   protected boolean _validNewAppDataReference(String newAppDataReference) throws IOException {
610     // LATER: Specify valid AppDataReference form in all VamsasArchive handlers
611     if (newAppDataReference==null)
612       throw new IOException("null newAppDataReference!");
613     if (entries.containsKey(newAppDataReference)) {
614       log.warn("Attempt to write '"+newAppDataReference+"' twice! - IGNORED");
615       // LATER: fix me? warning message should raise an exception here.
616       return false;
617     }
618     return true;
619   }
620   /**
621    * Transfers an AppDataReference from old to new vamsas archive, with a name change.
622    * @see transferAppDataEntry(String AppDataReference)
623    * @param AppDataReference
624    * @param NewAppDataReference - AppDataReference in new Archive
625    * @return
626    * @throws IOException
627    */
628   public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
629     if (original==null || !original.exists()) {
630       log.warn("No backup archive exists.");
631       return false;
632     }
633     if (AppDataReference==null)
634       throw new IOException("null AppDataReference!");
635
636     if (!_validNewAppDataReference(NewAppDataReference))
637       return false;
638     
639     accessOriginal();
640     
641     java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
642     
643     if (adstream==null) {
644       log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
645       return false;
646     }
647     
648     java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
649     // copy over the bytes
650     int written=-1;
651     long count=0;
652     byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
653     do {
654       if ((written = adstream.read(buffer))>-1) {
655         adout.write(buffer, 0, written);
656         log.debug("Transferring "+written+".");
657         count+=written;
658       }
659     } while (written>-1);
660     log.debug("Sucessfully transferred AppData for '"
661         +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
662     return true;
663   }
664   /**
665    * write data from a stream into an appData reference.
666    * @param AppDataReference - New AppDataReference not already written to archive
667    * @param adstream Source of data for appData reference - read until .read(buffer) returns -1
668    * @return true on success.
669    * @throws IOException for file IO or invalid AppDataReference string
670    */
671   public boolean writeAppdataFromStream(String AppDataReference, java.io.InputStream adstream) throws IOException {
672     if (!_validNewAppDataReference(AppDataReference)) {
673       log.warn("Invalid AppDataReference passed to writeAppdataFromStream");
674       throw new IOException("Invalid AppDataReference! (null, or maybe non-unique)!");
675     }
676       
677     if (AppDataReference==null) {
678       log.warn("null appdata passed.");
679       throw new IOException("Null AppDataReference");
680     }
681     
682     java.io.OutputStream adout = getAppDataStream(AppDataReference);
683     // copy over the bytes
684     int written=-1;
685     long count=0;
686     byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
687     do {
688       if ((written = adstream.read(buffer))>-1) {
689         adout.write(buffer, 0, written);
690         log.debug("Transferring "+written+".");
691         count+=written;
692       }
693     } while (written>-1);
694     return true;
695   }
696   /**
697    * transfers any AppDataReferences existing in the old document 
698    * that haven't already been transferred to the new one
699    * LATER: do the same for transfers requiring a namechange - more document dependent.
700    *  @return true if data was transferred.
701    */
702   public boolean transferRemainingAppDatas() throws IOException {
703     boolean transfered=false;
704     if (original==null || !original.exists()) {
705       log.warn("No backup archive exists.");
706       return false;
707     }
708     accessOriginal();
709     
710     if (getVorba()!=null) {
711       Vector originalRefs=null;
712       try {
713         originalRefs = vorba.getReferencedEntries(getVamsasDocument(), getOriginalArchiveReader());
714       } catch (Exception e) {
715         log.warn("Problems accessing original document entries!",e);
716       }
717       if (originalRefs!=null) {
718         Iterator ref = originalRefs.iterator();
719         while (ref.hasNext()) {
720           String oldentry = (String) ref.next();
721           if (oldentry!=null && !entries.containsKey(oldentry)) {
722             log.debug("Transferring remaining entry '"+oldentry+"'");
723             transfered |= transferAppDataEntry(oldentry);
724           }
725         }
726       }
727     } 
728     return transfered;
729   }
730   /**
731    * called after archive is written to put file in its final place
732    */
733   private void updateOriginal() {
734     if (!virginArchive) {
735       // make sure original document really is backed up and then overwrite it.
736       if (odoc!=null) {
737         // try to shut the odoc reader.
738         odoc.close();
739         odoc = null;
740       }
741       // Make a backup if it isn't done already
742       makeBackup();
743       try {
744         // copy new Archive data that was writen to a temporary file
745         odoclock.updateFrom(null, rchive);
746       }
747       catch (IOException e) {
748         // LATER: decide if leaving nastily named backup files around is necessary. 
749         log.error("Problem updating archive from temporary file! - backup left in '"
750             +backupFile().getAbsolutePath()+"'",e);
751       }
752       // Tidy up if necessary.
753       removeBackup();
754     } else {
755       
756     }
757   }
758 }