better command line processing options (load, save) and slower update poll check.
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / VamsasSession.java
1 package uk.ac.vamsas.client.simpleclient;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.PrintStream;
6 import java.io.PrintWriter;
7 import java.io.RandomAccessFile;
8 import java.io.Writer;
9 import java.util.Enumeration;
10 import java.util.Vector;
11
12 import org.apache.commons.logging.Log;
13 import org.apache.commons.logging.LogFactory;
14 import org.apache.log4j.Appender;
15 import org.apache.log4j.Logger;
16 import org.apache.log4j.FileAppender;
17 import org.apache.log4j.PatternLayout;
18
19 import uk.ac.vamsas.client.ClientHandle;
20 import uk.ac.vamsas.client.IClient;
21 import uk.ac.vamsas.client.UserHandle;
22 /**
23  * Does all the IO operations for a SimpleClient instance accessing 
24  * a SimpleClient vamsas session.
25  * 
26  * Basically, it defines the various standard names for the files 
27  * in the session directory (that maps to the sessionUrn), 
28  * provides constructors for the file handlers and watchers of 
29  * those file entities, and some higher level methods 
30  * to check and change the state flags for the session.
31  * 
32  * TODO: move the stuff below to the SimpleClientFactory documentation.
33  * much may not be valid now :
34  * Vamsas client is intialised with a path to create live session directories. 
35  * This path may contain a vamsas.properties file 
36  * that sets additional parameters (otherwise client 
37  * just uses the one on the classpath).
38  * 
39  * A vamsas session consists of :
40  *  SessionDir - translates to urn of a live session.
41  *  Contains: Vamsas Document (as a jar), Session client list file, 
42  *  both of which may be locked, and additional 
43  *  temporary versions of these files when write 
44  *  operations are taking place.
45  * 
46  * Zip file entries
47  *  - vamsasdocument.xml : core info
48  *  one or more:
49  *  - <applicationname>.version.sessionnumber.raw (string given in vamsasdocument.xml applicationData entry)
50  *  
51  * Lockfile
52  *  - filename given in the vamsasdocument.xml. Should be checked for validity by any client and rewritten if necessary. 
53  *    The lockfile can point to the jar itself.
54  * Mode of operation.
55  * Initially - documentHandler either:
56  *  - creates a zip for a new session for the client
57  *  - connect to an existing session zip 
58  *   1. reads session urn file
59  *   2. waits for lock
60  *   3. examines session - decide whether to create new application data slice or connect to one stored in session.
61  *   4. writes info into session file
62  *   5. releases lock and generates local client events.
63  *   6. Creates Watcher thread to generate events.
64  * 
65  * During the session
66  *  - Update watcher checks for file change - 
67  * 
68  * Procedures for file based session message exchange
69  *  - session document modification flag
70  *    
71  */
72
73 public class VamsasSession {
74   /**
75    * indicator file for informing other processes that 
76    * they should finalise their vamsas datasets for 
77    * storing into a vamsas archive.
78    */
79   public static final String CLOSEANDSAVE_FILE="stored.log";
80   /**
81    * session file storing the last_stored_stat data 
82    */
83   public static final String MODIFIEDDOC_FILE="modified";
84
85   
86   private SimpleSessionManager sessionManager = null;
87   
88   /**
89    * called to clear update flag after a successful offline storage event
90    */
91   protected void clearUnsavedFlag() {
92     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
93     if (!laststored.clearFlag())
94       log.warn("Unsaved flag was not cleared for "+sessionDir);
95   }
96   /**
97    * called to indicate session document has been modified.
98    *
99    */
100   protected void setUnsavedFlag() {
101     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
102     if (!laststored.setFlag())
103       log.warn("Couldn't set the Unsaved flag for "+sessionDir);
104   }
105   /**
106    * 
107    * @return true if session document has been modified since last offline storage event 
108    */
109   protected boolean getUnsavedFlag() {
110     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir, MODIFIEDDOC_FILE));
111     return laststored.checkFlag();
112   }
113   /**
114    * log file location
115    */
116   public static final String SESSION_LOG="Log.txt";
117   private static Log log = LogFactory.getLog(VamsasSession.class);
118   protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
119   /**
120    * setup the sessionLog using Log4j.
121    * @throws IOException
122    */
123   private void initLog() throws IOException {
124     // TODO: fix session event logging
125     // LATER: make dedicated appender format for session log.
126     /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
127     // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
128     // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
129     for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
130       System.out.println(e.nextElement());
131
132     }*/
133   
134     if (slog!= null ) {
135       File sessionLogFile =  new File(this.sessionDir, SESSION_LOG);
136       slog.addAppender(new FileAppender(new PatternLayout("%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(), true));
137     } else {
138       log.info("No appender for SessionLog");
139     }
140   }
141   
142   /**
143    * the sessionDir is given as the session location for new clients.
144    */
145   protected File sessionDir;
146   /**
147    * holds the list of attached clients
148    */
149   ClientsFile clist;
150   public static final String CLIENT_LIST="Clients.obj";
151   /**
152    * holds the data
153    */
154   VamsasFile vamArchive; 
155   public static final String VAMSAS_OBJ="VamDoc.jar";
156   
157   /**
158    * sets up the vamsas session files and watchers in sessionDir
159    * @param sessionDir1
160    */
161   protected VamsasSession(File sessionDir1) throws IOException {
162     if (sessionDir1==null)
163       throw new Error("Null directory for VamsasSession.");
164     if (sessionDir1.exists()) {
165       if (!sessionDir1.isDirectory() || !sessionDir1.canWrite() || !sessionDir1.canRead())
166         throw new IOException("Cannot access '"+sessionDir1+"' as a read/writable Directory.");
167       if (!checkSessionFiles(sessionDir1))
168       log.warn("checkSessionFiles() returned false. Possible client implementation error");
169       this.sessionDir = sessionDir1; 
170       initSessionObjects();
171       slog.debug("Initialising additional VamsasSession instance");
172       log.debug("Attached to VamsasSession in "+sessionDir1);
173       //} 
174     } else {
175       // start from scratch
176       if (!sessionDir1.mkdir())
177         throw new IOException("Failed to make VamsasSession directory in "+sessionDir1);
178       createSessionFiles();
179       initSessionObjects();
180       slog.debug("Session directory created.");
181       log.debug("Initialised VamsasSession in "+sessionDir1);
182     }
183   }
184   /**
185    * tests presence of existing sessionfiles files in dir
186    * @param dir
187    * @return
188    */
189   private boolean checkSessionFiles(File dir) throws IOException {
190     File c_file = new File(dir,CLIENT_LIST);
191     File v_doc = new File(dir,VAMSAS_OBJ);
192     if (c_file.exists() && v_doc.exists())
193       return true;
194     return false;
195   }
196   /**
197    * create new empty files in dir
198    *
199    */
200   private void createSessionFiles() throws IOException {
201     if (sessionDir==null)
202       throw new IOException("Invalid call to createSessionFiles() with null sessionDir");
203     File c_file = new File(sessionDir,CLIENT_LIST);
204     File v_doc = new File(sessionDir,VAMSAS_OBJ);
205     if (!c_file.exists() && c_file.createNewFile())
206       log.debug("Created new ClientFile "+c_file); // don't care if this works or not
207     if (!v_doc.exists() && v_doc.createNewFile())
208       log.debug("Created new Vamsas Session Document File "+v_doc); 
209   }
210   /**
211    * construct SessionFile objects and watchers for each
212    */
213   private void initSessionObjects() throws IOException {
214     createSessionFiles();
215     if (clist!=null || vamArchive!=null)
216       throw new IOException("initSessionObjects called for initialised VamsasSession object.");
217     clist = new ClientsFile(new File(sessionDir,CLIENT_LIST));
218     vamArchive = new VamsasFile(new File(sessionDir,VAMSAS_OBJ));
219     storedocfile=new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
220     initLog();
221   }
222   /**
223    * make a new watcher object for the clientFile
224    * @return new ClientFile watcher instance
225    */
226   public FileWatcher getClientWatcher() {
227     return new FileWatcher(clist.sessionFile);
228   }
229   /**
230    * make a new watcher object for the vamsas Document
231    * @return new ClientFile watcher instance
232    */
233   public FileWatcher getDocWatcher() {
234     return new FileWatcher(vamArchive.sessionFile);
235   }
236   FileWatcher store_doc_file=null;
237   public ClientsFile storedocfile=null;
238   /**
239    * make a new watcher object for the messages file
240    * @return new watcher instance
241    */
242   public FileWatcher getStoreWatcher() {
243     return new FileWatcher(new File(sessionDir,CLOSEANDSAVE_FILE));
244
245   }
246   /**
247    * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
248    * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
249    * @param client
250    * @param user
251    * @return
252    */
253   public void addStoreDocumentRequest(ClientHandle client, UserHandle user) throws IOException {
254     // TODO: replace this with clientsFile mechanism
255     SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
256     while (!sfw.lockFile())
257       log.debug("Trying to get lock for "+CLOSEANDSAVE_FILE);
258     RandomAccessFile sfwfile=sfw.fileLock.getRaFile();
259     sfwfile.setLength(0); // wipe out any old info.
260     // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
261     sfwfile.writeUTF(client.getClientUrn()+":"+user.getFullName()+"@"+user.getOrganization());
262     sfw.unlockFile();
263     if (store_doc_file!=null)
264       store_doc_file.setState();
265     slog.info("FinalizeAppData request from "+user.getFullName()+" using "+client.getClientUrn()+"");
266   }
267   /**
268    * create a new session with an existing vamsas Document - by copying it into the session.
269    * @param archive
270    */
271   public void setVamsasDocument(File archive) throws IOException {
272     log.debug("Transferring vamsas data from "+archive+" to session:"+vamArchive.sessionFile);
273     SessionFile xtantdoc = new SessionFile(archive);
274     vamArchive.updateFrom(null, xtantdoc);
275     // LATER: decide if session archive provenance should be updated to reflect access.
276     // TODO: soon! do a proper import objects from external file 
277     log.debug("Transfer complete.");
278   }
279   /**
280    * write session as a new vamsas Document (this will overwrite any existing file without warning)
281    * TODO: test
282    * TODO: verify that lock should be released for vamsas document.
283    * @param destarchive
284    */
285   protected void writeVamsasDocument(File destarchive, Lock extlock) throws IOException {
286     log.debug("Transferring vamsas data from "+vamArchive.sessionFile+" to session:"+destarchive);
287     SessionFile newdoc = new SessionFile(destarchive);
288     if (extlock==null && !vamArchive.lockFile())
289       while (!vamArchive.lockFile())
290         log.info("Trying to get lock for "+vamArchive.sessionFile);
291     // TODO: LATER: decide if a provenance entry should be written in the exported document recording the export from the session
292     newdoc.updateFrom(extlock, vamArchive);
293     // LATER: LATER: fix use of updateFrom for file systems where locks cannot be made (because they don't have a lockManager, ie NFS/Unix, etc).
294     vamArchive.unLock();
295     newdoc.unlockFile();
296     log.debug("Transfer complete.");
297   }
298   /**
299          * extant archive IO handler
300          */
301   VamsasArchive _va=null;
302   /**
303    * Creates a VamsasArchive Vobject for accessing and updating document
304    * Note: this will lock the Vamsas Document for exclusive access to the client.
305    * @return session vamsas document
306    * @throws IOException if locks fail or vamsas document read fails.
307    */
308   protected VamsasArchive getVamsasDocument() throws IOException {
309     // check we haven't already done this once - probably should be done by caller
310     if (_va!=null)
311       return _va;
312     // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
313     long tries=5000;
314     while (vamArchive.getLock()==null && --tries>0) {
315 //       Thread.sleep(1);
316         log.debug("Trying to get a document lock for the "+tries+"'th time.");
317       }
318     if (tries==0) 
319       throw new IOException("Failed to get lock for vamsas archive.");
320       
321     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true, vamArchive);
322
323     return va;
324   }
325   /**
326    * Unlocks the vamsas archive session document after it has been closed.
327    * @throws IOException
328    */
329   protected void unlockVamsasDocument() throws IOException {
330     if (_va!=null)
331       _va.closeArchive();
332     _va=null;
333     if (vamArchive!=null)
334       vamArchive.unLock();
335     
336   }
337   /**
338    * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
339    * @see java.io.File.createTempFile
340    * @param pref Prefix for name
341    * @param suff Suffix for name
342    * @return SessionFile object configured for the new file (of length zero)
343    * @throws IOException
344    */
345   protected SessionFile getTempSessionFile(String pref, String suff) throws IOException {
346     File tfile = File.createTempFile(pref,suff,sessionDir);
347     SessionFile tempFile = new SessionFile(tfile);
348     return tempFile;
349   }
350   
351   /**
352    * add a IClient to the session
353    * 
354    * add the client to the client list file
355    * @param client client to add to the session
356    */
357   protected void addClient(IClient client)
358   {
359     if (client == null)
360       slog.error("Try to add a null client to the session ");
361     else {
362       log.debug("Adding client "+client.getClientHandle().getClientUrn());
363       getClientWatcherElement().haltWatch();
364       clist.addClient(client.getClientHandle());
365       getClientWatcherElement().enableWatch();
366       log.debug("Added.");
367     }
368   }
369   
370 /**
371  *  
372  * removes a client from the current session
373  *  removes the client from the session client list
374  *  if the client is the last one from the session  (ClientList), the current session is removed 
375  *  from active session list.
376  *  
377  * @param client client to remove
378  */
379   protected void removeClient(IClient client)
380   {
381     if (client == null)
382       {
383         log.error("Null client passed to removeClient");
384         return;
385       }
386     SessionFileWatcherElement cwe=getClientWatcherElement();
387     if (cwe!=null && cwe.isWatchEnabled()) {
388       cwe.haltWatch();
389     };
390     clist.removeClient(client.getClientHandle(),null);
391     if (this.clist.retrieveClientList() == null|| this.clist.retrieveClientList().length<1)
392       {//assume it is the last client has been removed shutting down session
393         slog.info("last client removed: removing session");
394         log.debug("last client removed: removing session");
395         this.getSessionManager().removeSession(client.getSessionHandle());
396       }
397     else
398     {
399       int active=clist.retrieveClientList().length;
400       log.debug("Still "+active+" active clients");
401       slog.info("Still "+active+" active clients");
402     }
403     if (cwe!=null) {
404       cwe.enableWatch();
405     }
406   }
407 /**
408  * @return the sessionManager
409  */
410 protected SimpleSessionManager getSessionManager() {
411   return sessionManager;
412 }
413 /**
414  * @param sessionManager the sessionManager to set
415  */
416 protected void setSessionManager(SimpleSessionManager sessionManager) {
417   this.sessionManager = sessionManager;
418 }
419 public ClientsFile getStoreDocFile() {
420   if (storedocfile==null) {
421     
422   }
423   return storedocfile;
424 }
425 SessionFileWatcherElement clistWatchElement=null;
426 public SessionFileWatcherElement getClientWatcherElement() {
427   if (clistWatchElement==null) {
428     clistWatchElement=new SessionFileWatcherElement(clist,null);
429   }
430   return clistWatchElement;
431 }
432 /**
433  * writes a vector of vorba Ids to the session.
434  * @param modObjects 
435 public void setModObjectList(Vector modObjects) {
436   log.debug("Writing "+modObjects.size()+" ids to ModObjectList");
437   // TODO Auto-generated method stub
438 }
439 **
440  * get current list of modified objects.
441  * @return null or Vector of objects
442  *
443 public Vector getModObjectList() {
444   log.debug("Reading modObjectList");
445   return null;
446 }
447 */
448 }
449
450