SimpleClientAppdata implemented but untested.
[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       // resolve and load data
152       JarInputStream entry = getAppDataStream(appdata, docreader); 
153       ByteArrayOutputStream bytes = new ByteArrayOutputStream();
154       try {
155         byte buff[] = new byte[_TRANSFER_BUFFER];
156         int olen=0;
157         while (entry.available()>0) {
158           int len = entry.read(buff, olen, _TRANSFER_BUFFER);
159           bytes.write(buff, 0, len);
160           olen+=len;
161         }
162         buff=null;
163       } catch (Exception e) {
164         log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
165       }
166       if (bytes.size()>0) {
167         // LATER: deal with probable OutOfMemoryErrors here
168         log.debug("Got "+bytes.size()+" bytes from AppDataReference "+appdata.getDataReference());
169         byte data[] = bytes.toByteArray();
170         bytes = null;
171         return data;
172       }
173       return null;
174     } else {
175       log.debug("Returning inline AppData block for "+appdata.getVorbaId());
176       return appdata.getData();
177     }
178   }
179   /**
180    * internal method for getting a DataInputStream from an AppData object.
181    * @param appdata
182    * @param docreader
183    * @return data in object or null if no data is accessible
184    */
185   private DataInput getAppDataAsDataInputStream(AppData appdata, VamsasArchiveReader docreader) {
186     if (appdata!=null) {
187       String entryRef = appdata.getDataReference();
188       if (entryRef!=null) {
189         log.debug("Resolving AppData reference for "+entryRef);
190         InputStream jstrm = docreader.getAppdataStream(entryRef);
191         if (jstrm!=null)
192           return new AppDataInputStream(jstrm);
193         else {
194           log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
195           return null;
196         }
197       } else {
198         // return a byteArray input stream
199         byte[] data=appdata.getData();
200         if (data.length>0) {
201           ByteArrayInputStream stream = new ByteArrayInputStream(data);
202           return new DataInputStream(stream);
203         } else {
204           log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
205           return null;
206         }
207       }
208     } else {
209       log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
210     }
211     return null;
212   }
213
214   /**
215    * internal method for getting ByteArray from AppData object
216    * @param clientOrUser - true for returning userData, otherwise return Client AppData.
217    * @return null or byte array
218    */
219   private byte[] _getappdataByteArray(boolean clientOrUser) {
220     if (clientdoc==null)
221       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
222     byte[] data=null;
223     String appdName;
224     if (!clientOrUser) {
225       appdName = "Client's Appdata";
226     } else {
227       appdName = "User's Appdata";
228     }    
229     log.debug("getting "+appdName+" as a byte array");
230     extractAppData(clientdoc.getVamsasDocument());
231     AppData object;
232     if (!clientOrUser) {
233       object = appsGlobal;
234     } else {
235       object = usersData;
236     }
237     if (object!=null) {
238       log.debug("Trying to resolve "+appdName+" object to byte array.");
239       data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
240     }
241     if (data == null)
242       log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
243     return data;
244     
245   }
246   
247   /**
248    * common method for Client and User AppData->InputStream accessor
249    * @param clientOrUser - the appData to resolve - false for client, true for user appdata.
250    * @return null or the DataInputStream desired.
251    */
252   private DataInput _getappdataInputStream(boolean clientOrUser) {
253     if (clientdoc==null)
254       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
255     String appdName;
256     if (!clientOrUser) {
257       appdName = "Client's Appdata";
258     } else {
259       appdName = "User's Appdata";
260     }
261     if (log.isDebugEnabled())
262       log.debug("getting "+appdName+" as an input stream.");
263     extractAppData(clientdoc.getVamsasDocument());
264     AppData object;
265     if (!clientOrUser) {
266       object = appsGlobal;
267     } else {
268       object = usersData;
269     }
270     if (object!=null) {
271       log.debug("Trying to resolve ClientAppdata object to an input stream.");
272       return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
273     }
274     log.debug("getClientInputStream returning null.");
275     return null;
276   }
277   /* (non-Javadoc)
278    * @see org.vamsas.client.IClientAppdata#getClientAppdata()
279    */
280   public byte[] getClientAppdata() {
281     return _getappdataByteArray(false);
282   }
283   /* (non-Javadoc)
284    * @see org.vamsas.client.IClientAppdata#getClientInputStream()
285    */
286   public DataInput getClientInputStream() {
287     return _getappdataInputStream(false);
288   }
289
290   /* (non-Javadoc)
291    * @see org.vamsas.client.IClientAppdata#getUserAppdata()
292    */
293   public byte[] getUserAppdata() {
294     return _getappdataByteArray(true);
295   }
296
297   /* (non-Javadoc)
298    * @see org.vamsas.client.IClientAppdata#getUserInputStream()
299    */
300   public DataInput getUserInputStream() {
301     return _getappdataInputStream(true);
302   }
303   /**
304    * methods for writing new AppData entries.
305    */
306   private DataOutput _getAppdataOutputStream(boolean clientOrUser) {
307     String apdname;
308     SessionFile apdfile=null;
309     if (!clientOrUser) {
310       apdname = "clientAppData";
311       apdfile = newAppData;
312     } else {
313       apdname = "userAppData";
314       apdfile = newUserData;
315     }
316     try {
317       if (apdfile==null) {
318         apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
319         log.debug("Successfully made temp appData file for "+apdname);
320       } else {
321         // truncate to remove existing data.
322         apdfile.fileLock.rafile.setLength(0);
323         log.debug("Successfully truncated existing temp appData for "+apdname);
324       }
325       } catch (Exception e) {
326       log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
327     }
328     // we do not make another file for the new entry if one exists already
329     if (!clientOrUser) {
330       newAppData = apdfile;
331     } else {
332       newUserData = apdfile;
333     }
334     try {
335       apdfile.lockFile();
336       JarOutputStream dstrm = 
337         new JarOutputStream(
338             new BufferedOutputStream(new java.io.FileOutputStream(apdfile.sessionFile)));
339       if (!clientOrUser) {
340         newAppDataStream = dstrm;
341       } else {
342         newUserDataStream = dstrm;
343       }
344       dstrm.putNextEntry(new JarEntry("appData_entry.dat"));
345       // 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
346       return new AppDataOutputStream(dstrm);
347     }
348     catch (Exception e) {
349       log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
350     }
351     // tidy up and return null
352     apdfile.unlockFile();
353     return null;
354    }
355   /* (non-Javadoc)
356    * @see org.vamsas.client.IClientAppdata#getClientOutputStream()
357    */
358   public DataOutput getClientOutputStream() {
359     if (clientdoc==null)
360       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
361     if (log.isDebugEnabled())
362       log.debug("trying to getClientOutputStream for "+clientdoc.sclient.client.getClientUrn());   
363     return _getAppdataOutputStream(false);
364   }
365
366   /* (non-Javadoc)
367    * @see org.vamsas.client.IClientAppdata#getUserOutputStream()
368    */
369   public DataOutput getUserOutputStream() {
370     if (clientdoc==null)
371       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
372     if (log.isDebugEnabled())
373       log.debug("trying to getUserOutputStream for ("
374           +clientdoc.sclient.getUserHandle().getFullName()+")"+clientdoc.sclient.client.getClientUrn());   
375     return _getAppdataOutputStream(true);
376   }
377
378   /* (non-Javadoc)
379    * @see org.vamsas.client.IClientAppdata#hasClientAppdata()
380    */
381   public boolean hasClientAppdata() {
382     if (clientdoc==null)
383       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
384     extractAppData(clientdoc.getVamsasDocument());
385     // LATER - check validity of a DataReference before we return true
386     if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
387       return true;
388     return false;
389   }
390
391   /* (non-Javadoc)
392    * @see org.vamsas.client.IClientAppdata#hasUserAppdata()
393    */
394   public boolean hasUserAppdata() {
395     if (clientdoc==null)
396       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
397     extractAppData(clientdoc.getVamsasDocument());
398     // LATER - check validity of a DataReference before we return true
399     if ((appsGlobal!=null) && (appsGlobal.getDataReference()!=null || appsGlobal.getData()!=null))
400       return true;
401     return false;
402   }
403   private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
404     try {
405       if (data!=null && data.length>0) 
406         ostrm.write(data);
407       ostrm.closeEntry();
408       return true;
409     }
410     catch (Exception e) {
411       log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
412     }
413     return false;
414   }
415   /* (non-Javadoc)
416    * @see org.vamsas.client.IClientAppdata#setClientAppdata(byte[])
417    */
418   public void setClientAppdata(byte[] data) {
419     if (clientdoc==null)
420       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
421     _getAppdataOutputStream(false);
422     if (newAppDataStream==null) {
423       // LATER: define an exception for this ? - operation may fail even if file i/o not involved
424       log.error("Serious! - couldn't open new AppDataStream in session directory "+clientdoc.sclient._session.sessionDir);
425     } else {
426       _writeAppDataStream(newAppDataStream, data);
427       // LATER: deal with error case - do we make session read only, or what ?
428     }
429   }
430
431   /* (non-Javadoc)
432    * @see org.vamsas.client.IClientAppdata#setUserAppdata(byte[])
433    */
434   public void setUserAppdata(byte[] data) {
435     if (clientdoc==null)
436       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
437     _getAppdataOutputStream(true);
438     if (newUserDataStream==null) {
439       // LATER: define an exception for this ? - operation may fail even if file i/o not involved
440       log.error("Serious! - couldn't open new UserDataStream in session directory "+clientdoc.sclient._session.sessionDir);
441     } else {
442       _writeAppDataStream(newUserDataStream, data);
443       // LATER: deal with error case - do we make session read only, or what ?
444     }
445   }
446   /* (non-Javadoc)
447    * @see java.lang.Object#finalize()
448    */
449   protected void finalize() throws Throwable {
450     if (newAppDataStream!=null) {
451       newAppDataStream = null;
452     }
453     if (newAppDataStream!=null) {
454       newUserDataStream = null;
455     }
456     if (newAppData!=null) {
457       newAppData.eraseExistence();
458       newAppData = null;
459     }
460     if (newUserData!=null) {
461       newUserData.eraseExistence();
462       newUserData = null;
463     }
464     super.finalize();
465   }
466
467 }