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;
21 import org.vamsas.objects.utils.DocumentStuff;
22 import org.vamsas.objects.utils.ProvenanceStuff;
23 import org.vamsas.objects.utils.document.VersionEntries;
26 * Class for high-level io and Jar manipulation involved in creating
27 * or updating a vamsas archive (with backups).
28 * Writes to a temporary file and then swaps new file for backup.
29 * uses the sessionFile locking mechanism for safe I/O
33 public class VamsasArchive {
34 private static Log log = LogFactory.getLog(VamsasArchive.class);
36 * destination of new archive data (tempfile if virginarchive=true, original archive location otherwise)
38 java.io.File archive=null;
40 * locked IO handler for new archive file
42 SessionFile rchive=null;
44 * original archive file to be updated (or null if virgin) where new data will finally reside
46 java.io.File original=null;
48 * original archive IO handler
50 SessionFile odoclock = null;
52 * Original archive reader class
54 VamsasArchiveReader odoc = null;
56 * true if a real vamsas document is being written.
58 boolean vamsasdocument=true;
60 * Output stream for archived data
62 JarOutputStream newarchive=null;
64 * JarEntries written to archive
66 Hashtable entries = null;
68 * true if we aren't just updating an archive
70 private boolean virginArchive=false;
72 * Create a new vamsas archive
73 * File locks are made immediately to avoid contention
75 * @param archive - file spec for new vamsas archive
76 * @param vamsasdocument true if archive is to be a fully fledged vamsas document archive
77 * @throws IOException if call to accessOriginal failed for updates, or openArchive failed.
79 public VamsasArchive(File archive, boolean vamsasdocument) throws IOException {
81 if (archive==null || (archive!=null && !archive.canWrite())) {
82 log.fatal("Invalid parameters for VamsasArchive constructor:"+((archive!=null)
83 ? "File cannot be overwritten." : "Null Object not valid constructor parameter"));
85 this.vamsasdocument = vamsasdocument;
86 if (archive.exists()) {
87 this.original = archive;
88 this.archive = null; // archive will be a temp file when the open method is called
91 this.accessOriginal();
92 } catch (IOException e) {
93 throw new IOException("Lock failed for existing archive"+archive);
97 this.archive = archive; // archive is written in place.
103 * name of backup of existing archive that has been updated/overwritten.
104 * onlu one backup will be made - and this is it.
106 File originalBackup = null;
108 private void makeBackup() {
109 if (!virginArchive) {
110 if (originalBackup==null && original!=null && original.exists()) {
113 originalBackup = odoclock.backupSessionFile(null, original.getName(), ".bak", original.getParentFile());
115 catch (IOException e) {
116 log.warn("Problem whilst making a backup of original archive.",e);
123 * called after archive is written to put file in its final place
124 * TODO: FINISH ?? original should have sessionFile, and archive should also have sessionFile
126 private void updateOriginal() {
127 if (!virginArchive) {
128 // make sure original document really is backed up and then overwrite it.
130 // try to shut the odoc reader.
134 // Make a backup if it isn't done already
135 if (originalBackup==null)
138 // copy new Archive data that was writen to a temporary file
139 odoclock.updateFrom(null, rchive);
141 catch (IOException e) {
142 log.error("Problem updating archive from temporary file! - backup in '"
143 +backupFile().getAbsolutePath()+"'",e);
146 // don't need to do anything.
150 * called by app to get name of backup if it was made.
151 * @return null or a valid file object
153 public File backupFile() {
155 if (!virginArchive) {
157 return ((original!=null) ? originalBackup : null);
163 * @return JarEntry name for the vamsas XML stream in this archive
165 protected String getDocumentJarEntry() {
167 return VamsasArchiveReader.VAMSASDOC;
168 return VamsasArchiveReader.VAMSASXML;
172 * @return true if Vamsas Document has been written to archive
174 protected boolean isDocumentWritten() {
175 if (newarchive==null)
176 log.warn("isDocumentWritten() called for unopened archive.");
178 if (entries.containsKey(getDocumentJarEntry()))
184 * Add unique entry strings to internal JarEntries list.
186 * @return true if entry was unique and was added.
188 private boolean addEntry(String entry) {
190 entries=new Hashtable();
191 if (entries.containsKey(entry))
193 entries.put(entry, new Integer(entries.size()));
197 * adds named entry to newarchive or returns false.
199 * @return true if entry was unique and could be added
200 * @throws IOException if entry name was invalid or a new entry could not be made on newarchive
202 private boolean addValidEntry(String entry) throws IOException {
203 JarEntry je = new JarEntry(entry);
204 if (!addEntry(entry))
206 newarchive.putNextEntry(je);
211 * opens the new archive ready for writing. If the new archive is replacing an existing one,
212 * then the existing archive will be locked, and the new archive written to a temporary file.
213 * The new archive will be put in place once close() is called.
214 * @throws IOException
216 private void openArchive() throws IOException {
218 if (newarchive!=null) {
219 log.warn("openArchive() called multiple times.");
220 throw new IOException("Vamsas Archive '"+archive.getAbsolutePath()+"' is already open.");
222 if (archive==null && (virginArchive || original==null)) {
223 log.warn("openArchive called on uninitialised VamsasArchive object.");
224 throw new IOException("Badly initialised VamsasArchive object - no archive file specified.");
226 if (!virginArchive) {
229 // make a temporary file to write to
230 archive = File.createTempFile(original.getName(), ".new",original.getParentFile());
232 if (archive.exists())
233 log.warn("New archive file name already in use! Possible lock failure imminent?");
236 rchive = new SessionFile(archive);
238 newarchive = new JarOutputStream(new BufferedOutputStream(new java.io.FileOutputStream(archive)));
239 entries = new Hashtable();
242 * Safely initializes the VAMSAS XML document Jar Entry.
243 * @return Writer to pass to the marshalling function.
244 * @throws IOException if a document entry has already been written.
246 public PrintWriter getDocumentOutputStream() throws IOException {
247 if (newarchive==null)
249 if (!isDocumentWritten()) {
251 if (addValidEntry(getDocumentJarEntry()))
252 return new PrintWriter(new java.io.OutputStreamWriter(newarchive, "UTF-8"));
253 } catch (Exception e) {
254 log.warn("Problems opening XML document JarEntry stream",e);
257 throw new IOException("Vamsas Document output stream is already written.");
262 * Opens and returns the applicationData output stream for the appdataReference string.
263 * @param appdataReference
264 * @return Output stream to write to
265 * @throws IOException
267 public AppDataOutputStream getAppDataStream(String appdataReference) throws IOException {
268 if (newarchive==null)
270 if (addValidEntry(appdataReference)) {
271 return new AppDataOutputStream(newarchive);
277 * Stops any current write to archive, and reverts to the backup if it exists.
278 * All existing locks on the original will be released. All backup files are removed.
280 public boolean cancelArchive() {
281 if (newarchive!=null) {
285 } catch (Exception e) {};
286 if (!virginArchive) {
287 // then there is something to recover.
288 if (originalBackup!=null) {
289 // backup has been made.
290 // revert from backup and delete it (changing backup filename)
292 rchive = new SessionFile(original);
294 SessionFile bckup = new SessionFile(originalBackup);
297 rchive.updateFrom(null, bckup); // recover from backup file.
300 originalBackup.delete();
303 catch (Exception e) {
304 log.warn("Problems when trying to cancel Archive "+archive.getAbsolutePath(), e);
310 log.info("cancelArchive called before archive("+original.getAbsolutePath()+") has been opened!");
312 closeAndReset(); // tidy up and release locks.
317 * only do this if you want to destroy the current file output stream
320 private void closeAndReset() {
325 if (original!=null) {
332 if (odoclock!=null) {
333 odoclock.unlockFile();
342 private final int _TRANSFER_BUFFER=4096*4;
344 * open original archive file for exclusive (locked) reading.
345 * @throws IOException
347 private void accessOriginal() throws IOException {
348 if (original!=null && original.exists()) {
350 odoclock = new SessionFile(original);
353 odoc = new VamsasArchiveReader(original);
358 * Convenience method to copy over the referred entry from the backup to the new version.
359 * Warning messages are raised if no backup exists or the
360 * entry doesn't exist in the backed-up original.
361 * Duplicate writes return true - but a warning message will also be raised.
362 * @param AppDataReference
363 * @return true if AppDataReference now exists in the new document
364 * @throws IOException
366 public boolean transferAppDataEntry(String AppDataReference) throws IOException {
367 return transferAppDataEntry(AppDataReference, AppDataReference);
370 * Transfers an AppDataReference from old to new vamsas archive, with a name change.
371 * @see transferAppDataEntry(String AppDataReference)
372 * @param AppDataReference
373 * @param NewAppDataReference - AppDataReference in new Archive
375 * @throws IOException
377 public boolean transferAppDataEntry(String AppDataReference, String NewAppDataReference) throws IOException {
378 // TODO: Specify valid AppDataReference form in all VamsasArchive handlers
379 if (AppDataReference==null)
380 throw new IOException("null AppDataReference!");
381 if (original==null || !original.exists()) {
382 log.warn("No backup archive exists.");
385 if (entries.containsKey(NewAppDataReference)) {
386 log.warn("Attempt to write '"+NewAppDataReference+"' twice! - IGNORED");
392 java.io.InputStream adstream = odoc.getAppdataStream(AppDataReference);
394 if (adstream==null) {
395 log.warn("AppDataReference '"+AppDataReference+"' doesn't exist in backup archive.");
399 java.io.OutputStream adout = getAppDataStream(NewAppDataReference);
400 // copy over the bytes
403 byte[] buffer = new byte[_TRANSFER_BUFFER]; // conservative estimate of a sensible buffer
405 if ((written = adstream.read(buffer))>-1) {
406 adout.write(buffer, 0, written);
407 log.debug("Transferring "+written+".");
410 } while (written>-1);
411 log.debug("Sucessfully transferred AppData for '"
412 +AppDataReference+"' as '"+NewAppDataReference+"' ("+count+" bytes)");
416 * Tidies up and closes archive, removing any backups that were created.
417 * NOTE: It is up to the caller to delete the original archive backup obtained from backupFile()
418 * TODO: ensure all extant AppDataReference jar entries are transferred to new Jar
419 * TODO: provide convenient mechanism for generating new unique AppDataReferences and adding them to the document
421 public void closeArchive() throws IOException {
422 if (newarchive!=null) {
423 newarchive.closeEntry();
424 if (!isDocumentWritten())
425 log.warn("Premature closure of archive '"+archive.getAbsolutePath()+"': No document has been written.");
430 log.warn("Attempt to close archive that has not been opened for writing.");
434 * Access original archive if it exists, pass the reader to the client
435 * Note: this is NOT thread safe and a call to closeArchive() will by necessity
436 * close and invalidate the VamsasArchiveReader object.
437 * @return null if no original archive exists.
439 public VamsasArchiveReader getOriginalArchiveReader() throws IOException {
440 if (!virginArchive) {
447 * returns original document's root vamsas elements.
449 * @throws IOException
450 * @throws org.exolab.castor.xml.MarshalException
451 * @throws org.exolab.castor.xml.ValidationException
453 public object[] getOriginalRoots() throws IOException,
454 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
455 return VamsasArchive.getOriginalRoots(this);
458 * Access original document if it exists, and get VAMSAS root objects.
459 * @return vector of vamsas roots from original document
460 * @throws IOException
462 public static object[] getOriginalRoots(VamsasArchive ths) throws IOException,
463 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
464 VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
467 if (oReader.isValid()) {
468 InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
469 VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
471 return doc.getVAMSAS();
472 // TODO ensure embedded appDatas are garbage collected to save memory
474 InputStream vxmlis = oReader.getVamsasXmlStream();
475 if (vxmlis!=null) { // Might be an old vamsas file.
476 BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
477 InputStreamReader vxml = new InputStreamReader(ixml);
478 VAMSAS root[] = new VAMSAS[1];
479 root[0] = VAMSAS.unmarshal(vxml);
488 * Access and return current vamsas Document, if it exists, or create a new one
489 * (without affecting VamsasArchive object state - so is NOT THREAD SAFE)
490 * TODO: possibly modify internal state to lock low-level files
491 * (like the IClientDocument interface instance constructer would do)
492 * @see org.vamsas.simpleclient.VamsasArchive.getOriginalVamsasDocument for additional caveats
495 * @throws IOException
496 * @throws org.exolab.castor.xml.MarshalException
497 * @throws org.exolab.castor.xml.ValidationException
499 public VamsasDocument getVamsasDocument() throws IOException,
500 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
501 VamsasDocument doc = getOriginalVamsasDocument(this);
504 // Create a new document and return it
505 doc = DocumentStuff.newVamsasDocument(new VAMSAS[] { new VAMSAS()},
506 ProvenanceStuff.newProvenance("org.vamsas.simpleclient.VamsasArchive", "Created new empty document")
507 , VersionEntries.latestVersion());
511 * Access the original vamsas document for a VamsasArchive class, and return it.
512 * Users of the VamsasArchive class should use the getVamsasDocument method to retrieve
513 * the current document - only use this one if you want the 'backup' version.
514 * TODO: catch OutOfMemoryError - they are likely to occur here.
515 * NOTE: vamsas.xml datastreams are constructed as 'ALPHA_VERSION' vamsas documents.
517 * @return null if no document exists.
518 * @throws IOException
519 * @throws org.exolab.castor.xml.MarshalException
520 * @throws org.exolab.castor.xml.ValidationException
522 public static VamsasDocument getOriginalVamsasDocument(VamsasArchive ths) throws IOException,
523 org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {
524 VamsasArchiveReader oReader = ths.getOriginalArchiveReader();
526 if (oReader.isValid()) {
527 InputStreamReader vdoc = new InputStreamReader(oReader.getVamsasDocumentStream());
528 VamsasDocument doc = VamsasDocument.unmarshal(vdoc);
532 // deprecated data handler
533 InputStream vxmlis = oReader.getVamsasXmlStream();
534 if (vxmlis!=null) { // Might be an old vamsas file.
535 BufferedInputStream ixml = new BufferedInputStream(oReader.getVamsasXmlStream());
536 InputStreamReader vxml = new InputStreamReader(ixml);
537 VAMSAS root[] = new VAMSAS[1];
538 root[0] = VAMSAS.unmarshal(vxml);
540 log.debug("Reading old format vamsas.xml into a dummy document.");
541 VamsasDocument doc = DocumentStuff.newVamsasDocument(root,
542 ProvenanceStuff.newProvenance(
543 "org.vamsas.simpleclient.VamsasArchive", // TODO: VAMSAS: decide on 'system' operations provenance form
544 "Vamsas Document constructed from vamsas.xml in <file>"
545 // TODO: VAMSAS: decide on machine readable info embedding in provenance should be done
546 +ths.original+"</file>"), VersionEntries.ALPHA_VERSION);
553 } // otherwise - there was no valid original document to read.