4 package uk.ac.vamsas.client.simpleclient;
6 import java.io.BufferedInputStream;
7 import java.io.BufferedOutputStream;
8 import java.io.ByteArrayInputStream;
9 import java.io.ByteArrayOutputStream;
10 import java.io.DataInput;
11 import java.io.DataInputStream;
12 import java.io.DataOutput;
13 import java.io.DataOutputStream;
14 import java.io.FileInputStream;
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.util.Vector;
18 import java.util.jar.JarEntry;
19 import java.util.jar.JarInputStream;
20 import java.util.jar.JarOutputStream;
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
25 import uk.ac.vamsas.client.AppDataInputStream;
26 import uk.ac.vamsas.client.AppDataOutputStream;
27 import uk.ac.vamsas.client.IClientAppdata;
28 import uk.ac.vamsas.objects.core.AppData;
29 import uk.ac.vamsas.objects.core.ApplicationData;
30 import uk.ac.vamsas.objects.core.User;
31 import uk.ac.vamsas.objects.utils.AppDataReference;
35 * Access interface to data chunks read from a VamsasArchiveReader stream
36 * (or byte buffer input stream) or written to a VamsasArchive stream.
37 * // TODO: get VamsasArchiveReader from sclient
39 public class SimpleClientAppdata implements IClientAppdata {
40 private static Log log = LogFactory.getLog(SimpleClientAppdata.class);
42 * has the session's document been accessed to get the AppData entrys?
44 protected boolean accessedDocument = false;
46 * has the user datablock been modified ?
47 * temporary file containing new user specific application data chunk
49 SessionFile newUserData=null;
50 JarOutputStream newUserDataStream = null;
52 * has the apps global datablock been modified ?
53 * temporary file containing new global application data chunk
55 SessionFile newAppData=null;
56 JarOutputStream newAppDataStream=null;
58 * set by extractAppData
60 protected ApplicationData appsGlobal = null;
62 * set by extractAppData
64 protected User usersData = null;
66 ClientDocument clientdoc;
69 * - accessed ClientAppdata
70 * - accessed UserAppdata
71 * => inputStream from embedded xml or jar entry of backup has been created
74 * => an output stream has been created and written to - or a data chunk has been written.
75 * - need flag for switching between embedded and jar entry mode ? - always write a jar entry for a stream.
76 * - need code for rewind and overwriting if the set*Appdata methods are called more than once.
77 * - need flags for streams to except a call to set*Appdata when an output stream exists and is open.
79 * @param clientdoc The ClientDocument instance that this IClientAppData is accessing
81 protected SimpleClientAppdata(ClientDocument clientdoc) {
82 if (clientdoc==null) {
83 log.fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
84 throw new Error("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
86 this.clientdoc = clientdoc;
89 * ensures that the appData information for this client
90 * instance has been extracted from the vamsas document provided by clientdoc.
92 private void extractAppData()
94 if (!accessedDocument)
95 _extractAppData(clientdoc.getVamsasDocument());
98 * gets appropriate app data for the application, if it exists in this dataset
99 * Called by every accessor to ensure data has been retrieved from document.
101 private void _extractAppData(uk.ac.vamsas.objects.core.VamsasDocument doc) {
103 log.debug("extractAppData called for null document object");
106 if (accessedDocument) {
109 Vector apldataset = AppDataReference.getUserandApplicationsData(
110 doc, clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());
111 accessedDocument = true;
112 if (apldataset!=null) {
113 if (apldataset.size()>0) {
114 AppData clientdat = (AppData) apldataset.get(0);
115 if (clientdat instanceof ApplicationData) {
116 appsGlobal = (ApplicationData) clientdat;
117 if (apldataset.size()>1) {
118 clientdat = (AppData) apldataset.get(1);
119 if (clientdat instanceof User) {
120 usersData = (User) clientdat;
122 if (apldataset.size()>2)
123 log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
126 log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName());
128 apldataset.removeAllElements(); // destroy references.
133 * LATER: generalize this for different low-level session implementations (it may not always be a Jar)
138 private InputStream getAppDataStream(AppData appdata, VamsasArchiveReader docreader) {
139 String entryRef = appdata.getDataReference();
140 if (entryRef!=null) {
141 log.debug("Resolving appData reference +"+entryRef);
142 InputStream entry = docreader.getAppdataStream(entryRef);
145 // log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
148 log.debug("GetAppDataStream called for an AppData without a data reference.");
153 * yuk - size of buffer used for slurping appData JarEntry into a byte array.
155 private final int _TRANSFER_BUFFER=4096*4;
158 * Resolve AppData object to a byte array.
160 * @param archiveReader
161 * @return null or the application data as a byte array
163 private byte[] getAppDataAsByteArray(AppData appdata, VamsasArchiveReader docreader) {
164 if (appdata.getData()==null) {
165 if (docreader==null) {
166 log.warn("Silently failing getAppDataAsByteArray with null docreader.",new Exception());
169 // resolve and load data
170 InputStream entry = getAppDataStream(appdata, docreader);
171 ByteArrayOutputStream bytes = new ByteArrayOutputStream();
173 byte buff[] = new byte[_TRANSFER_BUFFER];
175 while (entry!=null && entry.available()>0) {
176 int len = entry.read(buff, 0, _TRANSFER_BUFFER);
178 { bytes.write(buff, 0, len);
183 } catch (Exception e) {
184 log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
186 if (bytes.size()>0) {
187 // LATER: deal with probable OutOfMemoryErrors here
188 log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference());
189 byte data[] = bytes.toByteArray();
195 log.debug("Returning inline AppData block for "+appdata.getVorbaId());
196 return appdata.getData();
200 * internal method for getting a DataInputStream from an AppData object.
203 * @return data in object or null if no data is accessible
205 private AppDataInputStream getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
206 if (appdata!=null && docreader!=null) {
207 String entryRef = appdata.getDataReference();
208 if (entryRef!=null) {
209 log.debug("Resolving AppData reference for "+entryRef);
210 InputStream jstrm = docreader.getAppdataStream(entryRef);
212 return new AppDataInputStream(jstrm);
214 log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
218 // return a byteArray input stream
219 byte[] data=appdata.getData();
221 ByteArrayInputStream stream = new ByteArrayInputStream(data);
222 return new AppDataInputStream(stream);
224 log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
229 log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
235 * internal method for getting ByteArray from AppData object
236 * @param clientOrUser - true for returning userData, otherwise return Client AppData.
237 * @return null or byte array
239 private byte[] _getappdataByteArray(boolean clientOrUser) {
241 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
245 appdName = "Client's Appdata";
247 appdName = "User's Appdata";
249 log.debug("getting "+appdName+" as a byte array");
258 log.debug("Trying to resolve "+appdName+" object to byte array.");
259 data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
262 log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
268 * common method for Client and User AppData->InputStream accessor
269 * @param clientOrUser - the appData to resolve - false for client, true for user appdata.
270 * @return null or the DataInputStream desired.
272 private AppDataInputStream _getappdataInputStream(boolean clientOrUser) {
274 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
277 appdName = "Client's Appdata";
279 appdName = "User's Appdata";
281 if (log.isDebugEnabled())
282 log.debug("getting "+appdName+" as an input stream.");
291 log.debug("Trying to resolve ClientAppdata object to an input stream.");
292 return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
294 log.debug("getClientInputStream returning null.");
298 * @see uk.ac.vamsas.client.IClientAppdata#getClientAppdata()
300 public byte[] getClientAppdata() {
301 return _getappdataByteArray(false);
304 * @see uk.ac.vamsas.client.IClientAppdata#getClientInputStream()
306 public AppDataInputStream getClientInputStream() {
307 return _getappdataInputStream(false);
311 * @see uk.ac.vamsas.client.IClientAppdata#getUserAppdata()
313 public byte[] getUserAppdata() {
314 return _getappdataByteArray(true);
318 * @see uk.ac.vamsas.client.IClientAppdata#getUserInputStream()
320 public AppDataInputStream getUserInputStream() {
321 return _getappdataInputStream(true);
324 * methods for writing new AppData entries.
326 private AppDataOutputStream _getAppdataOutputStream(boolean clientOrUser) {
327 // Must access document to get any existing references
330 SessionFile apdfile=null;
332 apdname = "clientAppData";
333 apdfile = newAppData;
335 apdname = "userAppData";
336 apdfile = newUserData;
340 apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
341 log.debug("Successfully made temp appData file for "+apdname);
343 // truncate to remove existing data.
344 apdfile.fileLock.getRaFile().setLength(0);
345 log.debug("Successfully truncated existing temp appData for "+apdname);
347 } catch (Exception e) {
348 log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
350 // we do not make another file for the new entry if one exists already
352 newAppData = apdfile;
354 newUserData = apdfile;
358 // LATER: Refactor these local AppDatastream IO stuff to their own class.
359 JarOutputStream dstrm =
360 new JarOutputStream(apdfile.fileLock.getBufferedOutputStream(true));
362 newAppDataStream = dstrm;
364 newUserDataStream = dstrm;
366 dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
367 // 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
368 return new AppDataOutputStream(dstrm);
370 catch (Exception e) {
371 log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
373 // tidy up and return null
374 apdfile.unlockFile();
378 * copy data from the appData jar file to an appropriately
379 * referenced jar or Data entry for the given ApplicationData
380 * Assumes the JarFile is properly closed.
381 * @param vdoc session Document handler
382 * @param appd the AppData whose block is being updated
383 * @param apdjar the new data in a Jar written by this class
385 protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd, SessionFile apdjar) throws IOException {
386 if (apdjar==null || apdjar.sessionFile==null || !apdjar.sessionFile.exists()) {
387 throw new IOException("No temporary Appdata to recover and transfer.");
390 log.fatal("FATAL! NO DOCUMENT TO WRITE TO!");
391 throw new IOException("FATAL! NO DOCUMENT TO WRITE TO!");
393 log.debug("Recovering AppData entry from "+apdjar.sessionFile);
394 JarInputStream istrm = new JarInputStream(apdjar.getBufferedInputStream(true));
396 while (istrm.available()>0 && (je=istrm.getNextJarEntry())!=null && !je.getName().equals("appData_entry.dat")) {
398 log.debug("Ignoring extraneous entry "+je.getName());
400 if (istrm.available()>0 && je!=null) {
401 log.debug("Found appData_entry.dat in Jar");
402 String ref = appd.getDataReference();
404 throw new IOException("Null AppData.DataReference passed.");
406 log.debug("Writing appData_entry.dat as "+ref);
407 if (vdoc.writeAppdataFromStream(ref, istrm)) {
408 log.debug("Entry updated successfully.");
410 throw new IOException("writeAppdataFromStream did not return true - expect future badness."); // LATER - verify why this might occur.
413 throw new IOException("Couldn't find appData_entry.dat in temporary jar file "+apdjar.sessionFile.getAbsolutePath());
418 * @see uk.ac.vamsas.client.IClientAppdata#getClientOutputStream()
420 public AppDataOutputStream getClientOutputStream() {
422 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
423 if (log.isDebugEnabled())
424 log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());
425 return _getAppdataOutputStream(false);
429 * @see uk.ac.vamsas.client.IClientAppdata#getUserOutputStream()
431 public AppDataOutputStream getUserOutputStream() {
433 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
434 if (log.isDebugEnabled())
435 log.debug("trying to getUserOutputStream for ("
436 +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());
437 return _getAppdataOutputStream(true);
441 * @see uk.ac.vamsas.client.IClientAppdata#hasClientAppdata()
443 public boolean hasClientAppdata() {
445 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
447 // LATER - check validity of a DataReference before we return true
448 // TODO: return true if usersData is null but we have already written a new data stream
449 if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
455 * @see uk.ac.vamsas.client.IClientAppdata#hasUserAppdata()
457 public boolean hasUserAppdata() {
459 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
461 // LATER - check validity of a DataReference before we return true
462 // TODO: return true if usersData is null but we have already written a new data stream
463 if ((usersData!=null) && (usersData.getDataReference()!=null || usersData.getData()!=null))
467 private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
469 if (data!=null && data.length>0)
474 catch (Exception e) {
475 log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
480 * @see uk.ac.vamsas.client.IClientAppdata#setClientAppdata(byte[])
482 public void setClientAppdata(byte[] data) {
484 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
485 _getAppdataOutputStream(false);
486 if (newAppDataStream==null) {
487 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
488 log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir);
490 _writeAppDataStream(newAppDataStream, data);
491 // LATER: deal with error case - do we make session read only, or what ?
496 * @see uk.ac.vamsas.client.IClientAppdata#setUserAppdata(byte[])
498 public void setUserAppdata(byte[] data) {
500 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
501 _getAppdataOutputStream(true);
502 if (newUserDataStream==null) {
503 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
504 log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir);
506 _writeAppDataStream(newUserDataStream, data);
507 // LATER: deal with error case - do we make session read only, or what ?
511 * flush and close outstanding output streams.
512 * - do this before checking data length.
513 * @throws IOException
515 protected void closeForWriting() throws IOException {
516 if (newAppDataStream!=null) {
517 newAppDataStream.flush();
518 newAppDataStream.closeEntry();
519 newAppDataStream.close();
521 if (newUserDataStream!=null) {
522 newUserDataStream.flush();
523 newUserDataStream.closeEntry();
524 newUserDataStream.close();
531 * @return true if any AppData blocks have to be updated in session Jar
533 protected boolean isModified() {
534 // LATER differentiate between core xml modification and Jar Entry modification.
535 if ((newAppData!=null && newAppData.sessionFile.exists()) || (newUserData!=null && newUserData.sessionFile.exists()))
540 * @see java.lang.Object#finalize()
542 protected void finalize() throws Throwable {
543 if (newAppDataStream!=null) {
544 newAppDataStream = null;
546 if (newAppDataStream!=null) {
547 newUserDataStream = null;
549 if (newAppData!=null) {
550 newAppData.eraseExistence();
553 if (newUserData!=null) {
554 newUserData.eraseExistence();