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;
10 import java.util.jar.JarEntry;
11 import java.util.jar.JarOutputStream;
13 import org.apache.commons.logging.Log;
14 import org.apache.commons.logging.LogFactory;
17 * Class for creating a vamsas archive
19 * Writes to a temporary file and then swaps new file for backup.
20 * uses the sessionFile locking mechanism for safe I/O
24 public class VamsasArchive {
25 private static Log log = LogFactory.getLog(VamsasArchive.class);
27 * destination of new archive data
29 java.io.File archive=null;
31 * locked IO handler for new archive file
33 SessionFile rchive=null;
35 * original archive file that is to be updated
37 java.io.File original=null;
39 * original archive IO handler
41 SessionFile odoclock = null;
43 * Original archive reader class
45 VamsasArchiveReader odoc = null;
47 * true if a real vamsas document is being written.
49 boolean vamsasdocument=true;
51 * Output stream for archived data
53 JarOutputStream newarchive=null;
55 * JarEntries written to archive
57 Hashtable entries = null;
59 * true if we aren't just updating an archive
61 private boolean virginArchive=false;
63 * Create a new vamsas archive
64 * nb. No file locks are made until open() is called.
65 * @param archive - file spec for new vamsas archive
66 * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
68 public VamsasArchive(File archive, boolean vamsasdocument) {
70 if (archive==null || (archive!=null && archive.canWrite())) {
71 log.fatal("Invalid parameters for VamsasArchive constructor:"+((archive!=null)
72 ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
74 this.vamsasdocument = vamsasdocument;
75 if (archive.exists()) {
76 this.original = archive;
77 this.archive = null; // archive will be a temp file when the open method is called
81 this.archive = archive;
86 * name of backup of existing archive that has been updated/overwritten.
88 File originalBackup = null;
90 private void makeBackup() {
92 if (originalBackup==null && original!=null && original.exists()) {
95 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
96 // rchive.fileLock.rafile.getChannel().truncate(0);
98 catch (IOException e) {
99 log.warn("Problem whilst making a backup of original archive.",e);
105 * called after archive is written to put file in its final place
106 * TODO: FINISH original should have sessionFile, and archive should also have sessionFile
108 private void updateOriginal() {
109 if (original!=null) {
110 if (!virginArchive) {
111 if (!archive.getAbsolutePath().equals(original)) {
112 if (originalBackup==null)
115 odoclock.updateFrom(null, rchive);
117 catch (IOException e) {
118 log.error("Problem updating archive from temporary file!",e);
122 archive.renameTo(original);
127 * called by app to get name of backup if it was made.
128 * @return null or a valid file object
130 public File backupFile() {
134 return ((original==null) ? originalBackup : null);
140 protected String getDocumentJarEntry() {
142 return VamsasArchiveReader.VAMSASDOC;
143 return VamsasArchiveReader.VAMSASXML;
146 * @return true if Vamsas Document has been written to archive
148 protected boolean isDocumentWritten() {
149 if (newarchive==null)
150 log.warn("isDocumentWritten called for unopened archive.");
152 if (entries.containsKey(getDocumentJarEntry()))
158 * Add unique entry strings to internal JarEntries list.
160 * @return true if entry was unique and was added.
162 private boolean addEntry(String entry) {
164 entries=new Hashtable();
165 if (entries.containsKey(entry))
167 entries.put(entry, new Integer(entries.size()));
171 * adds named entry to newarchive or returns false.
173 * @return true if entry was unique and could be added
174 * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
176 private boolean addValidEntry(String entry) throws IOException {
177 JarEntry je = new JarEntry(entry);
178 if (!addEntry(entry))
180 newarchive.putNextEntry(je);
184 File tempoutput = null;
185 SessionFile trchive = null;
186 private void openArchive() throws IOException {
188 if (newarchive!=null) {
189 log.warn("openArchive() called multiple times.");
190 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
194 if (original==null) {
195 log.warn("openArchive called on uninitialised VamsasArchive object.");
196 throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
200 // make a temporary file to write to
201 archive = File.createTempFile(original.getName(), "new");
205 rchive = new SessionFile(archive);
207 newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
208 entries = new Hashtable();
212 * Safely initializes the VAMSAS XML document Jar Entry.
213 * @return Writer to pass to the marshalling function.
214 * @throws IOException if a document entry has already been written.
216 public PrintWriter getDocumentOutputStream() throws IOException {
217 if (newarchive==null)
219 if (!isDocumentWritten()) {
221 if (addValidEntry(getDocumentJarEntry()))
222 return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
223 } catch (Exception e) {
224 log.warn("Problems opening XML document JarEntry stream",e);
227 throw new IOException("Vamsas Document output stream is already written.");
232 * Opens and returns the applicationData output stream for the appdataReference string.
233 * TODO: Make a wrapper class to catch calls to OutputStream.close() which normally close the Jar output stream.
234 * @param appdataReference
235 * @return Output stream to write to
236 * @throws IOException
238 public OutputStream getAppDataStream(String appdataReference) throws IOException {
239 if (newarchive!=null)
241 if (addValidEntry(appdataReference)) {
242 return new DataOutputStream(newarchive);
248 * Stops any current write to archive, and reverts to the backup if it exists.
249 * All existing locks on the original will be released.
251 public boolean cancelArchive() {
252 if (newarchive!=null) {
255 } catch (Exception e) {};
256 if (!virginArchive) {
257 // then there is something to recover.
258 if (originalBackup!=null) {
259 // backup has been made.
260 // revert from backup and delete it (changing backup filename)
262 rchive = new SessionFile(original);
264 SessionFile bckup = new SessionFile(originalBackup);
267 rchive.updateFrom(null,bckup); // recover from backup file.
270 originalBackup.delete();
272 catch (Exception e) {
273 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
277 // original is untouched
278 // just delete temp files
282 log.info("cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
289 * only do this if you want to destroy the current file output stream
292 private void closeAndReset() {
295 if (original!=null) {
301 if (odoclock!=null) {
302 odoclock.unlockFile();
311 private final int _TRANSFER_BUFFER=4096*4;
313 * open backup for exclusive (locked) reading.
314 * @throws IOException
316 private void accessBackup() throws IOException {
317 if (original!=null && original.exists()) {
319 odoclock = new SessionFile(original);
322 odoc = new VamsasArchiveReader(original);
327 * Convenience method to copy over the referred entry from the backup to the new version.
328 * Warning messages are raised if no backup exists or the
329 * entry doesn't exist in the backed-up original.
330 * Duplicate writes return true - but a warning message will also be raised.
331 * @param AppDataReference
332 * @return true if AppDataReference now exists in the new document
333 * @throws IOException
335 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
336 // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
337 if (AppDataReference==null)
338 throw new IOException("Invalid AppData Reference!");
339 if (original==null || !original.exists()) {
340 log.warn("No backup archive exists.");
343 if (entries.containsKey(AppDataReference)) {
344 log.warn("Attempt to write '"+AppDataReference+"' twice! - IGNORED");
350 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
352 if (adstream==null) {
353 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
357 java.io.OutputStream adout = getAppDataStream(AppDataReference);
358 // copy over the bytes
361 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
363 if ((written = adstream.read(buffer))>-1) {
364 adout.write(buffer, 0, written);
365 log.debug("Transferring "+written+".");
368 } while (written>-1);
369 log.debug("Sucessfully transferred AppData for "+AppDataReference+" ("+count+" bytes)");
374 * Tidies up and closes archive, removing any backups that were created.
375 * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
377 public void closeArchive() throws IOException {
378 if (newarchive!=null) {
379 newarchive.closeEntry();
380 if (!isDocumentWritten())
381 log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
386 log.warn("Attempt to close archive that has not been opened for writing.");