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