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 * gets appropriate app data for the application, if it exists in this dataset
90 * Called by every accessor to ensure data has been retrieved from document.
92 private void extractAppData(uk.ac.vamsas.objects.core.VamsasDocument doc) {
94 log.debug("extractAppData called for null document object");
97 if (accessedDocument) {
100 Vector apldataset = AppDataReference.getUserandApplicationsData(
101 doc, clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());
102 accessedDocument = true;
103 if (apldataset!=null) {
104 if (apldataset.size()>0) {
105 AppData clientdat = (AppData) apldataset.get(0);
106 if (clientdat instanceof ApplicationData) {
107 appsGlobal = (ApplicationData) clientdat;
108 if (apldataset.size()>1) {
109 clientdat = (AppData) apldataset.get(1);
110 if (clientdat instanceof User) {
111 usersData = (User) clientdat;
113 if (apldataset.size()>2)
114 log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
117 log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName());
119 apldataset.removeAllElements(); // destroy references.
124 * LATER: generalize this for different low-level session implementations (it may not always be a Jar)
129 private InputStream getAppDataStream(AppData appdata, VamsasArchiveReader docreader) {
130 String entryRef = appdata.getDataReference();
131 if (entryRef!=null) {
132 log.debug("Resolving appData reference +"+entryRef);
133 InputStream entry = docreader.getAppdataStream(entryRef);
136 // log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
139 log.debug("GetAppDataStream called for an AppData without a data reference.");
144 * yuk - size of buffer used for slurping appData JarEntry into a byte array.
146 private final int _TRANSFER_BUFFER=4096*4;
149 * Resolve AppData object to a byte array.
151 * @param archiveReader
152 * @return null or the application data as a byte array
154 private byte[] getAppDataAsByteArray(AppData appdata, VamsasArchiveReader docreader) {
155 if (appdata.getData()==null) {
156 if (docreader==null) {
157 log.warn("Silently failing getAppDataAsByteArray with null docreader.",new Exception());
160 // resolve and load data
161 InputStream entry = getAppDataStream(appdata, docreader);
162 ByteArrayOutputStream bytes = new ByteArrayOutputStream();
164 byte buff[] = new byte[_TRANSFER_BUFFER];
166 while (entry!=null && entry.available()>0) {
167 int len = entry.read(buff, 0, _TRANSFER_BUFFER);
169 { bytes.write(buff, 0, len);
174 } catch (Exception e) {
175 log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
177 if (bytes.size()>0) {
178 // LATER: deal with probable OutOfMemoryErrors here
179 log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference());
180 byte data[] = bytes.toByteArray();
186 log.debug("Returning inline AppData block for "+appdata.getVorbaId());
187 return appdata.getData();
191 * internal method for getting a DataInputStream from an AppData object.
194 * @return data in object or null if no data is accessible
196 private AppDataInputStream getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
197 if (appdata!=null && docreader!=null) {
198 String entryRef = appdata.getDataReference();
199 if (entryRef!=null) {
200 log.debug("Resolving AppData reference for "+entryRef);
201 InputStream jstrm = docreader.getAppdataStream(entryRef);
203 return new AppDataInputStream(jstrm);
205 log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
209 // return a byteArray input stream
210 byte[] data=appdata.getData();
212 ByteArrayInputStream stream = new ByteArrayInputStream(data);
213 return new AppDataInputStream(stream);
215 log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
220 log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
226 * internal method for getting ByteArray from AppData object
227 * @param clientOrUser - true for returning userData, otherwise return Client AppData.
228 * @return null or byte array
230 private byte[] _getappdataByteArray(boolean clientOrUser) {
232 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
236 appdName = "Client's Appdata";
238 appdName = "User's Appdata";
240 log.debug("getting "+appdName+" as a byte array");
241 extractAppData(clientdoc.getVamsasDocument());
249 log.debug("Trying to resolve "+appdName+" object to byte array.");
250 data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
253 log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
259 * common method for Client and User AppData->InputStream accessor
260 * @param clientOrUser - the appData to resolve - false for client, true for user appdata.
261 * @return null or the DataInputStream desired.
263 private AppDataInputStream _getappdataInputStream(boolean clientOrUser) {
265 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
268 appdName = "Client's Appdata";
270 appdName = "User's Appdata";
272 if (log.isDebugEnabled())
273 log.debug("getting "+appdName+" as an input stream.");
274 extractAppData(clientdoc.getVamsasDocument());
282 log.debug("Trying to resolve ClientAppdata object to an input stream.");
283 return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
285 log.debug("getClientInputStream returning null.");
289 * @see uk.ac.vamsas.client.IClientAppdata#getClientAppdata()
291 public byte[] getClientAppdata() {
292 return _getappdataByteArray(false);
295 * @see uk.ac.vamsas.client.IClientAppdata#getClientInputStream()
297 public AppDataInputStream getClientInputStream() {
298 return _getappdataInputStream(false);
302 * @see uk.ac.vamsas.client.IClientAppdata#getUserAppdata()
304 public byte[] getUserAppdata() {
305 return _getappdataByteArray(true);
309 * @see uk.ac.vamsas.client.IClientAppdata#getUserInputStream()
311 public AppDataInputStream getUserInputStream() {
312 return _getappdataInputStream(true);
315 * methods for writing new AppData entries.
317 private AppDataOutputStream _getAppdataOutputStream(boolean clientOrUser) {
319 SessionFile apdfile=null;
321 apdname = "clientAppData";
322 apdfile = newAppData;
324 apdname = "userAppData";
325 apdfile = newUserData;
329 apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
330 log.debug("Successfully made temp appData file for "+apdname);
332 // truncate to remove existing data.
333 apdfile.fileLock.getRaFile().setLength(0);
334 log.debug("Successfully truncated existing temp appData for "+apdname);
336 } catch (Exception e) {
337 log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
339 // we do not make another file for the new entry if one exists already
341 newAppData = apdfile;
343 newUserData = apdfile;
347 // LATER: Refactor these local AppDatastream IO stuff to their own class.
348 JarOutputStream dstrm =
349 new JarOutputStream(apdfile.fileLock.getBufferedOutputStream(true));
351 newAppDataStream = dstrm;
353 newUserDataStream = dstrm;
355 dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
356 // 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
357 return new AppDataOutputStream(dstrm);
359 catch (Exception e) {
360 log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
362 // tidy up and return null
363 apdfile.unlockFile();
367 * copy data from the appData jar file to an appropriately
368 * referenced jar or Data entry for the given ApplicationData
369 * Assumes the JarFile is properly closed.
370 * @param vdoc session Document handler
371 * @param appd the AppData whose block is being updated
372 * @param apdjar the new data in a Jar written by this class
374 protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd, SessionFile apdjar) throws IOException {
375 if (apdjar==null || apdjar.sessionFile==null || !apdjar.sessionFile.exists()) {
376 throw new IOException("No temporary Appdata to recover and transfer.");
379 log.fatal("FATAL! NO DOCUMENT TO WRITE TO!");
380 throw new IOException("FATAL! NO DOCUMENT TO WRITE TO!");
382 log.debug("Recovering AppData entry from "+apdjar.sessionFile);
383 JarInputStream istrm = new JarInputStream(apdjar.getBufferedInputStream(true));
385 while (istrm.available()>0 && (je=istrm.getNextJarEntry())!=null && !je.getName().equals("appData_entry.dat")) {
387 log.debug("Ignoring extraneous entry "+je.getName());
389 if (istrm.available()>0 && je!=null) {
390 log.debug("Found appData_entry.dat in Jar");
391 String ref = appd.getDataReference();
393 throw new IOException("Null AppData.DataReference passed.");
395 log.debug("Writing appData_entry.dat as "+ref);
396 if (vdoc.writeAppdataFromStream(ref, istrm)) {
397 log.debug("Entry updated successfully.");
399 throw new IOException("writeAppdataFromStream did not return true - expect future badness."); // LATER - verify why this might occur.
402 throw new IOException("Couldn't find appData_entry.dat in temporary jar file "+apdjar.sessionFile.getAbsolutePath());
407 * @see uk.ac.vamsas.client.IClientAppdata#getClientOutputStream()
409 public AppDataOutputStream getClientOutputStream() {
411 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
412 if (log.isDebugEnabled())
413 log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());
414 return _getAppdataOutputStream(false);
418 * @see uk.ac.vamsas.client.IClientAppdata#getUserOutputStream()
420 public AppDataOutputStream getUserOutputStream() {
422 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
423 if (log.isDebugEnabled())
424 log.debug("trying to getUserOutputStream for ("
425 +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());
426 return _getAppdataOutputStream(true);
430 * @see uk.ac.vamsas.client.IClientAppdata#hasClientAppdata()
432 public boolean hasClientAppdata() {
434 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
435 extractAppData(clientdoc.getVamsasDocument());
436 // LATER - check validity of a DataReference before we return true
437 // TODO: return true if usersData is null but we have already written a new data stream
438 if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
444 * @see uk.ac.vamsas.client.IClientAppdata#hasUserAppdata()
446 public boolean hasUserAppdata() {
448 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
449 extractAppData(clientdoc.getVamsasDocument());
450 // LATER - check validity of a DataReference before we return true
451 // TODO: return true if usersData is null but we have already written a new data stream
452 if ((usersData!=null) && (usersData.getDataReference()!=null || usersData.getData()!=null))
456 private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
458 if (data!=null && data.length>0)
463 catch (Exception e) {
464 log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
469 * @see uk.ac.vamsas.client.IClientAppdata#setClientAppdata(byte[])
471 public void setClientAppdata(byte[] data) {
473 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
474 _getAppdataOutputStream(false);
475 if (newAppDataStream==null) {
476 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
477 log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir);
479 _writeAppDataStream(newAppDataStream, data);
480 // LATER: deal with error case - do we make session read only, or what ?
485 * @see uk.ac.vamsas.client.IClientAppdata#setUserAppdata(byte[])
487 public void setUserAppdata(byte[] data) {
489 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
490 _getAppdataOutputStream(true);
491 if (newUserDataStream==null) {
492 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
493 log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir);
495 _writeAppDataStream(newUserDataStream, data);
496 // LATER: deal with error case - do we make session read only, or what ?
500 * flush and close outstanding output streams.
501 * - do this before checking data length.
502 * @throws IOException
504 protected void closeForWriting() throws IOException {
505 if (newAppDataStream!=null) {
506 newAppDataStream.flush();
507 newAppDataStream.closeEntry();
508 newAppDataStream.close();
510 if (newUserDataStream!=null) {
511 newUserDataStream.flush();
512 newUserDataStream.closeEntry();
513 newUserDataStream.close();
520 * @return true if any AppData blocks have to be updated in session Jar
522 protected boolean isModified() {
523 // LATER differentiate between core xml modification and Jar Entry modification.
524 if ((newAppData!=null && newAppData.sessionFile.exists()) || (newUserData!=null && newUserData.sessionFile.exists()))
529 * @see java.lang.Object#finalize()
531 protected void finalize() throws Throwable {
532 if (newAppDataStream!=null) {
533 newAppDataStream = null;
535 if (newAppDataStream!=null) {
536 newUserDataStream = null;
538 if (newAppData!=null) {
539 newAppData.eraseExistence();
542 if (newUserData!=null) {
543 newUserData.eraseExistence();