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