refactored org to uk
[vamsas.git] / src / org / vamsas / client / simpleclient / SimpleClientAppdata.java
index d488782..84bc48a 100644 (file)
@@ -3,14 +3,21 @@
  */
 package org.vamsas.client.simpleclient;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInput;
 import java.io.DataInputStream;
 import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.util.Vector;
+import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
+import java.util.jar.JarOutputStream;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -33,13 +40,25 @@ public class SimpleClientAppdata implements IClientAppdata {
    */
   protected boolean accessedDocument = false;
   /**
-   * has the user datablock been modified ?
+   * has the user datablock been modified ? 
+   * temporary file containing new user specific application data chunk
    */
-  protected boolean modifiedUserData = false;
+  SessionFile newUserData=null;
+  JarOutputStream newUserDataStream = null;
   /**
    * has the apps global datablock been modified ?
+   * temporary file containing new global application data chunk
    */
-  protected boolean modifiedAppData = false;
+  SessionFile newAppData=null;
+  JarOutputStream newAppDataStream=null;
+  /**
+   * set by extractAppData
+   */
+  protected ApplicationData appsGlobal = null;
+  /**
+   * set by extractAppData
+   */
+  protected User usersData = null;
   
   ClientDocument clientdoc;
   /**
@@ -64,14 +83,6 @@ public class SimpleClientAppdata implements IClientAppdata {
     this.clientdoc = clientdoc;
   }
   /**
-   * set by extractAppData
-   */
-  protected ApplicationData appsGlobal = null;
-  /**
-   * set by extractAppData
-   */
-  protected User usersData = null;
-  /**
    * gets appropriate app data for the application, if it exists in this dataset
    * Called by every accessor to ensure data has been retrieved from document.
    */
@@ -91,12 +102,10 @@ public class SimpleClientAppdata implements IClientAppdata {
         AppData clientdat = (AppData) apldataset.get(0);
         if (clientdat instanceof ApplicationData) {
           appsGlobal = (ApplicationData) clientdat;
-          modifiedAppData = false;
           if (apldataset.size()>1) {
             clientdat = (AppData) apldataset.get(1);
             if (clientdat instanceof User) {
               usersData = (User) clientdat;
-              modifiedUserData = false;
             }
             if (apldataset.size()>2)
               log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
@@ -143,6 +152,10 @@ public class SimpleClientAppdata implements IClientAppdata {
    */
   private byte[] getAppDataAsByteArray(AppData appdata, VamsasArchiveReader docreader) {
     if (appdata.getData()==null) {
+      if (docreader==null) {
+        log.warn("Silently failing getAppDataAsByteArray with null docreader.",new Exception());
+        return null;
+      }
       // resolve and load data
       JarInputStream entry = getAppDataStream(appdata, docreader); 
       ByteArrayOutputStream bytes = new ByteArrayOutputStream();
@@ -178,7 +191,7 @@ public class SimpleClientAppdata implements IClientAppdata {
    * @return data in object or null if no data is accessible
    */
   private DataInput getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
-    if (appdata!=null) {
+    if (appdata!=null && docreader!=null) {
       String entryRef = appdata.getDataReference();
       if (entryRef!=null) {
         log.debug("Resolving AppData reference for "+entryRef);
@@ -222,7 +235,7 @@ public class SimpleClientAppdata implements IClientAppdata {
       appdName = "User's Appdata";
     }    
     log.debug("getting "+appdName+" as a byte array");
-    extractAppData(clientdoc.getVamsasDocument());// TODO: get VamsasArchiveReader from sclient
+    extractAppData(clientdoc.getVamsasDocument());
     AppData object;
     if (!clientOrUser) {
       object = appsGlobal;
@@ -298,15 +311,103 @@ public class SimpleClientAppdata implements IClientAppdata {
   /**
    * methods for writing new AppData entries.
    */
-  
+  private DataOutput _getAppdataOutputStream(boolean clientOrUser) {
+    String apdname;
+    SessionFile apdfile=null;
+    if (!clientOrUser) {
+      apdname = "clientAppData";
+      apdfile = newAppData;
+    } else {
+      apdname = "userAppData";
+      apdfile = newUserData;
+    }
+    try {
+      if (apdfile==null) {
+        apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
+        log.debug("Successfully made temp appData file for "+apdname);
+      } else {
+        // truncate to remove existing data.
+        apdfile.fileLock.getRaFile().setLength(0);
+        log.debug("Successfully truncated existing temp appData for "+apdname);
+      }
+      } catch (Exception e) {
+      log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
+    }
+    // we do not make another file for the new entry if one exists already
+    if (!clientOrUser) {
+      newAppData = apdfile;
+    } else {
+      newUserData = apdfile;
+    }
+    try {
+      apdfile.lockFile();
+      // LATER: Refactor these local AppDatastream IO stuff to their own class.
+      JarOutputStream dstrm = 
+        new JarOutputStream(apdfile.fileLock.getBufferedOutputStream(true));
+      if (!clientOrUser) {
+        newAppDataStream = dstrm;
+      } else {
+        newUserDataStream = dstrm;
+      }
+      dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
+      // LATER: there may be trouble ahead if an AppDataOutputStream is written to by one thread when another truncates the file. This situation should be prevented if possible
+      return new AppDataOutputStream(dstrm);
+    }
+    catch (Exception e) {
+      log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
+    }
+    // tidy up and return null
+    apdfile.unlockFile();
+    return null;
+   }
+  /**
+   * copy data from the appData jar file to an appropriately 
+   * referenced jar or Data entry for the given ApplicationData
+   * Assumes the JarFile is properly closed. 
+   * @param vdoc session Document handler
+   * @param appd the AppData whose block is being updated
+   * @param apdjar the new data in a Jar written by this class
+   */
+  protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd, SessionFile apdjar) throws IOException {
+    if (apdjar==null || apdjar.sessionFile==null || !apdjar.sessionFile.exists()) {
+      throw new IOException("No temporary Appdata to recover and transfer.");
+    }
+    if (vdoc==null) {
+      log.fatal("FATAL! NO DOCUMENT TO WRITE TO!");
+      throw new IOException("FATAL! NO DOCUMENT TO WRITE TO!");
+    }
+    log.debug("Recovering AppData entry from "+apdjar.sessionFile);
+    JarInputStream istrm = new JarInputStream(apdjar.getBufferedInputStream(true));
+    JarEntry je=null;
+    while (istrm.available()>0 && (je=istrm.getNextJarEntry())!=null && !je.getName().equals("appData_entry.dat")) {
+      if (je!=null)
+        log.debug("Ignoring extraneous entry "+je.getName());
+    }
+    if (istrm.available()>0 && je!=null) {
+      log.debug("Found appData_entry.dat in Jar");
+      String ref = appd.getDataReference();
+      if (ref==null) {
+        throw new IOException("Null AppData.DataReference passed.");
+      }
+      if (vdoc.writeAppdataFromStream(ref, istrm)) {
+        log.debug("Entry updated successfully.");
+      } else {
+        throw new IOException("writeAppdataFromStream did not return true - expect future badness."); // LATER - verify why this might occur.
+      }
+    } else {
+      throw new IOException("Couldn't find appData_entry.dat in temporary jar file "+apdjar.sessionFile.getAbsolutePath());
+    }
+    istrm.close();
+  }
   /* (non-Javadoc)
    * @see org.vamsas.client.IClientAppdata#getClientOutputStream()
    */
   public DataOutput getClientOutputStream() {
     if (clientdoc==null)
       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    // TODO Auto-generated method stub
-    return null;
+    if (log.isDebugEnabled())
+      log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());   
+    return _getAppdataOutputStream(false);
   }
 
   /* (non-Javadoc)
@@ -315,8 +416,10 @@ public class SimpleClientAppdata implements IClientAppdata {
   public DataOutput getUserOutputStream() {
     if (clientdoc==null)
       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    // TODO Auto-generated method stub
-    return null;
+    if (log.isDebugEnabled())
+      log.debug("trying to getUserOutputStream for ("
+          +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());   
+    return _getAppdataOutputStream(true);
   }
 
   /* (non-Javadoc)
@@ -325,7 +428,10 @@ public class SimpleClientAppdata implements IClientAppdata {
   public boolean hasClientAppdata() {
     if (clientdoc==null)
       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    // TODO Auto-generated method stub
+    extractAppData(clientdoc.getVamsasDocument());
+    // LATER - check validity of a DataReference before we return true
+    if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
+      return true;
     return false;
   }
 
@@ -335,18 +441,38 @@ public class SimpleClientAppdata implements IClientAppdata {
   public boolean hasUserAppdata() {
     if (clientdoc==null)
       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    // TODO Auto-generated method stub
+    extractAppData(clientdoc.getVamsasDocument());
+    // LATER - check validity of a DataReference before we return true
+    if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
+      return true;
+    return false;
+  }
+  private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
+    try {
+      if (data!=null && data.length>0) 
+        ostrm.write(data);
+      ostrm.closeEntry();
+      return true;
+    }
+    catch (Exception e) {
+      log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
+    }
     return false;
   }
-
   /* (non-Javadoc)
    * @see org.vamsas.client.IClientAppdata#setClientAppdata(byte[])
    */
   public void setClientAppdata(byte[] data) {
     if (clientdoc==null)
       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    // TODO Auto-generated method stub
-    
+    _getAppdataOutputStream(false);
+    if (newAppDataStream==null) {
+      // LATER: define an exception for this ? - operation may fail even if file i/o not involved
+      log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir);
+    } else {
+      _writeAppDataStream(newAppDataStream, data);
+      // LATER: deal with error case - do we make session read only, or what ?
+    }
   }
 
   /* (non-Javadoc)
@@ -355,14 +481,62 @@ public class SimpleClientAppdata implements IClientAppdata {
   public void setUserAppdata(byte[] data) {
     if (clientdoc==null)
       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    // TODO Auto-generated method stub
-    
+    _getAppdataOutputStream(true);
+    if (newUserDataStream==null) {
+      // LATER: define an exception for this ? - operation may fail even if file i/o not involved
+      log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir);
+    } else {
+      _writeAppDataStream(newUserDataStream, data);
+      // LATER: deal with error case - do we make session read only, or what ?
+    }
+  }
+  /**
+   * flush and close outstanding output streams. 
+   *  - do this before checking data length.
+   * @throws IOException
+   */
+  protected void closeForWriting() throws IOException {
+    if (newAppDataStream!=null) {
+      newAppDataStream.flush();
+      newAppDataStream.closeEntry();
+      newAppDataStream.close();
+    }
+    if (newUserDataStream!=null) {
+      newUserDataStream.flush();
+      newUserDataStream.closeEntry();
+      newUserDataStream.close();
+    }
+  }
+
+
+  /**
+   * 
+   * @return true if any AppData blocks have to be updated in session Jar
+   */
+  protected boolean isModified() {
+    // LATER differentiate between core xml modification and Jar Entry modification.
+    if (newAppData.sessionFile.exists() || newUserData.sessionFile.exists())
+      return true;
+    return false;
   }
   /* (non-Javadoc)
    * @see java.lang.Object#finalize()
    */
   protected void finalize() throws Throwable {
-    // TODO Auto-generated method stub
+    if (newAppDataStream!=null) {
+      newAppDataStream = null;
+    }
+    if (newAppDataStream!=null) {
+      newUserDataStream = null;
+    }
+    if (newAppData!=null) {
+      newAppData.eraseExistence();
+      newAppData = null;
+    }
+    if (newUserData!=null) {
+      newUserData.eraseExistence();
+      newUserData = null;
+    }
     super.finalize();
   }