1 package org.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.PrintWriter;
11 import java.util.Hashtable;
12 import java.util.jar.JarEntry;
13 import java.util.jar.JarOutputStream;
15 import org.apache.commons.logging.Log;
16 import org.apache.commons.logging.LogFactory;
17 import org.vamsas.client.object;
18 import org.vamsas.objects.core.ApplicationData;
19 import org.vamsas.objects.core.VAMSAS;
20 import org.vamsas.objects.core.VamsasDocument;
23 * Class for creating a vamsas archive
25 * Writes to a temporary file and then swaps new file for backup.
26 * uses the sessionFile locking mechanism for safe I/O
30 public class VamsasArchive {
31 private static Log log = LogFactory.getLog(VamsasArchive.class);
33 * destination of new archive data
35 java.io.File archive=null;
37 * locked IO handler for new archive file
39 SessionFile rchive=null;
41 * original archive file that is to be updated
43 java.io.File original=null;
45 * original archive IO handler
47 SessionFile odoclock = null;
49 * Original archive reader class
51 VamsasArchiveReader odoc = null;
53 * true if a real vamsas document is being written.
55 boolean vamsasdocument=true;
57 * Output stream for archived data
59 JarOutputStream newarchive=null;
61 * JarEntries written to archive
63 Hashtable entries = null;
65 * true if we aren't just updating an archive
67 private boolean virginArchive=false;
69 * Create a new vamsas archive
70 * nb. No file locks are made until open() is called.
71 * @param archive - file spec for new vamsas archive
72 * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
74 public VamsasArchive(File archive, boolean vamsasdocument) {
76 if (archive==null || (archive!=null && archive.canWrite())) {
77 log.fatal("Invalid parameters for VamsasArchive constructor:"+((archive!=null)
78 ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
80 this.vamsasdocument = vamsasdocument;
81 if (archive.exists()) {
82 this.original = archive;
83 this.archive = null; // archive will be a temp file when the open method is called
87 this.archive = archive; // archive is written in place.
92 * name of backup of existing archive that has been updated/overwritten.
94 File originalBackup = null;
96 private void makeBackup() {
98 if (originalBackup==null && original!=null && original.exists()) {
101 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
103 catch (IOException e) {
104 log.warn("Problem whilst making a backup of original archive.",e);
110 * called after archive is written to put file in its final place
111 * TODO: FINISH original should have sessionFile, and archive should also have sessionFile
113 private void updateOriginal() {
114 if (original!=null) {
115 if (!virginArchive) {
120 if (!archive.getAbsolutePath().equals(original)) {
121 if (originalBackup==null)
124 odoclock.updateFrom(null, rchive);
126 catch (IOException e) {
127 log.error("Problem updating archive from temporary file!",e);
130 log.warn("archive and original are the same file! ("+archive.getAbsolutePath()+")");
132 } // else virginArchive are put in correct place from the beginning
137 * called by app to get name of backup if it was made.
138 * @return null or a valid file object
140 public File backupFile() {
142 if (!virginArchive) {
144 return ((original==null) ? originalBackup : null);
150 protected String getDocumentJarEntry() {
152 return VamsasArchiveReader.VAMSASDOC;
153 return VamsasArchiveReader.VAMSASXML;
157 * @return true if Vamsas Document has been written to archive
159 protected boolean isDocumentWritten() {
160 if (newarchive==null)
161 log.warn("isDocumentWritten called for unopened archive.");
163 if (entries.containsKey(getDocumentJarEntry()))
169 * Add unique entry strings to internal JarEntries list.
171 * @return true if entry was unique and was added.
173 private boolean addEntry(String entry) {
175 entries=new Hashtable();
176 if (entries.containsKey(entry))
178 entries.put(entry, new Integer(entries.size()));
182 * adds named entry to newarchive or returns false.
184 * @return true if entry was unique and could be added
185 * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
187 private boolean addValidEntry(String entry) throws IOException {
188 JarEntry je = new JarEntry(entry);
189 if (!addEntry(entry))
191 newarchive.putNextEntry(je);
196 * opens the new archive ready for writing. If the new archive is replacing an existing one,
197 * then the existing archive will be locked, and the new archive written to a temporary file.
198 * The new archive will be put in place once close() is called.
199 * @throws IOException
201 private void openArchive() throws IOException {
203 if (newarchive!=null) {
204 log.warn("openArchive() called multiple times.");
205 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
209 if (original==null) {
210 log.warn("openArchive called on uninitialised VamsasArchive object.");
211 throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
215 // make a temporary file to write to
216 archive = File.createTempFile(original.getName(), "new",original.getParentFile());
219 rchive = new SessionFile(archive);
221 newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
222 entries = new Hashtable();
225 * Safely initializes the VAMSAS XML document Jar Entry.
226 * @return Writer to pass to the marshalling function.
227 * @throws IOException if a document entry has already been written.
229 public PrintWriter getDocumentOutputStream() throws IOException {
230 if (newarchive==null)
232 if (!isDocumentWritten()) {
234 if (addValidEntry(getDocumentJarEntry()))
235 return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
236 } catch (Exception e) {
237 log.warn("Problems opening XML document JarEntry stream",e);
240 throw new IOException("Vamsas Document output stream is already written.");
245 * Opens and returns the applicationData output stream for the appdataReference string.
246 * @param appdataReference
247 * @return Output stream to write to
248 * @throws IOException
250 public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
251 if (newarchive!=null)
253 if (addValidEntry(appdataReference)) {
254 return new AppDataOutputStream(newarchive);
260 * Stops any current write to archive, and reverts to the backup if it exists.
261 * All existing locks on the original will be released.
263 public boolean cancelArchive() {
264 if (newarchive!=null) {
267 } catch (Exception e) {};
268 if (!virginArchive) {
269 // then there is something to recover.
270 if (originalBackup!=null) {
271 // backup has been made.
272 // revert from backup and delete it (changing backup filename)
274 rchive = new SessionFile(original);
276 SessionFile bckup = new SessionFile(originalBackup);
279 rchive.updateFrom(null,bckup); // recover from backup file.
282 originalBackup.delete();
284 catch (Exception e) {
285 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
289 // original is untouched
290 // just delete temp files
294 log.info("cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
301 * only do this if you want to destroy the current file output stream
304 private void closeAndReset() {
307 if (original!=null) {
313 if (odoclock!=null) {
314 odoclock.unlockFile();
323 private final int _TRANSFER_BUFFER=4096*4;
325 * open backup for exclusive (locked) reading.
326 * @throws IOException
328 private void accessOriginal() throws IOException {
329 if (original!=null && original.exists()) {
331 odoclock = new SessionFile(original);
334 odoc = new VamsasArchiveReader(original);
339 * Convenience method to copy over the referred entry from the backup to the new version.
340 * Warning messages are raised if no backup exists or the
341 * entry doesn't exist in the backed-up original.
342 * Duplicate writes return true - but a warning message will also be raised.
343 * @param AppDataReference
344 * @return true if AppDataReference now exists in the new document
345 * @throws IOException
347 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
348 return transferAppDataEntry(AppDataReference, AppDataReference);
351 * Transfers an AppDataReference from old to new vamsas archive, with a name change.
352 * @see transferAppDataEntry(String AppDataReference)
353 * @param AppDataReference
354 * @param NewAppDataReference - AppDataReference in new Archive
356 * @throws IOException
358 public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
359 // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
360 if (AppDataReference==null)
361 throw new IOException("null AppDataReference!");
362 if (original==null || !original.exists()) {
363 log.warn("No backup archive exists.");
366 if (entries.containsKey(NewAppDataReference)) {
367 log.warn("Attempt to write '"+NewAppDataReference+"' twice! - IGNORED");
373 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
375 if (adstream==null) {
376 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
380 java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
381 // copy over the bytes
384 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
386 if ((written = adstream.read(buffer))>-1) {
387 adout.write(buffer, 0, written);
388 log.debug("Transferring "+written+".");
391 } while (written>-1);
392 log.debug("Sucessfully transferred AppData for '"
393 +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
397 * Tidies up and closes archive, removing any backups that were created.
398 * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
400 public void closeArchive() throws IOException {
401 if (newarchive!=null) {
402 newarchive.closeEntry();
403 if (!isDocumentWritten())
404 log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
409 log.warn("Attempt to close archive that has not been opened for writing.");
413 * Access original archive if it exists, pass the reader to the client
414 * Note: this is NOT thread safe and a call to closeArchive() will by necessity
415 * close and invalidate the VamsasArchiveReader object.
416 * @return null if no original archive exists.
418 public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
419 if (!virginArchive) {
426 * returns original document's root vamsas elements.
428 * @throws IOException
429 * @throws org.exolab.castor.xml.MarshalException
430 * @throws org.exolab.castor.xml.ValidationException
432 public object[] getOriginalRoots() throws IOException,
433 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
434 return VamsasArchive.getOriginalRoots(this);
437 * Access original document if it exists, and get VAMSAS root objects.
438 * @return vector of vamsas roots from original document
439 * @throws IOException
441 public static object[] getOriginalRoots(VamsasArchive ths) throws IOException,
442 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
443 VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
446 if (oReader.isValid()) {
447 InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
448 VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
450 return doc.getVAMSAS();
451 // TODO ensure embedded appDatas are garbage collected
453 InputStream vxmlis = oReader.getVamsasXmlStream();
454 if (vxmlis!=null) { // Might be an old vamsas file.
455 BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
456 InputStreamReader vxml = new InputStreamReader(ixml);
457 VAMSAS root[] = new VAMSAS[1];
458 root[0] = VAMSAS.unmarshal(vxml);