X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fuk%2Fac%2Fvamsas%2Fclient%2Fsimpleclient%2FSimpleClientAppdata.java;h=77811c5fadacfd5a2997d41dcc3a36d57bdab3a6;hb=1eea4b639911330e7cd65e17c5421cc584f0d22e;hp=ececd9edfe34610adfda330371b1ae51895569f2;hpb=917d7bbb140b99761a96008a44910bb2a10022f1;p=vamsas.git diff --git a/src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java b/src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java index ececd9e..77811c5 100644 --- a/src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java +++ b/src/uk/ac/vamsas/client/simpleclient/SimpleClientAppdata.java @@ -1,549 +1,694 @@ -/** - * - */ -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; - } - /** - * 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(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 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(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 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) { - 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(clientdoc.getVamsasDocument()); - // 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(clientdoc.getVamsasDocument()); - // 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(); - } - -} +/* + * This file is part of the Vamsas Client version 0.2. + * Copyright 2010 by Jim Procter, Iain Milne, Pierre Marguerite, + * Andrew Waterhouse and Dominik Lindner. + * + * Earlier versions have also been incorporated into Jalview version 2.4 + * since 2008, and TOPALi version 2 since 2007. + * + * The Vamsas Client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Vamsas Client is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the Vamsas Client. If not, see . + */ +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(); + } + +}