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