4 package org.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;
24 import org.vamsas.client.IClientAppdata;
25 import org.vamsas.objects.core.AppData;
26 import org.vamsas.objects.core.ApplicationData;
27 import org.vamsas.objects.core.User;
28 import org.vamsas.objects.utils.AppDataReference;
32 * Access interface to data chunks read from a VamsasArchiveReader stream
33 * (or byte buffer input stream) or written to a VamsasArchive stream.
34 * // TODO: get VamsasArchiveReader from sclient
36 public class SimpleClientAppdata implements IClientAppdata {
37 private static Log log = LogFactory.getLog(SimpleClientAppdata.class);
39 * has the session's document been accessed to get the AppData entrys?
41 protected boolean accessedDocument = false;
43 * has the user datablock been modified ?
44 * temporary file containing new user specific application data chunk
46 SessionFile newUserData=null;
47 JarOutputStream newUserDataStream = null;
49 * has the apps global datablock been modified ?
50 * temporary file containing new global application data chunk
52 SessionFile newAppData=null;
53 JarOutputStream newAppDataStream=null;
55 * set by extractAppData
57 protected ApplicationData appsGlobal = null;
59 * set by extractAppData
61 protected User usersData = null;
63 ClientDocument clientdoc;
66 * - accessed ClientAppdata
67 * - accessed UserAppdata
68 * => inputStream from embedded xml or jar entry of backup has been created
71 * => an output stream has been created and written to - or a data chunk has been written.
72 * - need flag for switching between embedded and jar entry mode ? - always write a jar entry for a stream.
73 * - need code for rewind and overwriting if the set*Appdata methods are called more than once.
74 * - need flags for streams to except a call to set*Appdata when an output stream exists and is open.
76 * @param clientdoc The ClientDocument instance that this IClientAppData is accessing
78 protected SimpleClientAppdata(ClientDocument clientdoc) {
79 if (clientdoc==null) {
80 log.fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
81 throw new Error("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
83 this.clientdoc = clientdoc;
86 * gets appropriate app data for the application, if it exists in this dataset
87 * Called by every accessor to ensure data has been retrieved from document.
89 private void extractAppData(org.vamsas.objects.core.VamsasDocument doc) {
91 log.debug("extractAppData called for null document object");
94 if (accessedDocument) {
97 Vector apldataset = AppDataReference.getUserandApplicationsData(
98 doc, clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());
99 accessedDocument = true;
100 if (apldataset!=null) {
101 if (apldataset.size()>0) {
102 AppData clientdat = (AppData) apldataset.get(0);
103 if (clientdat instanceof ApplicationData) {
104 appsGlobal = (ApplicationData) clientdat;
105 if (apldataset.size()>1) {
106 clientdat = (AppData) apldataset.get(1);
107 if (clientdat instanceof User) {
108 usersData = (User) clientdat;
110 if (apldataset.size()>2)
111 log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
114 log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName());
116 apldataset.removeAllElements(); // destroy references.
121 * LATER: generalize this for different low-level session implementations (it may not always be a Jar)
126 private JarInputStream getAppDataStream(AppData appdata, VamsasArchiveReader docreader) {
127 String entryRef = appdata.getDataReference();
128 if (entryRef!=null) {
129 log.debug("Resolving appData reference +"+entryRef);
130 InputStream entry = docreader.getAppdataStream(entryRef);
132 if (entry instanceof JarInputStream) {
133 return (JarInputStream) entry;
135 log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
138 log.debug("GetAppDataStream called for an AppData without a data reference.");
143 * yuk - size of buffer used for slurping appData JarEntry into a byte array.
145 private final int _TRANSFER_BUFFER=4096*4;
148 * Resolve AppData object to a byte array.
150 * @param archiveReader
151 * @return null or the application data as a byte array
153 private byte[] getAppDataAsByteArray(AppData appdata, VamsasArchiveReader docreader) {
154 if (appdata.getData()==null) {
155 if (docreader==null) {
156 log.warn("Silently failing getAppDataAsByteArray with null docreader.",new Exception());
159 // resolve and load data
160 JarInputStream entry = getAppDataStream(appdata, docreader);
161 ByteArrayOutputStream bytes = new ByteArrayOutputStream();
163 byte buff[] = new byte[_TRANSFER_BUFFER];
165 while (entry.available()>0) {
166 int len = entry.read(buff, olen, _TRANSFER_BUFFER);
167 bytes.write(buff, 0, len);
171 } catch (Exception e) {
172 log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
174 if (bytes.size()>0) {
175 // LATER: deal with probable OutOfMemoryErrors here
176 log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference());
177 byte data[] = bytes.toByteArray();
183 log.debug("Returning inline AppData block for "+appdata.getVorbaId());
184 return appdata.getData();
188 * internal method for getting a DataInputStream from an AppData object.
191 * @return data in object or null if no data is accessible
193 private DataInput getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
194 if (appdata!=null && docreader!=null) {
195 String entryRef = appdata.getDataReference();
196 if (entryRef!=null) {
197 log.debug("Resolving AppData reference for "+entryRef);
198 InputStream jstrm = docreader.getAppdataStream(entryRef);
200 return new AppDataInputStream(jstrm);
202 log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
206 // return a byteArray input stream
207 byte[] data=appdata.getData();
209 ByteArrayInputStream stream = new ByteArrayInputStream(data);
210 return new DataInputStream(stream);
212 log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
217 log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
223 * internal method for getting ByteArray from AppData object
224 * @param clientOrUser - true for returning userData, otherwise return Client AppData.
225 * @return null or byte array
227 private byte[] _getappdataByteArray(boolean clientOrUser) {
229 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
233 appdName = "Client's Appdata";
235 appdName = "User's Appdata";
237 log.debug("getting "+appdName+" as a byte array");
238 extractAppData(clientdoc.getVamsasDocument());
246 log.debug("Trying to resolve "+appdName+" object to byte array.");
247 data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
250 log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
256 * common method for Client and User AppData->InputStream accessor
257 * @param clientOrUser - the appData to resolve - false for client, true for user appdata.
258 * @return null or the DataInputStream desired.
260 private DataInput _getappdataInputStream(boolean clientOrUser) {
262 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
265 appdName = "Client's Appdata";
267 appdName = "User's Appdata";
269 if (log.isDebugEnabled())
270 log.debug("getting "+appdName+" as an input stream.");
271 extractAppData(clientdoc.getVamsasDocument());
279 log.debug("Trying to resolve ClientAppdata object to an input stream.");
280 return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
282 log.debug("getClientInputStream returning null.");
286 * @see org.vamsas.client.IClientAppdata#getClientAppdata()
288 public byte[] getClientAppdata() {
289 return _getappdataByteArray(false);
292 * @see org.vamsas.client.IClientAppdata#getClientInputStream()
294 public DataInput getClientInputStream() {
295 return _getappdataInputStream(false);
299 * @see org.vamsas.client.IClientAppdata#getUserAppdata()
301 public byte[] getUserAppdata() {
302 return _getappdataByteArray(true);
306 * @see org.vamsas.client.IClientAppdata#getUserInputStream()
308 public DataInput getUserInputStream() {
309 return _getappdataInputStream(true);
312 * methods for writing new AppData entries.
314 private DataOutput _getAppdataOutputStream(boolean clientOrUser) {
316 SessionFile apdfile=null;
318 apdname = "clientAppData";
319 apdfile = newAppData;
321 apdname = "userAppData";
322 apdfile = newUserData;
326 apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
327 log.debug("Successfully made temp appData file for "+apdname);
329 // truncate to remove existing data.
330 apdfile.fileLock.getRaFile().setLength(0);
331 log.debug("Successfully truncated existing temp appData for "+apdname);
333 } catch (Exception e) {
334 log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
336 // we do not make another file for the new entry if one exists already
338 newAppData = apdfile;
340 newUserData = apdfile;
344 // LATER: Refactor these local AppDatastream IO stuff to their own class.
345 JarOutputStream dstrm =
346 new JarOutputStream(apdfile.fileLock.getBufferedOutputStream(true));
348 newAppDataStream = dstrm;
350 newUserDataStream = dstrm;
352 dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
353 // 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
354 return new AppDataOutputStream(dstrm);
356 catch (Exception e) {
357 log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
359 // tidy up and return null
360 apdfile.unlockFile();
364 * copy data from the appData jar file to an appropriately
365 * referenced jar or Data entry for the given ApplicationData
366 * Assumes the JarFile is properly closed.
367 * @param vdoc session Document handler
368 * @param appd the AppData whose block is being updated
369 * @param apdjar the new data in a Jar written by this class
371 protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd, SessionFile apdjar) throws IOException {
372 if (apdjar==null || apdjar.sessionFile==null || !apdjar.sessionFile.exists()) {
373 throw new IOException("No temporary Appdata to recover and transfer.");
376 log.fatal("FATAL! NO DOCUMENT TO WRITE TO!");
377 throw new IOException("FATAL! NO DOCUMENT TO WRITE TO!");
379 log.debug("Recovering AppData entry from "+apdjar.sessionFile);
380 JarInputStream istrm = new JarInputStream(apdjar.getBufferedInputStream(true));
382 while (istrm.available()>0 && (je=istrm.getNextJarEntry())!=null && !je.getName().equals("appData_entry.dat")) {
384 log.debug("Ignoring extraneous entry "+je.getName());
386 if (istrm.available()>0 && je!=null) {
387 log.debug("Found appData_entry.dat in Jar");
388 String ref = appd.getDataReference();
390 throw new IOException("Null AppData.DataReference passed.");
392 if (vdoc.writeAppdataFromStream(ref, istrm)) {
393 log.debug("Entry updated successfully.");
395 throw new IOException("writeAppdataFromStream did not return true - expect future badness."); // LATER - verify why this might occur.
398 throw new IOException("Couldn't find appData_entry.dat in temporary jar file "+apdjar.sessionFile.getAbsolutePath());
403 * @see org.vamsas.client.IClientAppdata#getClientOutputStream()
405 public DataOutput getClientOutputStream() {
407 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
408 if (log.isDebugEnabled())
409 log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());
410 return _getAppdataOutputStream(false);
414 * @see org.vamsas.client.IClientAppdata#getUserOutputStream()
416 public DataOutput getUserOutputStream() {
418 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
419 if (log.isDebugEnabled())
420 log.debug("trying to getUserOutputStream for ("
421 +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());
422 return _getAppdataOutputStream(true);
426 * @see org.vamsas.client.IClientAppdata#hasClientAppdata()
428 public boolean hasClientAppdata() {
430 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
431 extractAppData(clientdoc.getVamsasDocument());
432 // LATER - check validity of a DataReference before we return true
433 if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
439 * @see org.vamsas.client.IClientAppdata#hasUserAppdata()
441 public boolean hasUserAppdata() {
443 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
444 extractAppData(clientdoc.getVamsasDocument());
445 // LATER - check validity of a DataReference before we return true
446 if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
450 private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
452 if (data!=null && data.length>0)
457 catch (Exception e) {
458 log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
463 * @see org.vamsas.client.IClientAppdata#setClientAppdata(byte[])
465 public void setClientAppdata(byte[] data) {
467 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
468 _getAppdataOutputStream(false);
469 if (newAppDataStream==null) {
470 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
471 log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir);
473 _writeAppDataStream(newAppDataStream, data);
474 // LATER: deal with error case - do we make session read only, or what ?
479 * @see org.vamsas.client.IClientAppdata#setUserAppdata(byte[])
481 public void setUserAppdata(byte[] data) {
483 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
484 _getAppdataOutputStream(true);
485 if (newUserDataStream==null) {
486 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
487 log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir);
489 _writeAppDataStream(newUserDataStream, data);
490 // LATER: deal with error case - do we make session read only, or what ?
494 * flush and close outstanding output streams.
495 * - do this before checking data length.
496 * @throws IOException
498 protected void closeForWriting() throws IOException {
499 if (newAppDataStream!=null) {
500 newAppDataStream.flush();
501 newAppDataStream.closeEntry();
502 newAppDataStream.close();
504 if (newUserDataStream!=null) {
505 newUserDataStream.flush();
506 newUserDataStream.closeEntry();
507 newUserDataStream.close();
514 * @return true if any AppData blocks have to be updated in session Jar
516 protected boolean isModified() {
517 // LATER differentiate between core xml modification and Jar Entry modification.
518 if (newAppData.sessionFile.exists() || newUserData.sessionFile.exists())
523 * @see java.lang.Object#finalize()
525 protected void finalize() throws Throwable {
526 if (newAppDataStream!=null) {
527 newAppDataStream = null;
529 if (newAppDataStream!=null) {
530 newUserDataStream = null;
532 if (newAppData!=null) {
533 newAppData.eraseExistence();
536 if (newUserData!=null) {
537 newUserData.eraseExistence();