intermediate cjeckin - nfs woes!
[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.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;
19
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;
27
28 /**
29  * @author jimp
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
33  */
34 public class SimpleClientAppdata implements IClientAppdata {
35   private static Log log = LogFactory.getLog(SimpleClientAppdata.class);
36   /**
37    * has the session's document been accessed to get the AppData entrys?
38    */
39   protected boolean accessedDocument = false;
40   /**
41    * has the user datablock been modified ? 
42    * temporary file containing new user specific application data chunk
43    */
44   SessionFile newUserData=null;
45   JarOutputStream newUserDataStream = null;
46   /**
47    * has the apps global datablock been modified ?
48    * temporary file containing new global application data chunk
49    */
50   SessionFile newAppData=null;
51   JarOutputStream newAppDataStream=null;
52   /**
53    * set by extractAppData
54    */
55   protected ApplicationData appsGlobal = null;
56   /**
57    * set by extractAppData
58    */
59   protected User usersData = null;
60   
61   ClientDocument clientdoc;
62   /**
63    * state flags
64    * - accessed ClientAppdata
65    * - accessed UserAppdata
66    * => inputStream from embedded xml or jar entry of backup has been created
67    * - set ClientAppdata
68    * - set UserAppdata
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.
73    *  - need 
74    * @param clientdoc The ClientDocument instance that this IClientAppData is accessing
75    */
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.");
80     }
81     this.clientdoc = clientdoc;
82   }
83   /**
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.
86    */
87   private void extractAppData(org.vamsas.objects.core.VamsasDocument doc) {
88     if (doc==null) {
89       log.debug("extractAppData called for null document object");
90       return;
91     }
92     if (accessedDocument) {
93       return;
94     }
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;
107             }
108             if (apldataset.size()>2)
109               log.info("Ignoring additional ("+(apldataset.size()-2)+") AppDatas returned by document appdata query.");
110           } 
111         } else {
112           log.warn("Unexpected entry in AppDataReference query: id="+clientdat.getVorbaId()+" type="+clientdat.getClass().getName());
113         }
114         apldataset.removeAllElements(); // destroy references.
115       }
116     }
117   }
118   /**
119    * LATER: generalize this for different low-level session implementations (it may not always be a Jar)
120    * @param appdata
121    * @param docreader
122    * @return
123    */
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);
129       if (entry!=null) {
130         if (entry instanceof JarInputStream) {
131           return (JarInputStream) entry;
132         }
133         log.warn("Implementation problem - docreader didn't return a JarInputStream entry.");
134       }
135     } else {
136       log.debug("GetAppDataStream called for an AppData without a data reference.");
137     }
138     return null;
139   }
140   /**
141    * yuk - size of buffer used for slurping appData JarEntry into a byte array.
142    */
143   private final int _TRANSFER_BUFFER=4096*4;
144
145   /**
146    * Resolve AppData object to a byte array.
147    * @param appdata
148    * @param archiveReader
149    * @return null or the application data as a byte array
150    */
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());
155         return null;
156       }
157       // resolve and load data
158       JarInputStream entry = getAppDataStream(appdata, docreader); 
159       ByteArrayOutputStream bytes = new ByteArrayOutputStream();
160       try {
161         byte buff[] = new byte[_TRANSFER_BUFFER];
162         int olen=0;
163         while (entry.available()>0) {
164           int len = entry.read(buff, olen, _TRANSFER_BUFFER);
165           bytes.write(buff, 0, len);
166           olen+=len;
167         }
168         buff=null;
169       } catch (Exception e) {
170         log.warn("Unexpected exception - probable truncation when accessing VamsasDocument entry "+appdata.getDataReference(), e);
171       }
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();
176         bytes = null;
177         return data;
178       }
179       return null;
180     } else {
181       log.debug("Returning inline AppData block for "+appdata.getVorbaId());
182       return appdata.getData();
183     }
184   }
185   /**
186    * internal method for getting a DataInputStream from an AppData object.
187    * @param appdata
188    * @param docreader
189    * @return data in object or null if no data is accessible
190    */
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);
197         if (jstrm!=null)
198           return new AppDataInputStream(jstrm);
199         else {
200           log.debug("Returning null input stream for unresolved reference ("+entryRef+") id="+appdata.getVorbaId());
201           return null;
202         }
203       } else {
204         // return a byteArray input stream
205         byte[] data=appdata.getData();
206         if (data.length>0) {
207           ByteArrayInputStream stream = new ByteArrayInputStream(data);
208           return new DataInputStream(stream);
209         } else {
210           log.debug("Returning null input stream for empty Appdata data block in id="+appdata.getVorbaId());
211           return null;
212         }
213       }
214     } else {
215       log.debug("Returning null DataInputStream for appdata entry:"+appdata.getVorbaId());
216     }
217     return null;
218   }
219
220   /**
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
224    */
225   private byte[] _getappdataByteArray(boolean clientOrUser) {
226     if (clientdoc==null)
227       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
228     byte[] data=null;
229     String appdName;
230     if (!clientOrUser) {
231       appdName = "Client's Appdata";
232     } else {
233       appdName = "User's Appdata";
234     }    
235     log.debug("getting "+appdName+" as a byte array");
236     extractAppData(clientdoc.getVamsasDocument());
237     AppData object;
238     if (!clientOrUser) {
239       object = appsGlobal;
240     } else {
241       object = usersData;
242     }
243     if (object!=null) {
244       log.debug("Trying to resolve "+appdName+" object to byte array.");
245       data = getAppDataAsByteArray(object, clientdoc.getVamsasArchiveReader());
246     }
247     if (data == null)
248       log.debug("Returning null for "+appdName+"ClientAppdata byte[] array");
249     return data;
250     
251   }
252   
253   /**
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.
257    */
258   private DataInput _getappdataInputStream(boolean clientOrUser) {
259     if (clientdoc==null)
260       throw new Error("Implementation error, Improperly initialized SimpleClientAppdata.");
261     String appdName;
262     if (!clientOrUser) {
263       appdName = "Client's Appdata";
264     } else {
265       appdName = "User's Appdata";
266     }
267     if (log.isDebugEnabled())
268       log.debug("getting "+appdName+" as an input stream.");
269     extractAppData(clientdoc.getVamsasDocument());
270     AppData object;
271     if (!clientOrUser) {
272       object = appsGlobal;
273     } else {
274       object = usersData;
275     }
276     if (object!=null) {
277       log.debug("Trying to resolve ClientAppdata object to an input stream.");
278       return getAppDataAsDataInputStream(object, clientdoc.getVamsasArchiveReader());
279     }
280     log.debug("getClientInputStream returning null.");
281     return null;
282   }
283   /* (non-Javadoc)
284    * @see org.vamsas.client.IClientAppdata#getClientAppdata()
285    */
286   public byte[] getClientAppdata() {
287     return _getappdataByteArray(false);
288   }
289   /* (non-Javadoc)
290    * @see org.vamsas.client.IClientAppdata#getClientInputStream()
291    */
292   public DataInput getClientInputStream() {
293     return _getappdataInputStream(false);
294   }
295
296   /* (non-Javadoc)
297    * @see org.vamsas.client.IClientAppdata#getUserAppdata()
298    */
299   public byte[] getUserAppdata() {
300     return _getappdataByteArray(true);
301   }
302
303   /* (non-Javadoc)
304    * @see org.vamsas.client.IClientAppdata#getUserInputStream()
305    */
306   public DataInput getUserInputStream() {
307     return _getappdataInputStream(true);
308   }
309   /**
310    * methods for writing new AppData entries.
311    */
312   private DataOutput _getAppdataOutputStream(boolean clientOrUser) {
313     String apdname;
314     SessionFile apdfile=null;
315     if (!clientOrUser) {
316       apdname = "clientAppData";
317       apdfile = newAppData;
318     } else {
319       apdname = "userAppData";
320       apdfile = newUserData;
321     }
322     try {
323       if (apdfile==null) {
324         apdfile=clientdoc.sclient._session.getTempSessionFile(apdname,".jar");
325         log.debug("Successfully made temp appData file for "+apdname);
326       } else {
327         // truncate to remove existing data.
328         apdfile.fileLock.rafile.setLength(0);
329         log.debug("Successfully truncated existing temp appData for "+apdname);
330       }
331       } catch (Exception e) {
332       log.error("Whilst opening temp file in directory "+clientdoc.sclient._session.sessionDir, e);
333     }
334     // we do not make another file for the new entry if one exists already
335     if (!clientOrUser) {
336       newAppData = apdfile;
337     } else {
338       newUserData = apdfile;
339     }
340     try {
341       apdfile.lockFile();
342       JarOutputStream dstrm = 
343         new JarOutputStream(
344             new BufferedOutputStream(new java.io.FileOutputStream(apdfile.sessionFile)));
345       if (!clientOrUser) {
346         newAppDataStream = dstrm;
347       } else {
348         newUserDataStream = dstrm;
349       }
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);
353     }
354     catch (Exception e) {
355       log.error("Whilst opening jar output stream for file "+apdfile.sessionFile);
356     }
357     // tidy up and return null
358     apdfile.unlockFile();
359     return null;
360    }
361   /* (non-Javadoc)
362    * @see org.vamsas.client.IClientAppdata#getClientOutputStream()
363    */
364   public DataOutput getClientOutputStream() {
365     if (clientdoc==null)
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);
370   }
371
372   /* (non-Javadoc)
373    * @see org.vamsas.client.IClientAppdata#getUserOutputStream()
374    */
375   public DataOutput getUserOutputStream() {
376     if (clientdoc==null)
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);
382   }
383
384   /* (non-Javadoc)
385    * @see org.vamsas.client.IClientAppdata#hasClientAppdata()
386    */
387   public boolean hasClientAppdata() {
388     if (clientdoc==null)
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))
393       return true;
394     return false;
395   }
396
397   /* (non-Javadoc)
398    * @see org.vamsas.client.IClientAppdata#hasUserAppdata()
399    */
400   public boolean hasUserAppdata() {
401     if (clientdoc==null)
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))
406       return true;
407     return false;
408   }
409   private boolean _writeAppDataStream(JarOutputStream ostrm, byte[] data) {
410     try {
411       if (data!=null && data.length>0) 
412         ostrm.write(data);
413       ostrm.closeEntry();
414       return true;
415     }
416     catch (Exception e) {
417       log.error("Serious! - IO error when writing AppDataStream to file "+newAppData.sessionFile, e);
418     }
419     return false;
420   }
421   /* (non-Javadoc)
422    * @see org.vamsas.client.IClientAppdata#setClientAppdata(byte[])
423    */
424   public void setClientAppdata(byte[] data) {
425     if (clientdoc==null)
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);
431     } else {
432       _writeAppDataStream(newAppDataStream, data);
433       // LATER: deal with error case - do we make session read only, or what ?
434     }
435   }
436
437   /* (non-Javadoc)
438    * @see org.vamsas.client.IClientAppdata#setUserAppdata(byte[])
439    */
440   public void setUserAppdata(byte[] data) {
441     if (clientdoc==null)
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);
447     } else {
448       _writeAppDataStream(newUserDataStream, data);
449       // LATER: deal with error case - do we make session read only, or what ?
450     }
451   }
452   /**
453    * flush and close outstanding output streams. 
454    *  - do this before checking data length.
455    * @throws IOException
456    */
457   protected void closeForWriting() throws IOException {
458     if (newAppDataStream!=null) {
459       newAppDataStream.flush();
460       newAppDataStream.closeEntry();
461       newAppDataStream.close();
462     }
463     if (newUserDataStream!=null) {
464       newUserDataStream.flush();
465       newUserDataStream.closeEntry();
466       newUserDataStream.close();
467     }
468   }
469   /**
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
475    */
476   protected void updateAnAppdataEntry(VamsasArchive vdoc, AppData appd, SessionFile apdjar) {
477     
478   }
479
480   /**
481    * 
482    * @return true if any AppData blocks have to be updated in session Jar
483    */
484   protected boolean isModified() {
485     // LATER differentiate between core xml modification and Jar Entry modification.
486     if (newAppData.sessionFile.exists() || newUserData.sessionFile.exists())
487       return true;
488     return false;
489   }
490   /* (non-Javadoc)
491    * @see java.lang.Object#finalize()
492    */
493   protected void finalize() throws Throwable {
494     if (newAppDataStream!=null) {
495       newAppDataStream = null;
496     }
497     if (newAppDataStream!=null) {
498       newUserDataStream = null;
499     }
500     if (newAppData!=null) {
501       newAppData.eraseExistence();
502       newAppData = null;
503     }
504     if (newUserData!=null) {
505       newUserData.eraseExistence();
506       newUserData = null;
507     }
508     super.finalize();
509   }
510
511 }