1 package uk.ac.vamsas.client.simpleclient;
3 import java.io.BufferedInputStream;
4 import java.io.BufferedOutputStream;
5 import java.io.DataOutputStream;
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;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
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;
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
47 public class VamsasArchive {
48 private static Log log = LogFactory.getLog(VamsasArchive.class);
50 * Access original document if it exists, and get VAMSAS root objects.
51 * @return vector of vamsas roots from original document
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();
59 if (oReader.isValid()) {
60 InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
61 VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
63 return doc.getVAMSAS();
64 // TODO ensure embedded appDatas are garbage collected to save memory
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);
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.
86 * @return null if no document exists.
88 * @throws org.exolab.castor.xml.MarshalException
89 * @throws org.exolab.castor.xml.ValidationException
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);
96 * Uses VorbaXmlBinder to retrieve the VamsasDocument from the original archive referred to by ths
100 * @throws IOException
101 * @throws org.exolab.castor.xml.MarshalException
102 * @throws org.exolab.castor.xml.ValidationException
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();
109 return ths.vorba.getVamsasDocument(oReader);
111 // otherwise - there was no valid original document to read.
115 * destination of new archive data (tempfile if virginarchive=true, original archive location otherwise)
117 java.io.File archive=null;
119 * locked IO handler for new archive file
121 SessionFile rchive=null;
123 * original archive file to be updated (or null if virgin) where new data will finally reside
125 java.io.File original=null;
127 * original archive IO handler
129 SessionFile odoclock = null;
130 Lock destinationLock = null;
132 * Original archive reader class
134 VamsasArchiveReader odoc = null;
136 * true if a real vamsas document is being written.
138 boolean vamsasdocument=true;
140 * Output stream for archived data
142 org.apache.tools.zip.ZipOutputStream newarchive=null;
144 * JarEntries written to archive
146 Hashtable entries = null;
149 * true if we aren't just updating an archive
151 private boolean virginArchive=false;
154 * name of backup of existing archive that has been updated/overwritten.
155 * only one backup will be made - and this is it.
157 File originalBackup = null;
159 boolean donotdeletebackup=false;
160 private final int _TRANSFER_BUFFER=4096*4;
161 protected SimpleDocument vorba = null;
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
171 * @throws IOException
172 * @throws org.exolab.castor.xml.MarshalException
173 * @throws org.exolab.castor.xml.ValidationException
174 * ????? where does this live JBPNote ?
176 private VamsasDocument _doc=null;
179 * Create a new vamsas archive
180 * File locks are made immediately to avoid contention
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.
186 public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {
187 this(archive, false, vamsasdocument, null);
189 public VamsasArchive(File archive, boolean vamsasdocument, boolean overwrite) throws IOException {
190 this(archive, overwrite, vamsasdocument, null);
193 * Constructor for accessing Files under file-lock management (ie a session file)
195 * @param vamsasdocument
197 * @throws IOException
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");
204 * read and write to archive - will not overwrite original contents, and will always write an up to date vamsas document structure.
206 * @throws IOException
208 public VamsasArchive(VamsasFile archive) throws IOException {
209 this(archive, true, false);
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
219 public VamsasArchive(File archive, boolean overwrite, boolean vamsasdocument, SessionFile extantLock) throws IOException {
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"));
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())
235 this.odoclock = new SessionFile(archive);
237 odoclock.lockFile(); // lock the file *immediatly*
238 this.archive = null; // archive will be a temp file when the open method is called
241 this.accessOriginal();
242 } catch (IOException e) {
243 throw new IOException("Lock failed for existing archive"+archive);
246 this.original = null;
247 this.archive = archive; // archive is written in place.
248 if (extantLock!=null)
251 rchive = new SessionFile(archive);
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;
258 this.openArchive(); // open archive
261 * open original archive file for exclusive (locked) reading.
262 * @throws IOException
264 private void accessOriginal() throws IOException {
265 if (original!=null && original.exists()) {
267 odoclock = new SessionFile(original);
270 odoc = new VamsasArchiveReader(original);
271 // this constructor is not implemented yet odoc = new VamsasArchiveReader(odoclock.fileLock);
276 * Add unique entry strings to internal JarEntries list.
278 * @return true if entry was unique and was added.
280 private boolean addEntry(String entry) {
282 entries=new Hashtable();
283 if (log.isDebugEnabled())
285 log.debug("validating '"+entry+"' in hash for "+this);
287 if (entries.containsKey(entry))
289 entries.put(entry, new Integer(entries.size()));
293 * adds named entry to newarchive or returns false.
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
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))
304 newarchive.putNextEntry(je);
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
312 public File backupFile() {
314 if (!virginArchive) {
316 donotdeletebackup=true; // external reference has been made.
317 return ((original!=null) ? originalBackup : null);
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.
326 public boolean cancelArchive() {
327 if (newarchive!=null) {
329 newarchive.closeEntry();
330 newarchive.putNextEntry(new org.apache.tools.zip.ZipEntry("deleted"));
331 newarchive.closeEntry();
334 } catch (Exception e) {
335 log.debug("Whilst closing newarchive",e);
337 if (!virginArchive) {
338 // then there is something to recover.
342 catch (Exception e) {
343 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
349 log.warn("Client Error: cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
351 closeAndReset(); // tidy up and release locks.
356 * only do this if you want to destroy the current file output stream
359 private void closeAndReset() {
364 if (original!=null) {
371 if (odoclock!=null) {
372 odoclock.unlockFile();
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
387 public void closeArchive() throws IOException {
388 if (newarchive!=null) {
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
399 log.warn("Attempt to close archive that has not been opened for writing.");
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
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);
419 * @return JarEntry name for the vamsas XML stream in this archive
421 protected String getDocumentJarEntry() {
423 return VamsasArchiveReader.VAMSASDOC;
424 return VamsasArchiveReader.VAMSASXML;
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.
431 public PrintWriter getDocumentOutputStream() throws IOException {
432 if (newarchive==null)
434 if (!isDocumentWritten()) {
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);
442 throw new IOException("Vamsas Document output stream is already written.");
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.
453 public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
454 if (!virginArchive) {
461 * returns original document's root vamsas elements.
463 * @throws IOException
464 * @throws org.exolab.castor.xml.MarshalException
465 * @throws org.exolab.castor.xml.ValidationException
467 public Vobject[] getOriginalRoots() throws IOException,
468 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
469 return VamsasArchive.getOriginalRoots(this);
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
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);
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
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 {
495 _doc = getOriginalVamsasDocument(this, getVorba());
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";
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);
511 * @return Returns the current VorbaIdFactory for the archive.
513 public VorbaIdFactory getVorba() {
515 vorba = new SimpleDocument("simpleclient.VamsasArchive");
516 return vorba.getVorba();
519 * @return true if Vamsas Document has been written to archive
521 protected boolean isDocumentWritten() {
522 if (newarchive==null)
523 log.warn("isDocumentWritten() called for unopened archive.");
525 if (entries.containsKey(getDocumentJarEntry()))
530 private void makeBackup() {
531 if (!virginArchive) {
532 if (originalBackup==null && original!=null && original.exists()) {
535 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
537 catch (IOException e) {
538 log.warn("Problem whilst making a backup of original archive.",e);
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
550 private void openArchive() throws IOException {
552 if (newarchive!=null) {
553 log.warn("openArchive() called multiple times.");
554 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
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.");
560 if (!virginArchive) {
563 // make a temporary file to write to
564 archive = File.createTempFile(original.getName(), ".new",original.getParentFile());
566 if (archive.exists())
567 log.warn("New archive file name already in use! Possible lock failure imminent?");
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();
580 public void putVamsasDocument(VamsasDocument doc) throws IOException,
581 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
582 putVamsasDocument(doc, getVorba());
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
593 public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba) throws IOException,
594 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
596 doc.setVersion(VersionEntries.latestVersion()); // LATER: ensure this does the correct thing.
597 VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc);
601 * recovers the original file's contents from the (temporary) backup.
602 * @throws Exception if any SessionFile or file removal operations fail.
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)
609 rchive = new SessionFile(original);
611 SessionFile bckup = new SessionFile(originalBackup);
613 rchive.updateFrom(null, bckup); // recover from backup file.
621 * forget about any backup that was made - removing it first if it was only temporary.
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());
633 * @param vorba the VorbaIdFactory to use for accessing vamsas objects.
635 public void setVorba(VorbaIdFactory Vorba) {
638 vorba = new SimpleDocument(Vorba);
640 vorba.setVorba(Vorba);
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
653 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
654 return transferAppDataEntry(AppDataReference, AppDataReference);
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!
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.
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
679 * @throws IOException
681 public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
682 if (original==null || !original.exists()) {
683 log.warn("No backup archive exists.");
686 if (AppDataReference==null)
687 throw new IOException("null AppDataReference!");
689 if (!_validNewAppDataReference(NewAppDataReference))
694 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
696 if (adstream==null) {
697 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
701 java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
702 // copy over the bytes
705 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
707 if ((written = adstream.read(buffer))>-1) {
708 adout.write(buffer, 0, written);
709 log.debug("Transferring "+written+".");
712 } while (written>-1);
713 log.debug("Sucessfully transferred AppData for '"
714 +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
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
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)!");
730 if (AppDataReference==null) {
731 log.warn("null appdata passed.");
732 throw new IOException("Null AppDataReference");
735 java.io.OutputStream adout = getAppDataStream(AppDataReference);
736 // copy over the bytes
739 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
741 if ((written = adstream.read(buffer))>-1) {
742 adout.write(buffer, 0, written);
743 log.debug("Transferring "+written+".");
746 } while (written>-1);
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.
755 public boolean transferRemainingAppDatas() throws IOException {
756 boolean transfered=false;
757 if (original==null || !original.exists()) {
758 log.warn("No backup archive exists.");
763 if (getVorba()!=null) {
764 Vector originalRefs=null;
766 originalRefs = vorba.getReferencedEntries(getVamsasDocument(), getOriginalArchiveReader());
767 } catch (Exception e) {
768 log.warn("Problems accessing original document entries!",e);
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);
784 * called after archive is written to put file in its final place
786 private void updateOriginal() {
787 if (!virginArchive) {
788 // make sure original document really is backed up and then overwrite it.
790 // try to shut the odoc reader.
794 // Make a backup if it isn't done already
797 // copy new Archive data that was writen to a temporary file
798 odoclock.updateFrom(null, rchive);
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);
807 log.error("Problems updating, and failed to even make a backup file. Ooops!", e);
809 // Tidy up if necessary.