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