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 SessionFile rchive=null;
30 java.io.File original=null;
31 SessionFile odoclock = null;
32 VamsasArchiveReader odoc = null;
33 boolean vamsasdocument=true; // make a document archive (rather than a vamsas.xml archive)
34 JarOutputStream newarchive=null;
35 Hashtable entries = null;
38 * Create a new vamsas archive
39 * @param archive - file spec for new vamsas archive
40 * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
42 public VamsasArchive(File archive, boolean vamsasdocument) {
44 if (archive==null || (archive!=null && archive.canWrite())) {
45 log.fatal("Invalid parameters for VamsasArchive constructor:"+((archive!=null)
46 ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
48 this.vamsasdocument = vamsasdocument;
49 if (archive.exists()) {
50 this.original = archive;
52 // make the archive temp file when the open method is called
55 this.archive = archive; // write directly to the new archive
59 * called by app to determine if a backup will be made or not.
62 public File backupFile() {
66 return ((original==null) ? originalBackup : null);
72 protected String getDocumentJarEntry() {
74 return VamsasArchiveReader.VAMSASDOC;
75 return VamsasArchiveReader.VAMSASXML;
77 protected boolean isDocumentWritten() {
79 log.warn("isDocumentWritten called for unopened archive.");
81 if (entries.containsKey(getDocumentJarEntry()))
87 * Add unique entry strings to internal JarEntries list.
89 * @return true if entry was unique and was added.
91 private boolean addEntry(String entry) {
93 entries=new Hashtable();
94 if (entries.containsKey(entry))
96 entries.put(entry, new Integer(entries.size()));
100 * adds named entry to newarchive or returns false.
102 * @return true if entry was unique and could be added
103 * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
105 private boolean addValidEntry(String entry) throws IOException {
106 JarEntry je = new JarEntry(entry);
107 if (!addEntry(entry))
109 newarchive.putNextEntry(je);
113 File originalBackup = null;
115 private void makeBackup() {
116 if (originalBackup!=null && original!=null && original.exists()) {
119 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
120 // rchive.fileLock.rafile.getChannel().truncate(0);
122 catch (IOException e) {
123 log.warn("Problem whilst making a backup of original archive.",e);
127 File tempoutput = null;
128 SessionFile trchive = null;
129 private void openArchive() throws IOException {
131 if (newarchive!=null)
132 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
135 if (original==null) {
136 throw new IOException("Badly initialised VamsasArchive object - null archive file.");
138 // make a temporary file to write to
142 // tempfile is real archive
145 /* if (archive.exists())
146 if (original==null) {
147 original = rchive.backupSessionFile(null, archive.getName(), ".bak", archive.getParentFile());
148 rchive.fileLock.rafile.getChannel().truncate(0);
150 throw new IOException("Backup of current VamsasArchive already exists. Fix this BUG");
154 newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
155 entries = new Hashtable();
159 if (archive.exists())
160 if (original==null) {
161 original = rchive.backupSessionFile(null, archive.getName(), ".bak", archive.getParentFile());
162 rchive.fileLock.rafile.getChannel().truncate(0);
165 * Safely initializes the VAMSAS XML document Jar Entry.
166 * @return Writer to pass to the marshalling function.
167 * @throws IOException if a document entry has already been written.
169 public PrintWriter getDocumentOutputStream() throws IOException {
170 if (newarchive==null)
172 if (!isDocumentWritten()) {
174 if (addValidEntry(getDocumentJarEntry()))
175 return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
176 } catch (Exception e) {
177 log.warn("Problems opening XML document JarEntry stream",e);
180 throw new IOException("Vamsas Document output stream is already written.");
185 * Opens and returns the applicationData output stream for the appdataReference string.
186 * TODO: Make a wrapper class to catch calls to OutputStream.close() which normally close the Jar output stream.
187 * @param appdataReference
188 * @return Output stream to write to
189 * @throws IOException
191 public OutputStream getAppDataStream(String appdataReference) throws IOException {
192 if (newarchive!=null)
194 if (addValidEntry(appdataReference)) {
195 return new DataOutputStream(newarchive);
201 * Stops any current write to archive, and reverts to the backup if it exists.
204 public boolean cancelArchive() {
205 if (newarchive!=null) {
208 } catch (Exception e) {};
209 if (original!=null) {
212 // recover from backup file.
213 rchive.fileLock.rafile.getChannel().truncate(0);
214 SessionFile bck = new SessionFile(original);
215 if (bck.lockFile()) {
216 rchive.fileLock.rafile.getChannel().transferFrom(
217 bck.fileLock.rafile.getChannel(),
218 0, bck.fileLock.rafile.getChannel().size());
223 log.warn("Could not get lock on backup file to recover");
226 catch (Exception e) {
227 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
231 log.fatal("Attempt to backup from Archive Backup without valid write lock! ('"+archive.getAbsolutePath()+"') - BUG!");
238 * only do this if you want to destroy the current file output stream
241 private void closeAndReset() {
244 if (original!=null) {
250 if (odoclock!=null) {
251 odoclock.unlockFile();
260 private final int _TRANSFER_BUFFER=4096*4;
262 * open backup for exclusive (locked) reading.
263 * @throws IOException
265 private void accessBackup() throws IOException {
266 if (original!=null && original.exists()) {
268 odoclock = new SessionFile(original);
271 odoc = new VamsasArchiveReader(original);
275 * Convenience method to copy over the referred entry from the backup to the new version.
276 * Warning messages are raised if no backup exists or the
277 * entry doesn't exist in the backed-up original.
278 * Duplicate writes return true - but a warning message will also be raised.
279 * @param AppDataReference
280 * @return true if AppDataReference now exists in the new document
281 * @throws IOException
283 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
284 // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
285 if (AppDataReference==null)
286 throw new IOException("Invalid AppData Reference!");
287 if (original==null || !original.exists()) {
288 log.warn("No backup archive exists.");
291 if (entries.containsKey(AppDataReference)) {
292 log.warn("Attempt to write '"+AppDataReference+"' twice! - IGNORED");
298 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
300 if (adstream==null) {
301 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
305 java.io.OutputStream adout = getAppDataStream(AppDataReference);
306 // copy over the bytes
309 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
311 if ((written = adstream.read(buffer))>-1) {
312 adout.write(buffer, 0, written);
313 log.debug("Transferring "+written+".");
316 } while (written>-1);
317 log.debug("Sucessfully transferred AppData for "+AppDataReference+" ("+count+" bytes)");
322 * Tidies up and closes archive, removing any backups that were created.
323 * NOTE: It is up to the caller to
325 public void closeArchive() throws IOException {
326 if (newarchive!=null) {
327 newarchive.closeEntry();
328 if (!isDocumentWritten())
329 log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"'");
333 log.warn("Attempt to close archive that has not been opened for writing.");