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