X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Forg%2Fvamsas%2Fclient%2Fsimpleclient%2FSimpleClientAppdata.java;h=a2d89125f6c85ff122aac5425543607a7f02f781;hb=05cfd5783f5307bbcedb0b9205cebef11dae22df;hp=9f6b27f94bf074f503d4131c1db4eefee0795bc5;hpb=235e04a76494db78a018b52e8ad2aa74515b3d85;p=vamsas.git diff --git a/src/org/vamsas/client/simpleclient/SimpleClientAppdata.java b/src/org/vamsas/client/simpleclient/SimpleClientAppdata.java index 9f6b27f..a2d8912 100644 --- a/src/org/vamsas/client/simpleclient/SimpleClientAppdata.java +++ b/src/org/vamsas/client/simpleclient/SimpleClientAppdata.java @@ -3,9 +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; @@ -19,10 +31,35 @@ import org.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 @@ -47,86 +84,355 @@ public class SimpleClientAppdata implements IClientAppdata { } /** * 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() { - org.vamsas.objects.core.VamsasDocument doc = null; + private void extractAppData(org.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()); - ApplicationData appsglobal=null; - User usersdata = null; + accessedDocument = true; if (apldataset!=null) { if (apldataset.size()>0) { AppData clientdat = (AppData) apldataset.get(0); if (clientdat instanceof ApplicationData) { - appsglobal = (ApplicationData) clientdat; + appsGlobal = (ApplicationData) clientdat; if (apldataset.size()>1) { clientdat = (AppData) apldataset.get(1); - if (clientdat instanceof User) - usersdata = (User) clientdat; + 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. } } } - - /* (non-Javadoc) - * @see org.vamsas.client.IClientAppdata#getClientAppdata() + /** + * LATER: generalize this for different low-level session implementations (it may not always be a Jar) + * @param appdata + * @param docreader + * @return */ - public byte[] getClientAppdata() { - // TODO Auto-generated method stub + private JarInputStream 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) { + if (entry instanceof JarInputStream) { + return (JarInputStream) 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; - /* (non-Javadoc) - * @see org.vamsas.client.IClientAppdata#getClientInputStream() + /** + * Resolve AppData object to a byte array. + * @param appdata + * @param archiveReader + * @return null or the application data as a byte array */ - public DataInput getClientInputStream() { - // TODO Auto-generated method stub + 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(); + try { + byte buff[] = new byte[_TRANSFER_BUFFER]; + int olen=0; + while (entry.available()>0) { + int len = entry.read(buff, olen, _TRANSFER_BUFFER); + 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 DataInput 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 DataInputStream(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; } - /* (non-Javadoc) - * @see org.vamsas.client.IClientAppdata#getClientOutputStream() + /** + * internal method for getting ByteArray from AppData object + * @param clientOrUser - true for returning userData, otherwise return Client AppData. + * @return null or byte array */ - public DataOutput getClientOutputStream() { - // TODO Auto-generated method stub + 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(clientdoc.getVamsasDocument()); + 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 DataInput _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(clientdoc.getVamsasDocument()); + 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 org.vamsas.client.IClientAppdata#getClientAppdata() + */ + public byte[] getClientAppdata() { + return _getappdataByteArray(false); + } + /* (non-Javadoc) + * @see org.vamsas.client.IClientAppdata#getClientInputStream() + */ + public DataInput getClientInputStream() { + return _getappdataInputStream(false); + } /* (non-Javadoc) * @see org.vamsas.client.IClientAppdata#getUserAppdata() */ public byte[] getUserAppdata() { - // TODO Auto-generated method stub - return null; + return _getappdataByteArray(true); } /* (non-Javadoc) * @see org.vamsas.client.IClientAppdata#getUserInputStream() */ public DataInput getUserInputStream() { - // TODO Auto-generated method stub + return _getappdataInputStream(true); + } + /** + * 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.rafile.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( + new BufferedOutputStream(new java.io.FileOutputStream(apdfile.sessionFile))); + 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(new BufferedInputStream(new FileInputStream(apdjar.sessionFile))); + 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."); + if (log.isDebugEnabled()) + log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn()); + return _getAppdataOutputStream(false); } /* (non-Javadoc) * @see org.vamsas.client.IClientAppdata#getUserOutputStream() */ public DataOutput getUserOutputStream() { - // TODO Auto-generated method stub - return null; + 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 org.vamsas.client.IClientAppdata#hasClientAppdata() */ public boolean hasClientAppdata() { - // TODO Auto-generated method stub + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + 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; } @@ -134,30 +440,104 @@ public class SimpleClientAppdata implements IClientAppdata { * @see org.vamsas.client.IClientAppdata#hasUserAppdata() */ public boolean hasUserAppdata() { - // TODO Auto-generated method stub + if (clientdoc==null) + throw new Error("Implementation error, Improperly initialized SimpleClientAppdata."); + 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) { - // TODO Auto-generated method stub - + 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 org.vamsas.client.IClientAppdata#setUserAppdata(byte[]) */ public void setUserAppdata(byte[] data) { - // TODO Auto-generated method stub - + 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.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(); }