1 package org.vamsas.client.simpleclient;
3 import java.io.BufferedOutputStream;
4 import java.io.DataOutputStream;
6 import java.io.IOException;
7 import java.io.OutputStream;
8 import java.io.PrintWriter;
9 import java.util.Hashtable;
11 import java.util.jar.JarEntry;
12 import java.util.jar.JarInputStream;
13 import java.util.jar.JarOutputStream;
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
19 * Class for creating a vamsas archive
21 * Writes to a temporary file and then swaps new file for backup.
22 * uses the sessionFile locking mechanism for safe I/O
26 public class VamsasArchive {
27 private static Log log = LogFactory.getLog(VamsasArchive.class);
29 * destination of new archive data
31 java.io.File archive=null;
33 * locked IO handler for new archive file
35 SessionFile rchive=null;
37 * original archive file that is to be updated
39 java.io.File original=null;
41 * original archive IO handler
43 SessionFile odoclock = null;
45 * Original archive reader class
47 VamsasArchiveReader odoc = null;
49 * true if a real vamsas document is being written.
51 boolean vamsasdocument=true;
53 * Output stream for archived data
55 JarOutputStream newarchive=null;
57 * JarEntries written to archive
59 Hashtable entries = null;
61 * true if we aren't just updating an archive
63 private boolean virginArchive=false;
65 * Create a new vamsas archive
66 * nb. No file locks are made until open() is called.
67 * @param archive - file spec for new vamsas archive
68 * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
70 public VamsasArchive(File archive, boolean vamsasdocument) {
72 if (archive==null || (archive!=null && archive.canWrite())) {
73 log.fatal("Invalid parameters for VamsasArchive constructor:"+((archive!=null)
74 ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
76 this.vamsasdocument = vamsasdocument;
77 if (archive.exists()) {
78 this.original = archive;
79 this.archive = null; // archive will be a temp file when the open method is called
83 this.archive = archive;
88 * name of backup of existing archive that has been updated/overwritten.
90 File originalBackup = null;
92 private void makeBackup() {
94 if (originalBackup==null && original!=null && original.exists()) {
97 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
98 // rchive.fileLock.rafile.getChannel().truncate(0);
100 catch (IOException e) {
101 log.warn("Problem whilst making a backup of original archive.",e);
107 * called after archive is written to put file in its final place
108 * TODO: FINISH original should have sessionFile, and archive should also have sessionFile
110 private void updateOriginal() {
111 if (original!=null) {
112 if (!virginArchive) {
113 if (!archive.getAbsolutePath().equals(original)) {
114 if (originalBackup==null)
118 archive.renameTo(original);
123 * called by app to get name of backup if it was made.
124 * @return null or a valid file object
126 public File backupFile() {
130 return ((original==null) ? originalBackup : null);
136 protected String getDocumentJarEntry() {
138 return VamsasArchiveReader.VAMSASDOC;
139 return VamsasArchiveReader.VAMSASXML;
142 * @return true if Vamsas Document has been written to archive
144 protected boolean isDocumentWritten() {
145 if (newarchive==null)
146 log.warn("isDocumentWritten called for unopened archive.");
148 if (entries.containsKey(getDocumentJarEntry()))
154 * Add unique entry strings to internal JarEntries list.
156 * @return true if entry was unique and was added.
158 private boolean addEntry(String entry) {
160 entries=new Hashtable();
161 if (entries.containsKey(entry))
163 entries.put(entry, new Integer(entries.size()));
167 * adds named entry to newarchive or returns false.
169 * @return true if entry was unique and could be added
170 * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
172 private boolean addValidEntry(String entry) throws IOException {
173 JarEntry je = new JarEntry(entry);
174 if (!addEntry(entry))
176 newarchive.putNextEntry(je);
180 File tempoutput = null;
181 SessionFile trchive = null;
182 private void openArchive() throws IOException {
184 if (newarchive!=null) {
185 log.warn("openArchive() called multiple times.");
186 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
190 if (original==null) {
191 log.warn("openArchive called on uninitialised VamsasArchive object.");
192 throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
196 // make a temporary file to write to
197 archive = File.createTempFile(original.getName(), "new");
201 rchive = new SessionFile(archive);
203 newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
204 entries = new Hashtable();
208 * Safely initializes the VAMSAS XML document Jar Entry.
209 * @return Writer to pass to the marshalling function.
210 * @throws IOException if a document entry has already been written.
212 public PrintWriter getDocumentOutputStream() throws IOException {
213 if (newarchive==null)
215 if (!isDocumentWritten()) {
217 if (addValidEntry(getDocumentJarEntry()))
218 return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
219 } catch (Exception e) {
220 log.warn("Problems opening XML document JarEntry stream",e);
223 throw new IOException("Vamsas Document output stream is already written.");
228 * Opens and returns the applicationData output stream for the appdataReference string.
229 * TODO: Make a wrapper class to catch calls to OutputStream.close() which normally close the Jar output stream.
230 * @param appdataReference
231 * @return Output stream to write to
232 * @throws IOException
234 public OutputStream getAppDataStream(String appdataReference) throws IOException {
235 if (newarchive!=null)
237 if (addValidEntry(appdataReference)) {
238 return new DataOutputStream(newarchive);
244 * Stops any current write to archive, and reverts to the backup if it exists.
245 * All existing locks on the original will be released.
247 public boolean cancelArchive() {
248 if (newarchive!=null) {
251 } catch (Exception e) {};
252 if (!virginArchive) {
253 // then there is something to recover.
254 if (originalBackup!=null) {
255 // backup has been made.
256 // revert from backup and delete it (changing backup filename)
258 rchive = new SessionFile(original);
260 SessionFile bckup = new SessionFile(originalBackup);
263 rchive.updateFrom(null,bckup); // recover from backup file.
266 originalBackup.delete();
268 catch (Exception e) {
269 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
273 // original is untouched
274 // just delete temp files
278 log.info("cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
285 * only do this if you want to destroy the current file output stream
288 private void closeAndReset() {
291 if (original!=null) {
297 if (odoclock!=null) {
298 odoclock.unlockFile();
307 private final int _TRANSFER_BUFFER=4096*4;
309 * open backup for exclusive (locked) reading.
310 * @throws IOException
312 private void accessBackup() throws IOException {
313 if (original!=null && original.exists()) {
315 odoclock = new SessionFile(original);
318 odoc = new VamsasArchiveReader(original);
323 * Convenience method to copy over the referred entry from the backup to the new version.
324 * Warning messages are raised if no backup exists or the
325 * entry doesn't exist in the backed-up original.
326 * Duplicate writes return true - but a warning message will also be raised.
327 * @param AppDataReference
328 * @return true if AppDataReference now exists in the new document
329 * @throws IOException
331 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
332 // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
333 if (AppDataReference==null)
334 throw new IOException("Invalid AppData Reference!");
335 if (original==null || !original.exists()) {
336 log.warn("No backup archive exists.");
339 if (entries.containsKey(AppDataReference)) {
340 log.warn("Attempt to write '"+AppDataReference+"' twice! - IGNORED");
346 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
348 if (adstream==null) {
349 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
353 java.io.OutputStream adout = getAppDataStream(AppDataReference);
354 // copy over the bytes
357 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
359 if ((written = adstream.read(buffer))>-1) {
360 adout.write(buffer, 0, written);
361 log.debug("Transferring "+written+".");
364 } while (written>-1);
365 log.debug("Sucessfully transferred AppData for "+AppDataReference+" ("+count+" bytes)");
370 * Tidies up and closes archive, removing any backups that were created.
371 * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
373 public void closeArchive() throws IOException {
374 if (newarchive!=null) {
375 newarchive.closeEntry();
376 if (!isDocumentWritten())
377 log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
381 log.warn("Attempt to close archive that has not been opened for writing.");