52d5890fc5bd98a69950d9ec30d6ca7560d630d3
[vamsas.git] / src / org / vamsas / client / simpleclient / SimpleClientAppdata.java
1 /**
2  * 
3  */
4 package org.vamsas.client.simpleclient;
5
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.InputStream;
14 import java.util.Vector;
15 import java.util.jar.JarEntry;
16 import java.util.jar.JarInputStream;
17 import java.util.jar.JarOutputStream;
18
19 import org.apache.commons.logging.Log;
20 import org.apache.commons.logging.LogFactory;
21 import org.vamsas.client.IClientAppdata;
22 import org.vamsas.objects.core.AppData;
23 import org.vamsas.objects.core.ApplicationData;
24 import org.vamsas.objects.core.User;
25 import org.vamsas.objects.utils.AppDataReference;
26
27 /**
28  * @author jimp
29  * Access interface to data chunks read from a VamsasArchiveReader stream 
30  * (or byte buffer input stream) or written to a VamsasArchive stream.
31  * // TODO: get VamsasArchiveReader from sclient
32  */
33 public class SimpleClientAppdata implements IClientAppdata {
34   private static Log log = LogFactory.getLog(SimpleClientAppdata.class);
35   /**
36    * has the session's document been accessed to get the AppData entrys?
37    */
38   protected boolean accessedDocument = false;
39   /**
40    * has the user datablock been modified ? 
41    * temporary file containing new user specific application data chunk
42    */
43   SessionFile newUserData=null;
44   JarOutputStream newUserDataStream = null;
45   /**
46    * has the apps global datablock been modified ?
47    * temporary file containing new global application data chunk
48    */
49   SessionFile newAppData=null;
50   JarOutputStream newAppDataStream=null;
51   ClientDocument clientdoc;
52   /**
53    * state flags
54    * - accessed ClientAppdata
55    * - accessed UserAppdata
56    * => inputStream from embedded xml or jar entry of backup has been created
57    * - set ClientAppdata
58    * - set UserAppdata
59    * => an output stream has been created and written to - or a data chunk has been written.
60    *  - need flag for switching between embedded and jar entry mode ? - always write a jar entry for a stream.
61    *  - need code for rewind and overwriting if the set*Appdata methods are called more than once.
62    *  - need flags for streams to except a call to set*Appdata when an output stream exists and is open.
63    *  - need 
64    * @param clientdoc The ClientDocument instance that this IClientAppData is accessing
65    */
66   protected SimpleClientAppdata(ClientDocument clientdoc) {
67     if (clientdoc==null) {
68       log.fatal("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
69       throw new Error("Implementation error - Null ClientDocument for SimpleClientAppdata construction.");
70     }
71     this.clientdoc = clientdoc;
72   }
73   /**
74    * set by extractAppData
75    */
76   protected ApplicationData appsGlobal = null;
77   /**
78    * set by extractAppData
79    */
80   protected User usersData = null;
81   /**
82    * gets appropriate app data for the application, if it exists in this dataset
83    * Called by every accessor to ensure data has been retrieved from document.
84    */
85   private void extractAppData(org.vamsas.objects.core.VamsasDocument doc) {
86     if (doc==null) {
87       log.debug("extractAppData called for null document object");
88       return;
89     }
90     if (accessedDocument) {
91       return;
92     }
93     Vector apldataset = AppDataReference.getUserandApplicationsData(
94         doc, clientdoc.sclient.getUserHandle(), clientdoc.sclient.getClientHandle());
95     accessedDocument = true;
96     if (apldataset!=null) {
97       if (apldataset.size()>0) {
98         AppData clientdat = (AppData) apldataset.get(0);
99         if (clientdat instanceof ApplicationData) {
100           appsGlobal = (ApplicationData) clientdat;
101           if (apldataset.size()>1) {
102             clientdat = (AppData) apldataset.get(1);
103             if (clientdat instanceof User) {
104               usersData = (User) clientdat;
105             }
106             if (apldataset.size()>2)
107               log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
108           } 
109         } else {
110           log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName());
111         }
112         apldataset.removeAllElements(); // destroy references.
113       }
114     }
115   }
116   /**
117    * LATER: generalize this for different low-level session implementations (it may not always be a Jar)
118    * @param appdata
119    * @param docreader
120    * @return
121    */
122   private JarInputStream getAppDataStream(AppData appdata, VamsasArchiveReader docreader) {
123     String entryRef = appdata.getDataReference();
124     if (entryRef!=null) {
125       log.debug("Resolving appData reference +"+entryRef);
126       InputStream entry = docreader.getAppdataStream(entryRef);
127       if (entry!=null) {
128         if (entry instanceof JarInputStream) {
129           return (JarInputStream) entry;
130         }
131         log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
132       }
133     } else {
134       log.debug("GetAppDataStream called for an AppData without a data reference.");
135     }
136     return null;
137   }
138   /**
139    * yuk - size of buffer used for slurping appData JarEntry into a byte array.
140    */
141   private final int _TRANSFER_BUFFER=4096*4;
142
143   /**
144    * Resolve AppData object to a byte array.
145    * @param appdata
146    * @param archiveReader
147    * @return null or the application data as a byte array
148    */
149   private byte[] getAppDataAsByteArray(AppData appdata, VamsasArchiveReader docreader) {
150     if (appdata.getData()==null) {
151       if (docreader==null) {
152         log.warn("Silently failing getAppDataAsByteArray with null docreader.",new Exception());
153         return null;
154       }
155       // resolve and load data
156       JarInputStream entry = getAppDataStream(appdata, docreader); 
157       ByteArrayOutputStream bytes = new ByteArrayOutputStream();
158       try {
159         byte buff[] = new byte[_TRANSFER_BUFFER];
160         int olen=0;
161         while (entry.available()>0) {
162           int len = entry.read(buff, olen, _TRANSFER_BUFFER);
163           bytes.write(buff, 0, len);
164           olen+=len;
165         }
166         buff=null;
167       } catch (Exception e) {
168         log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
169       }
170       if (bytes.size()>0) {
171         // LATER: deal with probable OutOfMemoryErrors here
172         log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference());
173         byte data[] = bytes.toByteArray();
174         bytes = null;
175         return data;
176       }
177       return null;
178     } else {
179       log.debug("Returning inline AppData block for "+appdata.getVorbaId());
180       return appdata.getData();
181     }
182   }
183   /**
184    * internal method for getting a DataInputStream from an AppData object.
185    * @param appdata
186    * @param docreader
187    * @return data in object or null if no data is accessible
188    */
189   private DataInput getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
190     if (appdata!=null && docreader!=null) {
191       String entryRef = appdata.getDataReference();
192       if (entryRef!=null) {
193         log.debug("Resolving AppData reference for "+entryRef);
194         InputStream jstrm = docreader.getAppdataStream(entryRef);
195         if (jstrm!=null)
196           return new AppDataInputStream(jstrm);
197         else {
198           log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
199           return null;
200         }
201       } else {
202         // return a byteArray input stream
203         byte[] data=appdata.getData();
204         if (data.length>0) {
205           ByteArrayInputStream stream = new ByteArrayInputStream(data);
206           return new DataInputStream(stream);
207         } else {
208           log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
209           return null;
210         }
211       }
212     } else {
213       log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
214     }
215     return null;
216   }
217
218   /**
219    * internal method for getting ByteArray from AppData object
220    * @param clientOrUser - true for returning userData, otherwise return Client AppData.
221    * @return null or byte array
222    */
223   private byte[] _getappdataByteArray(boolean clientOrUser) {
224     if (clientdoc==null)
225       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
226     byte[] data=null;
227     String appdName;
228     if (!clientOrUser) {
229       appdName = "Client's Appdata";
230     } else {
231       appdName = "User's Appdata";
232     }    
233     log.debug("getting "+appdName+" as a byte array");
234     extractAppData(clientdoc.getVamsasDocument());
235     AppData object;
236     if (!clientOrUser) {
237       object = appsGlobal;
238     } else {
239       object = usersData;
240     }
241     if (object!=null) {
242       log.debug("Trying to resolve "+appdName+" object to byte array.");
243       data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
244     }
245     if (data == null)
246       log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
247     return data;
248     
249   }
250   
251   /**
252    * common method for Client and User AppData->InputStream accessor
253    * @param clientOrUser - the appData to resolve - false for client, true for user appdata.
254    * @return null or the DataInputStream desired.
255    */
256   private DataInput _getappdataInputStream(boolean clientOrUser) {
257     if (clientdoc==null)
258       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
259     String appdName;
260     if (!clientOrUser) {
261       appdName = "Client's Appdata";
262     } else {
263       appdName = "User's Appdata";
264     }
265     if (log.isDebugEnabled())
266       log.debug("getting "+appdName+" as an input stream.");
267     extractAppData(clientdoc.getVamsasDocument());
268     AppData object;
269     if (!clientOrUser) {
270       object = appsGlobal;
271     } else {
272       object = usersData;
273     }
274     if (object!=null) {
275       log.debug("Trying to resolve ClientAppdata object to an input stream.");
276       return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
277     }
278     log.debug("getClientInputStream returning null.");
279     return null;
280   }
281   /* (non-Javadoc)
282    * @see org.vamsas.client.IClientAppdata#getClientAppdata()
283    */
284   public byte[] getClientAppdata() {
285     return _getappdataByteArray(false);
286   }
287   /* (non-Javadoc)
288    * @see org.vamsas.client.IClientAppdata#getClientInputStream()
289    */
290   public DataInput getClientInputStream() {
291     return _getappdataInputStream(false);
292   }
293
294   /* (non-Javadoc)
295    * @see org.vamsas.client.IClientAppdata#getUserAppdata()
296    */
297   public byte[] getUserAppdata() {
298     return _getappdataByteArray(true);
299   }
300
301   /* (non-Javadoc)
302    * @see org.vamsas.client.IClientAppdata#getUserInputStream()
303    */
304   public DataInput getUserInputStream() {
305     return _getappdataInputStream(true);
306   }
307   /**
308    * methods for writing new AppData entries.
309    */
310   private DataOutput _getAppdataOutputStream(boolean clientOrUser) {
311     String apdname;
312     SessionFile apdfile=null;
313     if (!clientOrUser) {
314       apdname = "clientAppData";
315       apdfile = newAppData;
316     } else {
317       apdname = "userAppData";
318       apdfile = newUserData;
319     }
320     try {
321       if (apdfile==null) {
322         apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
323         log.debug("Successfully made temp appData file for "+apdname);
324       } else {
325         // truncate to remove existing data.
326         apdfile.fileLock.rafile.setLength(0);
327         log.debug("Successfully truncated existing temp appData for "+apdname);
328       }
329       } catch (Exception e) {
330       log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
331     }
332     // we do not make another file for the new entry if one exists already
333     if (!clientOrUser) {
334       newAppData = apdfile;
335     } else {
336       newUserData = apdfile;
337     }
338     try {
339       apdfile.lockFile();
340       JarOutputStream dstrm = 
341         new JarOutputStream(
342             new BufferedOutputStream(new java.io.FileOutputStream(apdfile.sessionFile)));
343       if (!clientOrUser) {
344         newAppDataStream = dstrm;
345       } else {
346         newUserDataStream = dstrm;
347       }
348       dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
349       // 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
350       return new AppDataOutputStream(dstrm);
351     }
352     catch (Exception e) {
353       log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
354     }
355     // tidy up and return null
356     apdfile.unlockFile();
357     return null;
358    }
359   /* (non-Javadoc)
360    * @see org.vamsas.client.IClientAppdata#getClientOutputStream()
361    */
362   public DataOutput getClientOutputStream() {
363     if (clientdoc==null)
364       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
365     if (log.isDebugEnabled())
366       log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());   
367     return _getAppdataOutputStream(false);
368   }
369
370   /* (non-Javadoc)
371    * @see org.vamsas.client.IClientAppdata#getUserOutputStream()
372    */
373   public DataOutput getUserOutputStream() {
374     if (clientdoc==null)
375       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
376     if (log.isDebugEnabled())
377       log.debug("trying to getUserOutputStream for ("
378           +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());   
379     return _getAppdataOutputStream(true);
380   }
381
382   /* (non-Javadoc)
383    * @see org.vamsas.client.IClientAppdata#hasClientAppdata()
384    */
385   public boolean hasClientAppdata() {
386     if (clientdoc==null)
387       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
388     extractAppData(clientdoc.getVamsasDocument());
389     // LATER - check validity of a DataReference before we return true
390     if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
391       return true;
392     return false;
393   }
394
395   /* (non-Javadoc)
396    * @see org.vamsas.client.IClientAppdata#hasUserAppdata()
397    */
398   public boolean hasUserAppdata() {
399     if (clientdoc==null)
400       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
401     extractAppData(clientdoc.getVamsasDocument());
402     // LATER - check validity of a DataReference before we return true
403     if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
404       return true;
405     return false;
406   }
407   private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
408     try {
409       if (data!=null && data.length>0) 
410         ostrm.write(data);
411       ostrm.closeEntry();
412       return true;
413     }
414     catch (Exception e) {
415       log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
416     }
417     return false;
418   }
419   /* (non-Javadoc)
420    * @see org.vamsas.client.IClientAppdata#setClientAppdata(byte[])
421    */
422   public void setClientAppdata(byte[] data) {
423     if (clientdoc==null)
424       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
425     _getAppdataOutputStream(false);
426     if (newAppDataStream==null) {
427       // LATER: define an exception for this ? - operation may fail even if file i/o not involved
428       log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir);
429     } else {
430       _writeAppDataStream(newAppDataStream, data);
431       // LATER: deal with error case - do we make session read only, or what ?
432     }
433   }
434
435   /* (non-Javadoc)
436    * @see org.vamsas.client.IClientAppdata#setUserAppdata(byte[])
437    */
438   public void setUserAppdata(byte[] data) {
439     if (clientdoc==null)
440       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
441     _getAppdataOutputStream(true);
442     if (newUserDataStream==null) {
443       // LATER: define an exception for this ? - operation may fail even if file i/o not involved
444       log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir);
445     } else {
446       _writeAppDataStream(newUserDataStream, data);
447       // LATER: deal with error case - do we make session read only, or what ?
448     }
449   }
450   /* (non-Javadoc)
451    * @see java.lang.Object#finalize()
452    */
453   protected void finalize() throws Throwable {
454     if (newAppDataStream!=null) {
455       newAppDataStream = null;
456     }
457     if (newAppDataStream!=null) {
458       newUserDataStream = null;
459     }
460     if (newAppData!=null) {
461       newAppData.eraseExistence();
462       newAppData = null;
463     }
464     if (newUserData!=null) {
465       newUserData.eraseExistence();
466       newUserData = null;
467     }
468     super.finalize();
469   }
470
471 }