applied LGPLv3 and source code formatting.
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / SimpleClientAppdata.java
index 7ea7dda..183d114 100644 (file)
-/**
- * 
- */
-package uk.ac.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;
-
-import uk.ac.vamsas.client.AppDataInputStream;
-import uk.ac.vamsas.client.AppDataOutputStream;
-import uk.ac.vamsas.client.IClientAppdata;
-import uk.ac.vamsas.objects.core.AppData;
-import uk.ac.vamsas.objects.core.ApplicationData;
-import uk.ac.vamsas.objects.core.User;
-import uk.ac.vamsas.objects.utils.AppDataReference;
-
-/**
- * @author jimp
- * Access interface to data chunks read from a VamsasArchiveReader stream 
- * (or byte buffer input stream) or written to a VamsasArchive stream.
- * // TODO: get VamsasArchiveReader from sclient
- */
-public class SimpleClientAppdata implements IClientAppdata {
-  private static Log log = LogFactory.getLog(SimpleClientAppdata.class);
-  /**
-   * has the session's document been accessed to get the AppData entrys?
-   */
-  protected boolean accessedDocument = false;
-  /**
-   * has the user datablock been modified ? 
-   * temporary file containing new user specific application data chunk
-   */
-  SessionFile newUserData=null;
-  JarOutputStream newUserDataStream = null;
-  /**
-   * has the apps global datablock been modified ?
-   * temporary file containing new global application data chunk
-   */
-  SessionFile newAppData=null;
-  JarOutputStream newAppDataStream=null;
-  /**
-   * set by extractAppData
-   */
-  protected ApplicationData appsGlobal = null;
-  /**
-   * set by extractAppData
-   */
-  protected User usersData = null;
-  
-  ClientDocument clientdoc;
-  /**
-   * state flags
-   * - accessed ClientAppdata
-   * - accessed UserAppdata
-   * => inputStream from embedded xml or jar entry of backup has been created
-   * - set ClientAppdata
-   * - set UserAppdata
-   * => an output stream has been created and written to - or a data chunk has been written.
-   *  - need flag for switching between embedded and jar entry mode ? - always write a jar entry for a stream.
-   *  - need code for rewind and overwriting if the set*Appdata methods are called more than once.
-   *  - need flags for streams to except a call to set*Appdata when an output stream exists and is open.
-   *  - need 
-   * @param clientdoc The ClientDocument instance that this IClientAppData is accessing
-   */
-  protected SimpleClientAppdata(ClientDocument clientdoc) {
-    if (clientdoc==null) {
-      log.fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
-      throw new Error("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
-    }
-    this.clientdoc = clientdoc;
-  }
-  /**
-   * ensures that the appData information for this client 
-   * instance has been extracted from the vamsas document provided by clientdoc.
-   */
-  private void extractAppData()
-  {
-    if (!accessedDocument)
-      _extractAppData(clientdoc.getVamsasDocument());
-  }
-  /**
-   * 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.
-   */
-  private void _extractAppData(uk.ac.vamsas.objects.core.VamsasDocument doc) {
-    if (doc==null) {
-      log.debug("extractAppData called for null document object");
-      return;
-    }
-    if (accessedDocument) {
-      return;
-    }
-    Vector apldataset = AppDataReference.getUserandApplicationsData(
-        doc, clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());
-    accessedDocument = true;
-    if (apldataset!=null) {
-      if (apldataset.size()>0) {
-        AppData clientdat = (AppData) apldataset.get(0);
-        if (clientdat instanceof ApplicationData) {
-          appsGlobal = (ApplicationData) clientdat;
-          if (apldataset.size()>1) {
-            clientdat = (AppData) apldataset.get(1);
-            if (clientdat instanceof User) {
-              usersData = (User) clientdat;
-            }
-            if (apldataset.size()>2)
-              log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
-          } 
-        } else {
-          log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName());
-        }
-        apldataset.removeAllElements(); // destroy references.
-      }
-    }
-  }
-  /**
-   * LATER: generalize this for different low-level session implementations (it may not always be a Jar)
-   * @param appdata
-   * @param docreader
-   * @return
-   */
-  private InputStream getAppDataStream(AppData appdata, VamsasArchiveReader docreader) {
-    String entryRef = appdata.getDataReference();
-    if (entryRef!=null) {
-      log.debug("Resolving appData reference +"+entryRef);
-      InputStream entry = docreader.getAppdataStream(entryRef);
-      if (entry!=null) {
-        return entry;
-        // log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
-      }
-    } else {
-      log.debug("GetAppDataStream called for an AppData without a data reference.");
-    }
-    return null;
-  }
-  /**
-   * yuk - size of buffer used for slurping appData JarEntry into a byte array.
-   */
-  private final int _TRANSFER_BUFFER=4096*4;
-
-  /**
-   * Resolve AppData object to a byte array.
-   * @param appdata
-   * @param archiveReader
-   * @return null or the application data as a byte array
-   */
-  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
-      InputStream entry = getAppDataStream(appdata, docreader); 
-      ByteArrayOutputStream bytes = new ByteArrayOutputStream();
-      try {
-        byte buff[] = new byte[_TRANSFER_BUFFER];
-        int olen=0;
-        while (entry!=null && entry.available()>0) {
-          int len = entry.read(buff, 0, _TRANSFER_BUFFER);
-          if (len>-1)
-          { bytes.write(buff, 0, len);
-            olen+=len;
-          }
-        }
-        buff=null;
-      } catch (Exception e) {
-        log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
-      }
-      if (bytes.size()>0) {
-        // LATER: deal with probable OutOfMemoryErrors here
-        log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference());
-        byte data[] = bytes.toByteArray();
-        bytes = null;
-        return data;
-      }
-      return null;
-    } else {
-      log.debug("Returning inline AppData block for "+appdata.getVorbaId());
-      return appdata.getData();
-    }
-  }
-  /**
-   * internal method for getting a DataInputStream from an AppData object.
-   * @param appdata
-   * @param docreader
-   * @return data in object or null if no data is accessible
-   */
-  private AppDataInputStream getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
-    if (appdata!=null && docreader!=null) {
-      String entryRef = appdata.getDataReference();
-      if (entryRef!=null) {
-        log.debug("Resolving AppData reference for "+entryRef);
-        InputStream jstrm = docreader.getAppdataStream(entryRef);
-        if (jstrm!=null)
-          return new AppDataInputStream(jstrm);
-        else {
-          log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
-          return null;
-        }
-      } else {
-        // return a byteArray input stream
-        byte[] data=appdata.getData();
-        if (data.length>0) {
-          ByteArrayInputStream stream = new ByteArrayInputStream(data);
-          return new AppDataInputStream(stream);
-        } else {
-          log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
-          return null;
-        }
-      }
-    } else {
-      log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
-    }
-    return null;
-  }
-
-  /**
-   * internal method for getting ByteArray from AppData object
-   * @param clientOrUser - true for returning userData, otherwise return Client AppData.
-   * @return null or byte array
-   */
-  private byte[] _getappdataByteArray(boolean clientOrUser) {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    byte[] data=null;
-    String appdName;
-    if (!clientOrUser) {
-      appdName = "Client's Appdata";
-    } else {
-      appdName = "User's Appdata";
-    }    
-    log.debug("getting "+appdName+" as a byte array");
-    extractAppData();
-    AppData object;
-    if (!clientOrUser) {
-      object = appsGlobal;
-    } else {
-      object = usersData;
-    }
-    if (object!=null) {
-      log.debug("Trying to resolve "+appdName+" object to byte array.");
-      data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
-    }
-    if (data == null)
-      log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
-    return data;
-    
-  }
-  
-  /**
-   * common method for Client and User AppData->InputStream accessor
-   * @param clientOrUser - the appData to resolve - false for client, true for user appdata.
-   * @return null or the DataInputStream desired.
-   */
-  private AppDataInputStream _getappdataInputStream(boolean clientOrUser) {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    String appdName;
-    if (!clientOrUser) {
-      appdName = "Client's Appdata";
-    } else {
-      appdName = "User's Appdata";
-    }
-    if (log.isDebugEnabled())
-      log.debug("getting "+appdName+" as an input stream.");
-    extractAppData();
-    AppData object;
-    if (!clientOrUser) {
-      object = appsGlobal;
-    } else {
-      object = usersData;
-    }
-    if (object!=null) {
-      log.debug("Trying to resolve ClientAppdata object to an input stream.");
-      return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
-    }
-    log.debug("getClientInputStream returning null.");
-    return null;
-  }
-  /* (non-Javadoc)
-   * @see uk.ac.vamsas.client.IClientAppdata#getClientAppdata()
-   */
-  public byte[] getClientAppdata() {
-    return _getappdataByteArray(false);
-  }
-  /* (non-Javadoc)
-   * @see uk.ac.vamsas.client.IClientAppdata#getClientInputStream()
-   */
-  public AppDataInputStream getClientInputStream() {
-    return _getappdataInputStream(false);
-  }
-
-  /* (non-Javadoc)
-   * @see uk.ac.vamsas.client.IClientAppdata#getUserAppdata()
-   */
-  public byte[] getUserAppdata() {
-    return _getappdataByteArray(true);
-  }
-
-  /* (non-Javadoc)
-   * @see uk.ac.vamsas.client.IClientAppdata#getUserInputStream()
-   */
-  public AppDataInputStream getUserInputStream() {
-    return _getappdataInputStream(true);
-  }
-  /**
-   * methods for writing new AppData entries.
-   */
-  private AppDataOutputStream _getAppdataOutputStream(boolean clientOrUser) {
-    // Must access document to get any existing references
-    extractAppData();
-    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.");
-      }
-      log.debug("Writing appData_entry.dat as "+ref);
-      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 uk.ac.vamsas.client.IClientAppdata#getClientOutputStream()
-   */
-  public AppDataOutputStream getClientOutputStream() {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    if (log.isDebugEnabled())
-      log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());   
-    return _getAppdataOutputStream(false);
-  }
-
-  /* (non-Javadoc)
-   * @see uk.ac.vamsas.client.IClientAppdata#getUserOutputStream()
-   */
-  public AppDataOutputStream getUserOutputStream() {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    if (log.isDebugEnabled())
-      log.debug("trying to getUserOutputStream for ("
-          +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());   
-    return _getAppdataOutputStream(true);
-  }
-
-  /* (non-Javadoc)
-   * @see uk.ac.vamsas.client.IClientAppdata#hasClientAppdata()
-   */
-  public boolean hasClientAppdata() {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    extractAppData();
-    // LATER - check validity of a DataReference before we return true
-    // TODO: return true if usersData is null but we have already written a new data stream 
-    if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
-      return true;
-    return false;
-  }
-
-  /* (non-Javadoc)
-   * @see uk.ac.vamsas.client.IClientAppdata#hasUserAppdata()
-   */
-  public boolean hasUserAppdata() {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    extractAppData();
-    // LATER - check validity of a DataReference before we return true
-    // TODO: return true if usersData is null but we have already written a new data stream 
-    if ((usersData!=null) && (usersData.getDataReference()!=null || usersData.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 uk.ac.vamsas.client.IClientAppdata#setClientAppdata(byte[])
-   */
-  public void setClientAppdata(byte[] data) {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    _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)
-   * @see uk.ac.vamsas.client.IClientAppdata#setUserAppdata(byte[])
-   */
-  public void setUserAppdata(byte[] data) {
-    if (clientdoc==null)
-      throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
-    _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!=null && newAppData.sessionFile.exists()) || (newUserData!=null && newUserData.sessionFile.exists()))
-      return true;
-    return false;
-  }
-  /* (non-Javadoc)
-   * @see java.lang.Object#finalize()
-   */
-  protected void finalize() throws Throwable {
-    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();
-  }
-
-}
+/*\r
+ * This file is part of the Vamsas Client version 0.1. \r
+ * Copyright 2009 by Jim Procter, Iain Milne, Pierre Marguerite, \r
+ *  Andrew Waterhouse and Dominik Lindner.\r
+ * \r
+ * Earlier versions have also been incorporated into Jalview version 2.4 \r
+ * since 2008, and TOPALi version 2 since 2007.\r
+ * \r
+ * The Vamsas Client is free software: you can redistribute it and/or modify\r
+ * it under the terms of the GNU Lesser General Public License as published by\r
+ * the Free Software Foundation, either version 3 of the License, or\r
+ * (at your option) any later version.\r
+ *  \r
+ * The Vamsas Client is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU Lesser General Public License for more details.\r
+ * \r
+ * You should have received a copy of the GNU Lesser General Public License\r
+ * along with the Vamsas Client.  If not, see <http://www.gnu.org/licenses/>.\r
+ */\r
+package uk.ac.vamsas.client.simpleclient;\r
+\r
+import java.io.BufferedInputStream;\r
+import java.io.BufferedOutputStream;\r
+import java.io.ByteArrayInputStream;\r
+import java.io.ByteArrayOutputStream;\r
+import java.io.DataInput;\r
+import java.io.DataInputStream;\r
+import java.io.DataOutput;\r
+import java.io.DataOutputStream;\r
+import java.io.FileInputStream;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.util.Vector;\r
+import java.util.jar.JarEntry;\r
+import java.util.jar.JarInputStream;\r
+import java.util.jar.JarOutputStream;\r
+\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+\r
+import uk.ac.vamsas.client.AppDataInputStream;\r
+import uk.ac.vamsas.client.AppDataOutputStream;\r
+import uk.ac.vamsas.client.IClientAppdata;\r
+import uk.ac.vamsas.objects.core.AppData;\r
+import uk.ac.vamsas.objects.core.ApplicationData;\r
+import uk.ac.vamsas.objects.core.User;\r
+import uk.ac.vamsas.objects.utils.AppDataReference;\r
+\r
+/**\r
+ * @author jimp Access interface to data chunks read from a VamsasArchiveReader\r
+ *         stream (or byte buffer input stream) or written to a VamsasArchive\r
+ *         stream. // TODO: get VamsasArchiveReader from sclient\r
+ */\r
+public class SimpleClientAppdata implements IClientAppdata {\r
+  private static Log log = LogFactory.getLog(SimpleClientAppdata.class);\r
+\r
+  /**\r
+   * has the session's document been accessed to get the AppData entrys?\r
+   */\r
+  protected boolean accessedDocument = false;\r
+\r
+  /**\r
+   * has the user datablock been modified ? temporary file containing new user\r
+   * specific application data chunk\r
+   */\r
+  SessionFile newUserData = null;\r
+\r
+  JarOutputStream newUserDataStream = null;\r
+\r
+  /**\r
+   * has the apps global datablock been modified ? temporary file containing new\r
+   * global application data chunk\r
+   */\r
+  SessionFile newAppData = null;\r
+\r
+  JarOutputStream newAppDataStream = null;\r
+\r
+  /**\r
+   * set by extractAppData\r
+   */\r
+  protected ApplicationData appsGlobal = null;\r
+\r
+  /**\r
+   * set by extractAppData\r
+   */\r
+  protected User usersData = null;\r
+\r
+  ClientDocument clientdoc;\r
+\r
+  /**\r
+   * state flags - accessed ClientAppdata - accessed UserAppdata => inputStream\r
+   * from embedded xml or jar entry of backup has been created - set\r
+   * ClientAppdata - set UserAppdata => an output stream has been created and\r
+   * written to - or a data chunk has been written. - need flag for switching\r
+   * between embedded and jar entry mode ? - always write a jar entry for a\r
+   * stream. - need code for rewind and overwriting if the set*Appdata methods\r
+   * are called more than once. - need flags for streams to except a call to\r
+   * set*Appdata when an output stream exists and is open. - need\r
+   * \r
+   * @param clientdoc\r
+   *          The ClientDocument instance that this IClientAppData is accessing\r
+   */\r
+  protected SimpleClientAppdata(ClientDocument clientdoc) {\r
+    if (clientdoc == null) {\r
+      log\r
+          .fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");\r
+      throw new Error(\r
+          "Implementation error - Null ClientDocument for SimpleClientAppdata construction.");\r
+    }\r
+    this.clientdoc = clientdoc;\r
+  }\r
+\r
+  /**\r
+   * ensures that the appData information for this client instance has been\r
+   * extracted from the vamsas document provided by clientdoc.\r
+   */\r
+  private void extractAppData() {\r
+    if (!accessedDocument)\r
+      _extractAppData(clientdoc.getVamsasDocument());\r
+  }\r
+\r
+  /**\r
+   * gets appropriate app data for the application, if it exists in this dataset\r
+   * Called by every accessor to ensure data has been retrieved from document.\r
+   */\r
+  private void _extractAppData(uk.ac.vamsas.objects.core.VamsasDocument doc) {\r
+    if (doc == null) {\r
+      log.debug("extractAppData called for null document object");\r
+      return;\r
+    }\r
+    if (accessedDocument) {\r
+      return;\r
+    }\r
+    Vector apldataset = AppDataReference.getUserandApplicationsData(doc,\r
+        clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());\r
+    accessedDocument = true;\r
+    if (apldataset != null) {\r
+      if (apldataset.size() > 0) {\r
+        AppData clientdat = (AppData) apldataset.get(0);\r
+        if (clientdat instanceof ApplicationData) {\r
+          appsGlobal = (ApplicationData) clientdat;\r
+          if (apldataset.size() > 1) {\r
+            clientdat = (AppData) apldataset.get(1);\r
+            if (clientdat instanceof User) {\r
+              usersData = (User) clientdat;\r
+            }\r
+            if (apldataset.size() > 2)\r
+              log.info("Ignoring additional (" + (apldataset.size() - 2)\r
+                  + ") AppDatas returned by document appdata query.");\r
+          }\r
+        } else {\r
+          log.warn("Unexpected entry in AppDataReference query: id="\r
+              + clientdat.getVorbaId() + " type="\r
+              + clientdat.getClass().getName());\r
+        }\r
+        apldataset.removeAllElements(); // destroy references.\r
+      }\r
+    }\r
+  }\r
+\r
+  /**\r
+   * LATER: generalize this for different low-level session implementations (it\r
+   * may not always be a Jar)\r
+   * \r
+   * @param appdata\r
+   * @param docreader\r
+   * @return\r
+   */\r
+  private InputStream getAppDataStream(AppData appdata,\r
+      VamsasArchiveReader docreader) {\r
+    String entryRef = appdata.getDataReference();\r
+    if (entryRef != null) {\r
+      log.debug("Resolving appData reference +" + entryRef);\r
+      InputStream entry = docreader.getAppdataStream(entryRef);\r
+      if (entry != null) {\r
+        return entry;\r
+        // log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");\r
+      }\r
+    } else {\r
+      log\r
+          .debug("GetAppDataStream called for an AppData without a data reference.");\r
+    }\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * yuk - size of buffer used for slurping appData JarEntry into a byte array.\r
+   */\r
+  private final int _TRANSFER_BUFFER = 4096 * 4;\r
+\r
+  /**\r
+   * Resolve AppData object to a byte array.\r
+   * \r
+   * @param appdata\r
+   * @param archiveReader\r
+   * @return null or the application data as a byte array\r
+   */\r
+  private byte[] getAppDataAsByteArray(AppData appdata,\r
+      VamsasArchiveReader docreader) {\r
+    if (appdata.getData() == null) {\r
+      if (docreader == null) {\r
+        log.warn("Silently failing getAppDataAsByteArray with null docreader.",\r
+            new Exception());\r
+        return null;\r
+      }\r
+      // resolve and load data\r
+      InputStream entry = getAppDataStream(appdata, docreader);\r
+      ByteArrayOutputStream bytes = new ByteArrayOutputStream();\r
+      try {\r
+        byte buff[] = new byte[_TRANSFER_BUFFER];\r
+        int olen = 0;\r
+        while (entry != null && entry.available() > 0) {\r
+          int len = entry.read(buff, 0, _TRANSFER_BUFFER);\r
+          if (len > -1) {\r
+            bytes.write(buff, 0, len);\r
+            olen += len;\r
+          }\r
+        }\r
+        buff = null;\r
+      } catch (Exception e) {\r
+        log\r
+            .warn(\r
+                "Unexpected exception - probable truncation when accessing VamsasDocument entry "\r
+                    + appdata.getDataReference(), e);\r
+      }\r
+      if (bytes.size() > 0) {\r
+        // LATER: deal with probable OutOfMemoryErrors here\r
+        log.debug("Got " + bytes.size() + " bytes from AppDataReference "\r
+            + appdata.getDataReference());\r
+        byte data[] = bytes.toByteArray();\r
+        bytes = null;\r
+        return data;\r
+      }\r
+      return null;\r
+    } else {\r
+      log.debug("Returning inline AppData block for " + appdata.getVorbaId());\r
+      return appdata.getData();\r
+    }\r
+  }\r
+\r
+  /**\r
+   * internal method for getting a DataInputStream from an AppData object.\r
+   * \r
+   * @param appdata\r
+   * @param docreader\r
+   * @return data in object or null if no data is accessible\r
+   */\r
+  private AppDataInputStream getAppDataAsDataInputStream(AppData appdata,\r
+      VamsasArchiveReader docreader) {\r
+    if (appdata != null && docreader != null) {\r
+      String entryRef = appdata.getDataReference();\r
+      if (entryRef != null) {\r
+        log.debug("Resolving AppData reference for " + entryRef);\r
+        InputStream jstrm = docreader.getAppdataStream(entryRef);\r
+        if (jstrm != null)\r
+          return new AppDataInputStream(jstrm);\r
+        else {\r
+          log.debug("Returning null input stream for unresolved reference ("\r
+              + entryRef + ") id=" + appdata.getVorbaId());\r
+          return null;\r
+        }\r
+      } else {\r
+        // return a byteArray input stream\r
+        byte[] data = appdata.getData();\r
+        if (data.length > 0) {\r
+          ByteArrayInputStream stream = new ByteArrayInputStream(data);\r
+          return new AppDataInputStream(stream);\r
+        } else {\r
+          log\r
+              .debug("Returning null input stream for empty Appdata data block in id="\r
+                  + appdata.getVorbaId());\r
+          return null;\r
+        }\r
+      }\r
+    } else {\r
+      log.debug("Returning null DataInputStream for appdata entry:"\r
+          + appdata.getVorbaId());\r
+    }\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * internal method for getting ByteArray from AppData object\r
+   * \r
+   * @param clientOrUser\r
+   *          - true for returning userData, otherwise return Client AppData.\r
+   * @return null or byte array\r
+   */\r
+  private byte[] _getappdataByteArray(boolean clientOrUser) {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    byte[] data = null;\r
+    String appdName;\r
+    if (!clientOrUser) {\r
+      appdName = "Client's Appdata";\r
+    } else {\r
+      appdName = "User's Appdata";\r
+    }\r
+    log.debug("getting " + appdName + " as a byte array");\r
+    extractAppData();\r
+    AppData object;\r
+    if (!clientOrUser) {\r
+      object = appsGlobal;\r
+    } else {\r
+      object = usersData;\r
+    }\r
+    if (object != null) {\r
+      log.debug("Trying to resolve " + appdName + " object to byte array.");\r
+      data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());\r
+    }\r
+    if (data == null)\r
+      log\r
+          .debug("Returning null for " + appdName\r
+              + "ClientAppdata byte[] array");\r
+    return data;\r
+\r
+  }\r
+\r
+  /**\r
+   * common method for Client and User AppData->InputStream accessor\r
+   * \r
+   * @param clientOrUser\r
+   *          - the appData to resolve - false for client, true for user\r
+   *          appdata.\r
+   * @return null or the DataInputStream desired.\r
+   */\r
+  private AppDataInputStream _getappdataInputStream(boolean clientOrUser) {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    String appdName;\r
+    if (!clientOrUser) {\r
+      appdName = "Client's Appdata";\r
+    } else {\r
+      appdName = "User's Appdata";\r
+    }\r
+    if (log.isDebugEnabled())\r
+      log.debug("getting " + appdName + " as an input stream.");\r
+    extractAppData();\r
+    AppData object;\r
+    if (!clientOrUser) {\r
+      object = appsGlobal;\r
+    } else {\r
+      object = usersData;\r
+    }\r
+    if (object != null) {\r
+      log.debug("Trying to resolve ClientAppdata object to an input stream.");\r
+      return getAppDataAsDataInputStream(object, clientdoc\r
+          .getVamsasArchiveReader());\r
+    }\r
+    log.debug("getClientInputStream returning null.");\r
+    return null;\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#getClientAppdata()\r
+   */\r
+  public byte[] getClientAppdata() {\r
+    return _getappdataByteArray(false);\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#getClientInputStream()\r
+   */\r
+  public AppDataInputStream getClientInputStream() {\r
+    return _getappdataInputStream(false);\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#getUserAppdata()\r
+   */\r
+  public byte[] getUserAppdata() {\r
+    return _getappdataByteArray(true);\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#getUserInputStream()\r
+   */\r
+  public AppDataInputStream getUserInputStream() {\r
+    return _getappdataInputStream(true);\r
+  }\r
+\r
+  /**\r
+   * methods for writing new AppData entries.\r
+   */\r
+  private AppDataOutputStream _getAppdataOutputStream(boolean clientOrUser) {\r
+    // Must access document to get any existing references\r
+    extractAppData();\r
+    String apdname;\r
+    SessionFile apdfile = null;\r
+    if (!clientOrUser) {\r
+      apdname = "clientAppData";\r
+      apdfile = newAppData;\r
+    } else {\r
+      apdname = "userAppData";\r
+      apdfile = newUserData;\r
+    }\r
+    try {\r
+      if (apdfile == null) {\r
+        apdfile = clientdoc.sclient._session\r
+            .getTempSessionFile(apdname, ".jar");\r
+        log.debug("Successfully made temp appData file for " + apdname);\r
+      } else {\r
+        // truncate to remove existing data.\r
+        apdfile.fileLock.getRaFile().setLength(0);\r
+        log\r
+            .debug("Successfully truncated existing temp appData for "\r
+                + apdname);\r
+      }\r
+    } catch (Exception e) {\r
+      log.error("Whilst opening temp file in directory "\r
+          + clientdoc.sclient._session.sessionDir, e);\r
+    }\r
+    // we do not make another file for the new entry if one exists already\r
+    if (!clientOrUser) {\r
+      newAppData = apdfile;\r
+    } else {\r
+      newUserData = apdfile;\r
+    }\r
+    try {\r
+      apdfile.lockFile();\r
+      // LATER: Refactor these local AppDatastream IO stuff to their own class.\r
+      JarOutputStream dstrm = new JarOutputStream(apdfile.fileLock\r
+          .getBufferedOutputStream(true));\r
+      if (!clientOrUser) {\r
+        newAppDataStream = dstrm;\r
+      } else {\r
+        newUserDataStream = dstrm;\r
+      }\r
+      dstrm.putNextEntry(new JarEntry("appData_entry.dat"));\r
+      // LATER: there may be trouble ahead if an AppDataOutputStream is written\r
+      // to by one thread when another truncates the file. This situation should\r
+      // be prevented if possible\r
+      return new AppDataOutputStream(dstrm);\r
+    } catch (Exception e) {\r
+      log.error("Whilst opening jar output stream for file "\r
+          + apdfile.sessionFile);\r
+    }\r
+    // tidy up and return null\r
+    apdfile.unlockFile();\r
+    return null;\r
+  }\r
+\r
+  /**\r
+   * copy data from the appData jar file to an appropriately referenced jar or\r
+   * Data entry for the given ApplicationData Assumes the JarFile is properly\r
+   * closed.\r
+   * \r
+   * @param vdoc\r
+   *          session Document handler\r
+   * @param appd\r
+   *          the AppData whose block is being updated\r
+   * @param apdjar\r
+   *          the new data in a Jar written by this class\r
+   */\r
+  protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd,\r
+      SessionFile apdjar) throws IOException {\r
+    if (apdjar == null || apdjar.sessionFile == null\r
+        || !apdjar.sessionFile.exists()) {\r
+      throw new IOException("No temporary Appdata to recover and transfer.");\r
+    }\r
+    if (vdoc == null) {\r
+      log.fatal("FATAL! NO DOCUMENT TO WRITE TO!");\r
+      throw new IOException("FATAL! NO DOCUMENT TO WRITE TO!");\r
+    }\r
+    log.debug("Recovering AppData entry from " + apdjar.sessionFile);\r
+    JarInputStream istrm = new JarInputStream(apdjar\r
+        .getBufferedInputStream(true));\r
+    JarEntry je = null;\r
+    while (istrm.available() > 0 && (je = istrm.getNextJarEntry()) != null\r
+        && !je.getName().equals("appData_entry.dat")) {\r
+      if (je != null)\r
+        log.debug("Ignoring extraneous entry " + je.getName());\r
+    }\r
+    if (istrm.available() > 0 && je != null) {\r
+      log.debug("Found appData_entry.dat in Jar");\r
+      String ref = appd.getDataReference();\r
+      if (ref == null) {\r
+        throw new IOException("Null AppData.DataReference passed.");\r
+      }\r
+      log.debug("Writing appData_entry.dat as " + ref);\r
+      if (vdoc.writeAppdataFromStream(ref, istrm)) {\r
+        log.debug("Entry updated successfully.");\r
+      } else {\r
+        throw new IOException(\r
+            "writeAppdataFromStream did not return true - expect future badness."); // LATER\r
+                                                                                    // -\r
+                                                                                    // verify\r
+                                                                                    // why\r
+                                                                                    // this\r
+                                                                                    // might\r
+                                                                                    // occur.\r
+      }\r
+    } else {\r
+      throw new IOException(\r
+          "Couldn't find appData_entry.dat in temporary jar file "\r
+              + apdjar.sessionFile.getAbsolutePath());\r
+    }\r
+    istrm.close();\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#getClientOutputStream()\r
+   */\r
+  public AppDataOutputStream getClientOutputStream() {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    if (log.isDebugEnabled())\r
+      log.debug("trying to getClientOutputStream for "\r
+          + clientdoc.sclient.client.getClientUrn());\r
+    return _getAppdataOutputStream(false);\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#getUserOutputStream()\r
+   */\r
+  public AppDataOutputStream getUserOutputStream() {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    if (log.isDebugEnabled())\r
+      log.debug("trying to getUserOutputStream for ("\r
+          + clientdoc.sclient.getUserHandle().getFullName() + ")"\r
+          + clientdoc.sclient.client.getClientUrn());\r
+    return _getAppdataOutputStream(true);\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#hasClientAppdata()\r
+   */\r
+  public boolean hasClientAppdata() {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    extractAppData();\r
+    // LATER - check validity of a DataReference before we return true\r
+    // TODO: return true if usersData is null but we have already written a new\r
+    // data stream\r
+    if ((appsGlobal != null)\r
+        && (appsGlobal.getDataReference() != null || appsGlobal.getData() != null))\r
+      return true;\r
+    return false;\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#hasUserAppdata()\r
+   */\r
+  public boolean hasUserAppdata() {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    extractAppData();\r
+    // LATER - check validity of a DataReference before we return true\r
+    // TODO: return true if usersData is null but we have already written a new\r
+    // data stream\r
+    if ((usersData != null)\r
+        && (usersData.getDataReference() != null || usersData.getData() != null))\r
+      return true;\r
+    return false;\r
+  }\r
+\r
+  private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {\r
+    try {\r
+      if (data != null && data.length > 0)\r
+        ostrm.write(data);\r
+      ostrm.closeEntry();\r
+      return true;\r
+    } catch (Exception e) {\r
+      log.error("Serious! - IO error when writing AppDataStream to file "\r
+          + newAppData.sessionFile, e);\r
+    }\r
+    return false;\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#setClientAppdata(byte[])\r
+   */\r
+  public void setClientAppdata(byte[] data) {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    _getAppdataOutputStream(false);\r
+    if (newAppDataStream == null) {\r
+      // LATER: define an exception for this ? - operation may fail even if file\r
+      // i/o not involved\r
+      log\r
+          .error("Serious! - couldn't open new AppDataStream in session directory "\r
+              + clientdoc.sclient._session.sessionDir);\r
+    } else {\r
+      _writeAppDataStream(newAppDataStream, data);\r
+      // LATER: deal with error case - do we make session read only, or what ?\r
+    }\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see uk.ac.vamsas.client.IClientAppdata#setUserAppdata(byte[])\r
+   */\r
+  public void setUserAppdata(byte[] data) {\r
+    if (clientdoc == null)\r
+      throw new Error(\r
+          "Implementation error, Improperly initialized SimpleClientAppdata.");\r
+    _getAppdataOutputStream(true);\r
+    if (newUserDataStream == null) {\r
+      // LATER: define an exception for this ? - operation may fail even if file\r
+      // i/o not involved\r
+      log\r
+          .error("Serious! - couldn't open new UserDataStream in session directory "\r
+              + clientdoc.sclient._session.sessionDir);\r
+    } else {\r
+      _writeAppDataStream(newUserDataStream, data);\r
+      // LATER: deal with error case - do we make session read only, or what ?\r
+    }\r
+  }\r
+\r
+  /**\r
+   * flush and close outstanding output streams. - do this before checking data\r
+   * length.\r
+   * \r
+   * @throws IOException\r
+   */\r
+  protected void closeForWriting() throws IOException {\r
+    if (newAppDataStream != null) {\r
+      newAppDataStream.flush();\r
+      newAppDataStream.closeEntry();\r
+      newAppDataStream.close();\r
+    }\r
+    if (newUserDataStream != null) {\r
+      newUserDataStream.flush();\r
+      newUserDataStream.closeEntry();\r
+      newUserDataStream.close();\r
+    }\r
+  }\r
+\r
+  /**\r
+   * \r
+   * @return true if any AppData blocks have to be updated in session Jar\r
+   */\r
+  protected boolean isModified() {\r
+    // LATER differentiate between core xml modification and Jar Entry\r
+    // modification.\r
+    if ((newAppData != null && newAppData.sessionFile.exists())\r
+        || (newUserData != null && newUserData.sessionFile.exists()))\r
+      return true;\r
+    return false;\r
+  }\r
+\r
+  /*\r
+   * (non-Javadoc)\r
+   * \r
+   * @see java.lang.Object#finalize()\r
+   */\r
+  protected void finalize() throws Throwable {\r
+    if (newAppDataStream != null) {\r
+      newAppDataStream = null;\r
+    }\r
+    if (newAppDataStream != null) {\r
+      newUserDataStream = null;\r
+    }\r
+    if (newAppData != null) {\r
+      newAppData.eraseExistence();\r
+      newAppData = null;\r
+    }\r
+    if (newUserData != null) {\r
+      newUserData.eraseExistence();\r
+      newUserData = null;\r
+    }\r
+    super.finalize();\r
+  }\r
+\r
+}\r