4 package org.vamsas.client.simpleclient;
6 import java.io.BufferedOutputStream;
7 import java.io.ByteArrayInputStream;
8 import java.io.ByteArrayOutputStream;
9 import java.io.DataInput;
10 import java.io.DataInputStream;
11 import java.io.DataOutput;
12 import java.io.DataOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.util.Vector;
16 import java.util.jar.JarEntry;
17 import java.util.jar.JarInputStream;
18 import java.util.jar.JarOutputStream;
20 import org.apache.commons.logging.Log;
21 import org.apache.commons.logging.LogFactory;
22 import org.vamsas.client.IClientAppdata;
23 import org.vamsas.objects.core.AppData;
24 import org.vamsas.objects.core.ApplicationData;
25 import org.vamsas.objects.core.User;
26 import org.vamsas.objects.utils.AppDataReference;
30 * Access interface to data chunks read from a VamsasArchiveReader stream
31 * (or byte buffer input stream) or written to a VamsasArchive stream.
32 * // TODO: get VamsasArchiveReader from sclient
34 public class SimpleClientAppdata implements IClientAppdata {
35 private static Log log = LogFactory.getLog(SimpleClientAppdata.class);
37 * has the session's document been accessed to get the AppData entrys?
39 protected boolean accessedDocument = false;
41 * has the user datablock been modified ?
42 * temporary file containing new user specific application data chunk
44 SessionFile newUserData=null;
45 JarOutputStream newUserDataStream = null;
47 * has the apps global datablock been modified ?
48 * temporary file containing new global application data chunk
50 SessionFile newAppData=null;
51 JarOutputStream newAppDataStream=null;
53 * set by extractAppData
55 protected ApplicationData appsGlobal = null;
57 * set by extractAppData
59 protected User usersData = null;
61 ClientDocument clientdoc;
64 * - accessed ClientAppdata
65 * - accessed UserAppdata
66 * => inputStream from embedded xml or jar entry of backup has been created
69 * => an output stream has been created and written to - or a data chunk has been written.
70 * - need flag for switching between embedded and jar entry mode ? - always write a jar entry for a stream.
71 * - need code for rewind and overwriting if the set*Appdata methods are called more than once.
72 * - need flags for streams to except a call to set*Appdata when an output stream exists and is open.
74 * @param clientdoc The ClientDocument instance that this IClientAppData is accessing
76 protected SimpleClientAppdata(ClientDocument clientdoc) {
77 if (clientdoc==null) {
78 log.fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
79 throw new Error("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
81 this.clientdoc = clientdoc;
84 * gets appropriate app data for the application, if it exists in this dataset
85 * Called by every accessor to ensure data has been retrieved from document.
87 private void extractAppData(org.vamsas.objects.core.VamsasDocument doc) {
89 log.debug("extractAppData called for null document object");
92 if (accessedDocument) {
95 Vector apldataset = AppDataReference.getUserandApplicationsData(
96 doc, clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());
97 accessedDocument = true;
98 if (apldataset!=null) {
99 if (apldataset.size()>0) {
100 AppData clientdat = (AppData) apldataset.get(0);
101 if (clientdat instanceof ApplicationData) {
102 appsGlobal = (ApplicationData) clientdat;
103 if (apldataset.size()>1) {
104 clientdat = (AppData) apldataset.get(1);
105 if (clientdat instanceof User) {
106 usersData = (User) clientdat;
108 if (apldataset.size()>2)
109 log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
112 log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName());
114 apldataset.removeAllElements(); // destroy references.
119 * LATER: generalize this for different low-level session implementations (it may not always be a Jar)
124 private JarInputStream getAppDataStream(AppData appdata, VamsasArchiveReader docreader) {
125 String entryRef = appdata.getDataReference();
126 if (entryRef!=null) {
127 log.debug("Resolving appData reference +"+entryRef);
128 InputStream entry = docreader.getAppdataStream(entryRef);
130 if (entry instanceof JarInputStream) {
131 return (JarInputStream) entry;
133 log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
136 log.debug("GetAppDataStream called for an AppData without a data reference.");
141 * yuk - size of buffer used for slurping appData JarEntry into a byte array.
143 private final int _TRANSFER_BUFFER=4096*4;
146 * Resolve AppData object to a byte array.
148 * @param archiveReader
149 * @return null or the application data as a byte array
151 private byte[] getAppDataAsByteArray(AppData appdata, VamsasArchiveReader docreader) {
152 if (appdata.getData()==null) {
153 if (docreader==null) {
154 log.warn("Silently failing getAppDataAsByteArray with null docreader.",new Exception());
157 // resolve and load data
158 JarInputStream entry = getAppDataStream(appdata, docreader);
159 ByteArrayOutputStream bytes = new ByteArrayOutputStream();
161 byte buff[] = new byte[_TRANSFER_BUFFER];
163 while (entry.available()>0) {
164 int len = entry.read(buff, olen, _TRANSFER_BUFFER);
165 bytes.write(buff, 0, len);
169 } catch (Exception e) {
170 log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
172 if (bytes.size()>0) {
173 // LATER: deal with probable OutOfMemoryErrors here
174 log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference());
175 byte data[] = bytes.toByteArray();
181 log.debug("Returning inline AppData block for "+appdata.getVorbaId());
182 return appdata.getData();
186 * internal method for getting a DataInputStream from an AppData object.
189 * @return data in object or null if no data is accessible
191 private DataInput getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
192 if (appdata!=null && docreader!=null) {
193 String entryRef = appdata.getDataReference();
194 if (entryRef!=null) {
195 log.debug("Resolving AppData reference for "+entryRef);
196 InputStream jstrm = docreader.getAppdataStream(entryRef);
198 return new AppDataInputStream(jstrm);
200 log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
204 // return a byteArray input stream
205 byte[] data=appdata.getData();
207 ByteArrayInputStream stream = new ByteArrayInputStream(data);
208 return new DataInputStream(stream);
210 log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
215 log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
221 * internal method for getting ByteArray from AppData object
222 * @param clientOrUser - true for returning userData, otherwise return Client AppData.
223 * @return null or byte array
225 private byte[] _getappdataByteArray(boolean clientOrUser) {
227 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
231 appdName = "Client's Appdata";
233 appdName = "User's Appdata";
235 log.debug("getting "+appdName+" as a byte array");
236 extractAppData(clientdoc.getVamsasDocument());
244 log.debug("Trying to resolve "+appdName+" object to byte array.");
245 data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
248 log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
254 * common method for Client and User AppData->InputStream accessor
255 * @param clientOrUser - the appData to resolve - false for client, true for user appdata.
256 * @return null or the DataInputStream desired.
258 private DataInput _getappdataInputStream(boolean clientOrUser) {
260 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
263 appdName = "Client's Appdata";
265 appdName = "User's Appdata";
267 if (log.isDebugEnabled())
268 log.debug("getting "+appdName+" as an input stream.");
269 extractAppData(clientdoc.getVamsasDocument());
277 log.debug("Trying to resolve ClientAppdata object to an input stream.");
278 return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
280 log.debug("getClientInputStream returning null.");
284 * @see org.vamsas.client.IClientAppdata#getClientAppdata()
286 public byte[] getClientAppdata() {
287 return _getappdataByteArray(false);
290 * @see org.vamsas.client.IClientAppdata#getClientInputStream()
292 public DataInput getClientInputStream() {
293 return _getappdataInputStream(false);
297 * @see org.vamsas.client.IClientAppdata#getUserAppdata()
299 public byte[] getUserAppdata() {
300 return _getappdataByteArray(true);
304 * @see org.vamsas.client.IClientAppdata#getUserInputStream()
306 public DataInput getUserInputStream() {
307 return _getappdataInputStream(true);
310 * methods for writing new AppData entries.
312 private DataOutput _getAppdataOutputStream(boolean clientOrUser) {
314 SessionFile apdfile=null;
316 apdname = "clientAppData";
317 apdfile = newAppData;
319 apdname = "userAppData";
320 apdfile = newUserData;
324 apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
325 log.debug("Successfully made temp appData file for "+apdname);
327 // truncate to remove existing data.
328 apdfile.fileLock.rafile.setLength(0);
329 log.debug("Successfully truncated existing temp appData for "+apdname);
331 } catch (Exception e) {
332 log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
334 // we do not make another file for the new entry if one exists already
336 newAppData = apdfile;
338 newUserData = apdfile;
342 JarOutputStream dstrm =
344 new BufferedOutputStream(new java.io.FileOutputStream(apdfile.sessionFile)));
346 newAppDataStream = dstrm;
348 newUserDataStream = dstrm;
350 dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
351 // 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
352 return new AppDataOutputStream(dstrm);
354 catch (Exception e) {
355 log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
357 // tidy up and return null
358 apdfile.unlockFile();
362 * @see org.vamsas.client.IClientAppdata#getClientOutputStream()
364 public DataOutput getClientOutputStream() {
366 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
367 if (log.isDebugEnabled())
368 log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());
369 return _getAppdataOutputStream(false);
373 * @see org.vamsas.client.IClientAppdata#getUserOutputStream()
375 public DataOutput getUserOutputStream() {
377 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
378 if (log.isDebugEnabled())
379 log.debug("trying to getUserOutputStream for ("
380 +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());
381 return _getAppdataOutputStream(true);
385 * @see org.vamsas.client.IClientAppdata#hasClientAppdata()
387 public boolean hasClientAppdata() {
389 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
390 extractAppData(clientdoc.getVamsasDocument());
391 // LATER - check validity of a DataReference before we return true
392 if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
398 * @see org.vamsas.client.IClientAppdata#hasUserAppdata()
400 public boolean hasUserAppdata() {
402 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
403 extractAppData(clientdoc.getVamsasDocument());
404 // LATER - check validity of a DataReference before we return true
405 if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
409 private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
411 if (data!=null && data.length>0)
416 catch (Exception e) {
417 log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
422 * @see org.vamsas.client.IClientAppdata#setClientAppdata(byte[])
424 public void setClientAppdata(byte[] data) {
426 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
427 _getAppdataOutputStream(false);
428 if (newAppDataStream==null) {
429 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
430 log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir);
432 _writeAppDataStream(newAppDataStream, data);
433 // LATER: deal with error case - do we make session read only, or what ?
438 * @see org.vamsas.client.IClientAppdata#setUserAppdata(byte[])
440 public void setUserAppdata(byte[] data) {
442 throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
443 _getAppdataOutputStream(true);
444 if (newUserDataStream==null) {
445 // LATER: define an exception for this ? - operation may fail even if file i/o not involved
446 log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir);
448 _writeAppDataStream(newUserDataStream, data);
449 // LATER: deal with error case - do we make session read only, or what ?
453 * flush and close outstanding output streams.
454 * - do this before checking data length.
455 * @throws IOException
457 protected void closeForWriting() throws IOException {
458 if (newAppDataStream!=null) {
459 newAppDataStream.flush();
460 newAppDataStream.closeEntry();
461 newAppDataStream.close();
463 if (newUserDataStream!=null) {
464 newUserDataStream.flush();
465 newUserDataStream.closeEntry();
466 newUserDataStream.close();
470 * copy data from the appData jar file to an appropriately
471 * referenced jar or Data entry for the given ApplicationData
472 * @param vdoc session Document handler
473 * @param appd the AppData whose block is being updated
474 * @param apdjar the new data in a Jar written by this class
476 protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd, SessionFile apdjar) {
482 * @return true if any AppData blocks have to be updated in session Jar
484 protected boolean isModified() {
485 // LATER differentiate between core xml modification and Jar Entry modification.
486 if (newAppData.sessionFile.exists() || newUserData.sessionFile.exists())
491 * @see java.lang.Object#finalize()
493 protected void finalize() throws Throwable {
494 if (newAppDataStream!=null) {
495 newAppDataStream = null;
497 if (newAppDataStream!=null) {
498 newUserDataStream = null;
500 if (newAppData!=null) {
501 newAppData.eraseExistence();
504 if (newUserData!=null) {
505 newUserData.eraseExistence();