/* * This file is part of the Vamsas Client version 0.1. * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, * Andrew Waterhouse and Dominik Lindner. * * Earlier versions have also been incorporated into Jalview version 2.4 * since 2008, and TOPALi version 2 since 2007. * * The Vamsas Client is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Vamsas Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the Vamsas Client. If not, see . */ package uk.ac.vamsas.client.simpleclient; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import uk.ac.vamsas.client.AppDataOutputStream; import uk.ac.vamsas.client.ClientHandle; import uk.ac.vamsas.client.IVorbaIdFactory; import uk.ac.vamsas.client.SessionHandle; import uk.ac.vamsas.client.UserHandle; import uk.ac.vamsas.client.Vobject; import uk.ac.vamsas.client.VorbaIdFactory; import uk.ac.vamsas.client.VorbaXmlBinder; import uk.ac.vamsas.objects.core.ApplicationData; import uk.ac.vamsas.objects.core.VAMSAS; import uk.ac.vamsas.objects.core.VamsasDocument; import uk.ac.vamsas.objects.utils.AppDataReference; import uk.ac.vamsas.objects.utils.DocumentStuff; import uk.ac.vamsas.objects.utils.ProvenanceStuff; import uk.ac.vamsas.objects.utils.document.VersionEntries; /** * Class for high-level io and Jar manipulation involved in creating or updating * a vamsas archive (with backups). Writes to a temporary file and then swaps * new file for backup. uses the sessionFile locking mechanism for safe I/O * * @author jimp * */ public class VamsasArchive { private static Log log = LogFactory.getLog(VamsasArchive.class); /** * Access original document if it exists, and get VAMSAS root objects. * * @return vector of vamsas roots from original document * @throws IOException */ public static Vobject[] getOriginalRoots(VamsasArchive ths) throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { VamsasArchiveReader oReader = ths.getOriginalArchiveReader(); if (oReader != null) { if (oReader.isValid()) { InputStreamReader vdoc = new InputStreamReader(oReader .getVamsasDocumentStream()); VamsasDocument doc = VamsasDocument.unmarshal(vdoc); if (doc != null) return doc.getVAMSAS(); // TODO ensure embedded appDatas are garbage collected to save memory } else { InputStream vxmlis = oReader.getVamsasXmlStream(); if (vxmlis != null) { // Might be an old vamsas file. BufferedInputStream ixml = new BufferedInputStream(oReader .getVamsasXmlStream()); InputStreamReader vxml = new InputStreamReader(ixml); VAMSAS root[] = new VAMSAS[1]; root[0] = VAMSAS.unmarshal(vxml); if (root[0] != null) return root; } } } return null; } /** * Access the original vamsas document for a VamsasArchive class, and return * it. Users of the VamsasArchive class should use the getVamsasDocument * method to retrieve the current document - only use this one if you want the * 'backup' version. TODO: catch OutOfMemoryError - they are likely to occur * here. NOTE: vamsas.xml datastreams are constructed as 'ALPHA_VERSION' * vamsas documents. * * @param ths * @return null if no document exists. * @throws IOException * @throws org.exolab.castor.xml.MarshalException * @throws org.exolab.castor.xml.ValidationException */ public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths) throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { return VamsasArchive.getOriginalVamsasDocument(ths, null); } /** * Uses VorbaXmlBinder to retrieve the VamsasDocument from the original * archive referred to by ths * * @param ths * @param vorba * @return * @throws IOException * @throws org.exolab.castor.xml.MarshalException * @throws org.exolab.castor.xml.ValidationException */ public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths, VorbaIdFactory vorba) throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { VamsasArchiveReader oReader = ths.getOriginalArchiveReader(); if (oReader != null) { ths.setVorba(vorba); return ths.vorba.getVamsasDocument(oReader); } // otherwise - there was no valid original document to read. return null; } /** * destination of new archive data (tempfile if virginarchive=true, original * archive location otherwise) */ java.io.File archive = null; /** * locked IO handler for new archive file */ SessionFile rchive = null; /** * original archive file to be updated (or null if virgin) where new data will * finally reside */ java.io.File original = null; /** * original archive IO handler */ SessionFile odoclock = null; Lock destinationLock = null; /** * Original archive reader class */ VamsasArchiveReader odoc = null; /** * true if a real vamsas document is being written. */ boolean vamsasdocument = true; /** * Output stream for archived data */ org.apache.tools.zip.ZipOutputStream newarchive = null; /** * JarEntries written to archive */ Hashtable entries = null; /** * true if we aren't just updating an archive */ private boolean virginArchive = false; /** * name of backup of existing archive that has been updated/overwritten. only * one backup will be made - and this is it. */ File originalBackup = null; boolean donotdeletebackup = false; private final int _TRANSFER_BUFFER = 4096 * 4; protected SimpleDocument vorba = null; /** * LATER: ? CUT'n'Paste error ? Access and return current vamsas Document, if * it exists, or create a new one (without affecting VamsasArchive object * state - so is NOT THREAD SAFE) _TODO: possibly modify internal state to * lock low-level files (like the IClientDocument interface instance * constructer would do) * * @see org.vamsas.simpleclient.VamsasArchive.getOriginalVamsasDocument for * additional caveats * * @return * @throws IOException * @throws org.exolab.castor.xml.MarshalException * @throws org.exolab.castor.xml.ValidationException * ????? where does this live JBPNote ? */ private VamsasDocument _doc = null; /** * Create a new vamsas archive File locks are made immediately to avoid * contention * * @param archive * - file spec for new vamsas archive * @param vamsasdocument * true if archive is to be a fully fledged vamsas document archive * @throws IOException * if call to accessOriginal failed for updates, or openArchive * failed. */ public VamsasArchive(File archive, boolean vamsasdocument) throws IOException { this(archive, false, vamsasdocument, null); } public VamsasArchive(File archive, boolean vamsasdocument, boolean overwrite) throws IOException { this(archive, overwrite, vamsasdocument, null); } /** * Constructor for accessing Files under file-lock management (ie a session * file) * * @param archive * @param vamsasdocument * @param overwrite * @throws IOException */ public VamsasArchive(VamsasFile archive, boolean vamsasdocument, boolean overwrite) throws IOException { this(archive.sessionFile, overwrite, vamsasdocument, archive); // log.debug("using non-functional lock-IO stream jar access constructor"); } /** * read and write to archive - will not overwrite original contents, and will * always write an up to date vamsas document structure. * * @param archive * @throws IOException */ public VamsasArchive(VamsasFile archive) throws IOException { this(archive, true, false); } /** * * @param archive * file to write * @param overwrite * true if original contents should be deleted * @param vamsasdocument * true if a proper VamsasDocument archive is to be written. * @param extantLock * SessionFile object holding a lock for the archive * @throws IOException */ public VamsasArchive(File archive, boolean overwrite, boolean vamsasdocument, SessionFile extantLock) throws IOException { super(); if (archive == null || (archive != null && !(archive.getAbsoluteFile().getParentFile() .canWrite() && (!archive.exists() || archive.canWrite())))) { log .fatal("Expect Badness! -- Invalid parameters for VamsasArchive constructor:" + ((archive != null) ? "File cannot be overwritten." : "Null Object not valid constructor parameter")); return; } this.vamsasdocument = vamsasdocument; if (archive.exists() && !overwrite) { this.original = archive; if (extantLock != null) { this.odoclock = extantLock; if (odoclock.fileLock == null || !odoclock.fileLock.isLocked()) odoclock.lockFile(); } else { this.odoclock = new SessionFile(archive); } odoclock.lockFile(); // lock the file *immediatly* this.archive = null; // archive will be a temp file when the open method // is called virginArchive = false; try { this.accessOriginal(); } catch (IOException e) { throw new IOException("Lock failed for existing archive" + archive); } } else { this.original = null; this.archive = archive; // archive is written in place. if (extantLock != null) rchive = extantLock; else rchive = new SessionFile(archive); rchive.lockFile(); if (rchive.fileLock == null || !rchive.fileLock.isLocked()) throw new IOException("Lock failed for new archive" + archive); rchive.fileLock.getRaFile().setLength(0); // empty the archive. virginArchive = true; } this.openArchive(); // open archive } /** * open original archive file for exclusive (locked) reading. * * @throws IOException */ private void accessOriginal() throws IOException { if (original != null && original.exists()) { if (odoclock == null) odoclock = new SessionFile(original); odoclock.lockFile(); if (odoc == null) odoc = new VamsasArchiveReader(original); // this constructor is not implemented yet odoc = new // VamsasArchiveReader(odoclock.fileLock); } } /** * Add unique entry strings to internal JarEntries list. * * @param entry * @return true if entry was unique and was added. */ private boolean addEntry(String entry) { if (entries == null) entries = new Hashtable(); if (log.isDebugEnabled()) { log.debug("validating '" + entry + "' in hash for " + this); } if (entries.containsKey(entry)) return false; entries.put(entry, new Integer(entries.size())); return true; } /** * adds named entry to newarchive or returns false. * * @param entry * @return true if entry was unique and could be added * @throws IOException * if entry name was invalid or a new entry could not be made on * newarchive */ private boolean addValidEntry(String entry) throws IOException { org.apache.tools.zip.ZipEntry je = new org.apache.tools.zip.ZipEntry(entry); // je.setExsetExtra(null); if (!addEntry(entry)) return false; newarchive.flush(); newarchive.putNextEntry(je); return true; } /** * called by app to get name of backup if it was made. If this is called, the * caller app *must* delete the backup themselves. * * @return null or a valid file object */ public File backupFile() { if (!virginArchive) { makeBackup(); donotdeletebackup = true; // external reference has been made. return ((original != null) ? originalBackup : null); } return null; } /** * Stops any current write to archive, and reverts to the backup if it exists. * All existing locks on the original will be released. All backup files are * removed. */ public boolean cancelArchive() { if (newarchive != null) { try { newarchive.closeEntry(); newarchive.putNextEntry(new org.apache.tools.zip.ZipEntry("deleted")); newarchive.closeEntry(); newarchive.close(); } catch (Exception e) { log.debug("Whilst closing newarchive", e); } ; if (!virginArchive) { // then there is something to recover. try { recoverBackup(); } catch (Exception e) { log.warn("Problems when trying to cancel Archive " + archive.getAbsolutePath(), e); return false; } } } else { log.warn("Client Error: cancelArchive called before archive(" + original.getAbsolutePath() + ") has been opened!"); } closeAndReset(); // tidy up and release locks. return true; } /** * only do this if you want to destroy the current file output stream * */ private void closeAndReset() { if (rchive != null) { rchive.unlockFile(); rchive = null; } if (original != null) { if (odoc != null) { odoc.close(); odoc = null; } if (archive != null) archive.delete(); if (odoclock != null) { odoclock.unlockFile(); odoclock = null; } } removeBackup(); newarchive = null; original = null; entries = null; } /** * Tidies up and closes archive, removing any backups that were created. NOTE: * It is up to the caller to delete the original archive backup obtained from * backupFile() TODO: ensure all extant AppDataReference jar entries are * transferred to new Jar TODO: provide convenient mechanism for generating * new unique AppDataReferences and adding them to the document */ public void closeArchive() throws IOException { if (newarchive != null) { newarchive.flush(); newarchive.closeEntry(); if (!isDocumentWritten()) log.warn("Premature closure of archive '" + archive.getAbsolutePath() + "': No document has been written."); newarchive.finish();// close(); // use newarchive.finish(); for a stream // IO newarchive.flush(); // updateOriginal(); closeAndReset(); } else { log .warn("Attempt to close archive that has not been opened for writing."); } } /** * Opens and returns the applicationData output stream for the * appdataReference string. * * @param appdataReference * @return Output stream to write to * @throws IOException */ public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException { if (newarchive == null) throw new IOException("Attempt to write to closed VamsasArchive object."); if (addValidEntry(appdataReference)) { return new AppDataOutputStream(newarchive); } return null; } /** * * @return JarEntry name for the vamsas XML stream in this archive */ protected String getDocumentJarEntry() { if (vamsasdocument) return VamsasArchiveReader.VAMSASDOC; return VamsasArchiveReader.VAMSASXML; } /** * Safely initializes the VAMSAS XML document Jar Entry. * * @return Writer to pass to the marshalling function. * @throws IOException * if a document entry has already been written. */ public PrintWriter getDocumentOutputStream() throws IOException { if (newarchive == null) openArchive(); if (!isDocumentWritten()) { try { if (addValidEntry(getDocumentJarEntry())) return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8")); } catch (Exception e) { log.warn("Problems opening XML document JarEntry stream", e); } } else { throw new IOException("Vamsas Document output stream is already written."); } return null; } /** * Access original archive if it exists, pass the reader to the client Note: * this is NOT thread safe and a call to closeArchive() will by necessity * close and invalidate the VamsasArchiveReader object. * * @return null if no original archive exists. */ public VamsasArchiveReader getOriginalArchiveReader() throws IOException { if (!virginArchive) { accessOriginal(); return odoc; } return null; } /** * returns original document's root vamsas elements. * * @return * @throws IOException * @throws org.exolab.castor.xml.MarshalException * @throws org.exolab.castor.xml.ValidationException */ public Vobject[] getOriginalRoots() throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { return VamsasArchive.getOriginalRoots(this); } /** * @return original document or a new empty document (with default provenance) * @throws IOException * @throws org.exolab.castor.xml.MarshalException * @throws org.exolab.castor.xml.ValidationException */ public VamsasDocument getVamsasDocument() throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { return getVamsasDocument("org.vamsas.simpleclient.VamsasArchive", "Created new empty document", null); } /** * Return the original document or a new empty document with initial * provenance entry. * * @param provenance_user * (null sets user to be the class name) * @param provenance_action * (null sets action to be 'created new document') * @param version * (null means use latest version) * @return (original document or a new vamsas document with supplied * provenance and version info) * @throws IOException * @throws org.exolab.castor.xml.MarshalException * @throws org.exolab.castor.xml.ValidationException */ public VamsasDocument getVamsasDocument(String provenance_user, String provenance_action, String version) throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { if (_doc != null) return _doc; _doc = getOriginalVamsasDocument(this, getVorba()); if (_doc != null) return _doc; // validate parameters if (provenance_user == null) provenance_user = "org.vamsas.simpleclient.VamsasArchive"; if (provenance_action == null) provenance_action = "Created new empty document"; if (version == null) version = VersionEntries.latestVersion(); // Create a new document and return it _doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS() }, ProvenanceStuff.newProvenance(provenance_user, provenance_action), version); return _doc; } /** * @return Returns the current VorbaIdFactory for the archive. */ public VorbaIdFactory getVorba() { if (vorba == null) vorba = new SimpleDocument("simpleclient.VamsasArchive"); return vorba.getVorba(); } /** * @return true if Vamsas Document has been written to archive */ protected boolean isDocumentWritten() { if (newarchive == null) log.warn("isDocumentWritten() called for unopened archive."); if (entries != null) { if (entries.containsKey(getDocumentJarEntry())) return true; } return false; } private void makeBackup() { if (!virginArchive) { if (originalBackup == null && original != null && original.exists()) { try { accessOriginal(); originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile()); } catch (IOException e) { log.warn("Problem whilst making a backup of original archive.", e); } } } } /** * opens the new archive ready for writing. If the new archive is replacing an * existing one, then the existing archive will be locked, and the new archive * written to a temporary file. The new archive will be put in place once * close() is called. * * @param doclock * LATER - pass existing lock on document, if it exists.... no need * yet? * @throws IOException */ private void openArchive() throws IOException { if (newarchive != null) { log.warn("openArchive() called multiple times."); throw new IOException("Vamsas Archive '" + archive.getAbsolutePath() + "' is already open."); } if (archive == null && (virginArchive || original == null)) { log.warn("openArchive called on uninitialised VamsasArchive object."); throw new IOException( "Badly initialised VamsasArchive object - no archive file specified."); } if (!virginArchive) { // lock the original accessOriginal(); // make a temporary file to write to archive = File.createTempFile(original.getName(), ".new", original .getParentFile()); } else { if (archive.exists()) log .warn("New archive file name already in use! Possible lock failure imminent?"); } if (rchive == null) rchive = new SessionFile(archive); if (!rchive.lockFile()) throw new IOException("Failed to get lock on file " + archive); // LATER: locked IO stream based access. // Manifest newmanifest = new Manifest(); newarchive = new org.apache.tools.zip.ZipOutputStream(rchive.fileLock .getBufferedOutputStream(true));// , newmanifest); // newarchive = new JarOutputStream(new BufferedOutputStream(new // java.io.FileOutputStream(archive))); entries = new Hashtable(); } public void putVamsasDocument(VamsasDocument doc) throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { putVamsasDocument(doc, getVorba()); } /** * * @param doc * @param vorba * @return (vorbaId string, Vobjhash) pairs for last hash of each object in * document * @throws IOException * @throws org.exolab.castor.xml.MarshalException * @throws org.exolab.castor.xml.ValidationException */ public void putVamsasDocument(VamsasDocument doc, VorbaIdFactory vorba) throws IOException, org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException { if (vamsasdocument) doc.setVersion(VersionEntries.latestVersion()); // LATER: ensure this does // the correct thing. VorbaXmlBinder.putVamsasDocument(getDocumentOutputStream(), vorba, doc); } /** * recovers the original file's contents from the (temporary) backup. * * @throws Exception * if any SessionFile or file removal operations fail. */ private void recoverBackup() throws Exception { if (originalBackup != null) { // backup has been made. // revert from backup and delete it (changing backup filename) if (rchive == null) { rchive = new SessionFile(original); } SessionFile bckup = new SessionFile(originalBackup); rchive.updateFrom(null, bckup); // recover from backup file. bckup.unlockFile(); bckup = null; removeBackup(); } } /** * forget about any backup that was made - removing it first if it was only * temporary. */ private void removeBackup() { if (originalBackup != null) { log.debug("Removing backup in " + originalBackup.getAbsolutePath()); if (!donotdeletebackup) if (!originalBackup.delete()) log.info("VamsasArchive couldn't remove temporary backup " + originalBackup.getAbsolutePath()); originalBackup = null; } } /** * @param vorba * the VorbaIdFactory to use for accessing vamsas objects. */ public void setVorba(VorbaIdFactory Vorba) { if (Vorba != null) { if (vorba == null) vorba = new SimpleDocument(Vorba); else vorba.setVorba(Vorba); } else getVorba(); } /** * Convenience method to copy over the referred entry from the backup to the * new version. Warning messages are raised if no backup exists or the entry * doesn't exist in the backed-up original. Duplicate writes return true - but * a warning message will also be raised. * * @param AppDataReference * @return true if AppDataReference now exists in the new document * @throws IOException */ public boolean transferAppDataEntry(String AppDataReference) throws IOException { return transferAppDataEntry(AppDataReference, AppDataReference); } /** * Validates the AppDataReference: not null and not already written to * archive. * * @param AppDataReference * @return true if valid. false if not * @throws IOException * for really broken references! */ protected boolean _validNewAppDataReference(String newAppDataReference) throws IOException { // LATER: Specify valid AppDataReference form in all VamsasArchive handlers if (newAppDataReference == null) throw new IOException("null newAppDataReference!"); if (entries.containsKey(newAppDataReference)) { log.warn("Attempt to write '" + newAppDataReference + "' twice! - IGNORED"); // LATER: fix me? warning message should raise an exception here. return false; } return true; } /** * Transfers an AppDataReference from old to new vamsas archive, with a name * change. * * @see transferAppDataEntry(String AppDataReference) * @param AppDataReference * @param NewAppDataReference * - AppDataReference in new Archive * @return * @throws IOException */ public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException { if (original == null || !original.exists()) { log.warn("No backup archive exists."); return false; } if (AppDataReference == null) throw new IOException("null AppDataReference!"); if (!_validNewAppDataReference(NewAppDataReference)) return false; accessOriginal(); java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference); if (adstream == null) { log.warn("AppDataReference '" + AppDataReference + "' doesn't exist in backup archive."); return false; } java.io.OutputStream adout = getAppDataStream(NewAppDataReference); // copy over the bytes int written = -1; long count = 0; byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a // sensible buffer do { if ((written = adstream.read(buffer)) > -1) { adout.write(buffer, 0, written); log.debug("Transferring " + written + "."); count += written; } } while (written > -1); log.debug("Sucessfully transferred AppData for '" + AppDataReference + "' as '" + NewAppDataReference + "' (" + count + " bytes)"); return true; } /** * write data from a stream into an appData reference. * * @param AppDataReference * - New AppDataReference not already written to archive * @param adstream * Source of data for appData reference - read until .read(buffer) * returns -1 * @return true on success. * @throws IOException * for file IO or invalid AppDataReference string */ public boolean writeAppdataFromStream(String AppDataReference, java.io.InputStream adstream) throws IOException { if (!_validNewAppDataReference(AppDataReference)) { log.warn("Invalid AppDataReference passed to writeAppdataFromStream"); throw new IOException( "Invalid AppDataReference! (null, or maybe non-unique)!"); } if (AppDataReference == null) { log.warn("null appdata passed."); throw new IOException("Null AppDataReference"); } java.io.OutputStream adout = getAppDataStream(AppDataReference); // copy over the bytes int written = -1; long count = 0; byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a // sensible buffer do { if ((written = adstream.read(buffer)) > -1) { adout.write(buffer, 0, written); log.debug("Transferring " + written + "."); count += written; } } while (written > -1); return true; } /** * transfers any AppDataReferences existing in the old document that haven't * already been transferred to the new one LATER: do the same for transfers * requiring a namechange - more document dependent. * * @return true if data was transferred. */ public boolean transferRemainingAppDatas() throws IOException { boolean transfered = false; if (original == null || !original.exists()) { log.warn("No backup archive exists."); return false; } accessOriginal(); if (getVorba() != null) { Vector originalRefs = null; try { originalRefs = vorba.getReferencedEntries(getVamsasDocument(), getOriginalArchiveReader()); } catch (Exception e) { log.warn("Problems accessing original document entries!", e); } if (originalRefs != null) { Iterator ref = originalRefs.iterator(); while (ref.hasNext()) { String oldentry = (String) ref.next(); if (oldentry != null && !entries.containsKey(oldentry)) { log.debug("Transferring remaining entry '" + oldentry + "'"); transfered |= transferAppDataEntry(oldentry); } } } } return transfered; } /** * called after archive is written to put file in its final place */ private void updateOriginal() { if (!virginArchive) { // make sure original document really is backed up and then overwrite it. if (odoc != null) { // try to shut the odoc reader. odoc.close(); odoc = null; } // Make a backup if it isn't done already makeBackup(); try { // copy new Archive data that was writen to a temporary file odoclock.updateFrom(null, rchive); } catch (IOException e) { // LATER: decide if leaving nastily named backup files around is // necessary. File backupFile = backupFile(); if (backupFile != null) log.error( "Problem updating archive from temporary file! - backup left in '" + backupFile().getAbsolutePath() + "'", e); else log .error( "Problems updating, and failed to even make a backup file. Ooops!", e); } // Tidy up if necessary. removeBackup(); } else { } } }