Removed clientfile watcher elements in attempt to remove any locks after client is...
[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.FileNotFoundException;
5 import java.io.IOException;
6 import java.io.RandomAccessFile;
7 import java.nio.channels.FileChannel;
8 import java.nio.channels.OverlappingFileLockException;
9
10 import org.apache.commons.logging.Log;
11 import org.apache.commons.logging.LogFactory;
12 import org.apache.log4j.FileAppender;
13 import org.apache.log4j.Logger;
14 import org.apache.log4j.PatternLayout;
15
16 import uk.ac.vamsas.client.ClientHandle;
17 import uk.ac.vamsas.client.Events;
18 import uk.ac.vamsas.client.IClient;
19 import uk.ac.vamsas.client.SessionHandle;
20 import uk.ac.vamsas.client.UserHandle;
21
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   /**
82    * session file storing the last_stored_stat data 
83    */
84   public static final String MODIFIEDDOC_FILE = "modified";
85
86   private SimpleSessionManager sessionManager = null;
87
88   /**
89    * Count of cycles before considering the current client as the last one of the session (if no other client registered as active )
90    */
91   private final int watchCycleCountBeforeLastClient = 1220;
92
93   /**
94    * time between checking 
95    */
96   public int WATCH_SLEEP = 30;
97
98   protected String clientFileDirectory = "clients";
99
100   /**
101    * called to clear update flag after a successful offline storage event
102    */
103   protected void clearUnsavedFlag() {
104     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
105         MODIFIEDDOC_FILE));
106     if (!laststored.clearFlag())
107       log.warn("Unsaved flag was not cleared for " + sessionDir);
108   }
109
110   /**
111    * called to indicate session document has been modified.
112    *
113    */
114   protected void setUnsavedFlag() {
115     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
116         MODIFIEDDOC_FILE));
117     if (!laststored.setFlag())
118       log.warn("Couldn't set the Unsaved flag for " + sessionDir);
119   }
120
121   /**
122    * 
123    * @return true if session document has been modified since last offline storage event 
124    */
125   protected boolean getUnsavedFlag() {
126     SessionFlagFile laststored = new SessionFlagFile(new File(sessionDir,
127         MODIFIEDDOC_FILE));
128     return laststored.checkFlag();
129   }
130
131   /**
132    * log file location
133    */
134   public static final String SESSION_LOG = "Log.txt";
135
136   private static Log log = LogFactory.getLog(VamsasSession.class);
137
138   protected Logger slog = Logger.getLogger("uk.ac.vamsas.client.SessionLog");
139   /**
140    * the appender that writes to the log file inside the session's directory.
141    */
142   private FileAppender slogAppender=null;
143   /**
144    * setup the sessionLog using Log4j.
145    * @throws IOException
146    */
147   private void initLog() throws IOException {
148     // TODO: fix session event logging
149     // LATER: make dedicated appender format for session log.
150     /*Appender app = slog.getAppender("log4j.appender.SESSIONLOG");
151     // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
152     // slog.addAppender(new FileAppender(app.getLayout(), new File(sessionDir, SESSION_LOG).getAbsolutePath()));
153     for (Enumeration e = slog.getAllAppenders() ; e.hasMoreElements() ;) {
154       System.out.println(e.nextElement());
155
156     }*/
157
158     if (slog != null) {
159       File sessionLogFile = new File(this.sessionDir, SESSION_LOG);
160       slog.addAppender(slogAppender = new FileAppender(new PatternLayout(
161           "%-4r [%t] %-5p %c %x - %m%n"), sessionLogFile.getAbsolutePath(),
162           true));
163     } else {
164       log.info("No appender for SessionLog");
165     }
166   }
167   private void closeSessionLog() {
168     if (slog!=null)
169     {
170       if (slogAppender!=null)
171       {
172         slog.removeAppender(slogAppender);
173         slogAppender.close();
174         slogAppender = null;
175       }
176     }
177   }
178
179   /**
180    * the sessionDir is given as the session location for new clients.
181    */
182   protected File sessionDir;
183
184   /**
185    * holds the list of attached clients
186    */
187   ClientsFile clist;
188
189   public static final String CLIENT_LIST = "Clients.obj";
190
191   /**
192    * holds the data
193    */
194   VamsasFile vamArchive;
195
196   public static final String VAMSAS_OBJ = "VamDoc.jar";
197
198   /**
199    * sets up the vamsas session files and watchers in sessionDir
200    * @param sessionDir1
201    */
202   protected VamsasSession(File sessionDir1) throws IOException {
203     if (sessionDir1 == null)
204       throw new Error("Null directory for VamsasSession.");
205     if (sessionDir1.exists()) {
206       if (!sessionDir1.isDirectory() || !sessionDir1.canWrite()
207           || !sessionDir1.canRead())
208         throw new IOException("Cannot access '" + sessionDir1
209             + "' as a read/writable Directory.");
210       if (!checkSessionFiles(sessionDir1))
211         log
212             .warn("checkSessionFiles() returned false. Possible client implementation error");
213       this.sessionDir = sessionDir1;
214       initSessionObjects();
215       slog.debug("Initialising additional VamsasSession instance");
216       log.debug("Attached to VamsasSession in " + sessionDir1);
217       //} 
218     } else {
219       // start from scratch
220       if (!sessionDir1.mkdir())
221         throw new IOException("Failed to make VamsasSession directory in "
222             + sessionDir1);
223       createSessionFiles();
224       initSessionObjects();
225       slog.debug("Session directory created.");
226       log.debug("Initialised VamsasSession in " + sessionDir1);
227     }
228   }
229
230   /**
231    * tests presence of existing sessionfiles files in dir
232    * @param dir
233    * @return
234    */
235   private boolean checkSessionFiles(File dir) throws IOException {
236     File c_file = new File(dir, CLIENT_LIST);
237     File v_doc = new File(dir, VAMSAS_OBJ);
238     if (c_file.exists() && v_doc.exists())
239       return true;
240     return false;
241   }
242
243   /**
244    * create new empty files in dir
245    *
246    */
247   private void createSessionFiles() throws IOException {
248     if (sessionDir == null)
249       throw new IOException(
250           "Invalid call to createSessionFiles() with null sessionDir");
251     File c_file = new File(sessionDir, CLIENT_LIST);
252     File v_doc = new File(sessionDir, VAMSAS_OBJ);
253     if (!c_file.exists() && c_file.createNewFile())
254       log.debug("Created new ClientFile " + c_file); // don't care if this works or not
255     if (!v_doc.exists() && v_doc.createNewFile())
256       log.debug("Created new Vamsas Session Document File " + v_doc);
257   }
258
259   /**
260    * construct SessionFile objects and watchers for each
261    */
262   private void initSessionObjects() throws IOException {
263     createSessionFiles();
264     if (clist != null || vamArchive != null)
265       throw new IOException(
266           "initSessionObjects called for initialised VamsasSession object.");
267     clist = new ClientsFile(new File(sessionDir, CLIENT_LIST));
268     vamArchive = new VamsasFile(new File(sessionDir, VAMSAS_OBJ));
269     storedocfile = new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
270     initLog();
271   }
272
273   /**
274    * make a new watcher object for the clientFile
275    * @return new ClientFile watcher instance
276    */
277   public FileWatcher getClientWatcher() {
278     return new FileWatcher(clist.sessionFile);
279   }
280
281   /**
282    * make a new watcher object for the vamsas Document
283    * @return new ClientFile watcher instance
284    */
285   public FileWatcher getDocWatcher() {
286     return new FileWatcher(vamArchive.sessionFile);
287   }
288
289   FileWatcher store_doc_file = null;
290
291   public ClientsFile storedocfile = null;
292
293   /**
294    * make a new watcher object for the messages file
295    * @return new watcher instance
296    */
297   public FileWatcher getStoreWatcher() {
298     return new FileWatcher(new File(sessionDir, CLOSEANDSAVE_FILE));
299
300   }
301
302   /**
303    * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
304    * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
305    * @param client
306    * @param user
307    * @return
308    */
309   public void addStoreDocumentRequest(ClientHandle client, UserHandle user)
310       throws IOException {
311     // TODO: replace this with clientsFile mechanism
312     SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
313     while (!sfw.lockFile())
314       log.debug("Trying to get lock for " + CLOSEANDSAVE_FILE);
315     RandomAccessFile sfwfile = sfw.fileLock.getRaFile();
316     sfwfile.setLength(0); // wipe out any old info.
317     // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
318     sfwfile.writeUTF(client.getClientUrn() + ":" + user.getFullName() + "@"
319         + user.getOrganization());
320     sfw.unlockFile();
321     if (store_doc_file != null)
322       store_doc_file.setState();
323     slog.info("FinalizeAppData request from " + user.getFullName() + " using "
324         + client.getClientUrn() + "");
325   }
326
327   /**
328    * create a new session with an existing vamsas Document - by copying it into the session.
329    * @param archive
330    */
331   public void setVamsasDocument(File archive) throws IOException {
332     log.debug("Transferring vamsas data from " + archive + " to session:"
333         + vamArchive.sessionFile);
334     SessionFile xtantdoc = new SessionFile(archive);
335     vamArchive.updateFrom(null, xtantdoc);
336     // LATER: decide if session archive provenance should be updated to reflect access.
337     // TODO: soon! do a proper import objects from external file 
338     log.debug("Transfer complete.");
339   }
340
341   /**
342    * write session as a new vamsas Document (this will overwrite any existing file without warning)
343    * TODO: test
344    * TODO: verify that lock should be released for vamsas document.
345    * @param destarchive
346    */
347   protected void writeVamsasDocument(File destarchive, Lock extlock)
348       throws IOException {
349     log.debug("Transferring vamsas data from " + vamArchive.sessionFile
350         + " to session:" + destarchive);
351     SessionFile newdoc = new SessionFile(destarchive);
352     if (extlock == null && !vamArchive.lockFile())
353       while (!vamArchive.lockFile())
354         log.info("Trying to get lock for " + vamArchive.sessionFile);
355     // TODO: LATER: decide if a provenance entry should be written in the exported document recording the export from the session
356     newdoc.updateFrom(extlock, vamArchive);
357     // 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).
358     vamArchive.unLock();
359     newdoc.unlockFile();
360     log.debug("Transfer complete.");
361   }
362
363   /**
364    * extant archive IO handler
365    */
366   VamsasArchive _va = null;
367
368   /**
369    * Creates a VamsasArchive Vobject for accessing and updating document
370    * Note: this will lock the Vamsas Document for exclusive access to the client.
371    * @return session vamsas document
372    * @throws IOException if locks fail or vamsas document read fails.
373    */
374   protected VamsasArchive getVamsasDocument() throws IOException {
375     // check we haven't already done this once - probably should be done by caller
376     if (_va != null)
377       return _va;
378     // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
379     long tries = 5000;
380     while (vamArchive.getLock() == null && --tries > 0) {
381       //       Thread.sleep(1);
382       log.debug("Trying to get a document lock for the " + tries + "'th time.");
383     }
384     if (tries == 0)
385       throw new IOException("Failed to get lock for vamsas archive.");
386
387     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true,
388         vamArchive);
389
390     return va;
391   }
392
393   /**
394    * Unlocks the vamsas archive session document after it has been closed.
395    * @throws IOException
396    */
397   protected void unlockVamsasDocument() throws IOException {
398     if (_va != null)
399       _va.closeArchive();
400     _va = null;
401     if (vamArchive != null)
402       vamArchive.unLock();
403
404   }
405
406   /**
407    * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
408    * @see java.io.File.createTempFile
409    * @param pref Prefix for name
410    * @param suff Suffix for name
411    * @return SessionFile object configured for the new file (of length zero)
412    * @throws IOException
413    */
414   protected SessionFile getTempSessionFile(String pref, String suff)
415       throws IOException {
416     File tfile = File.createTempFile(pref, suff, sessionDir);
417     SessionFile tempFile = new SessionFile(tfile);
418     return tempFile;
419   }
420
421   /**
422    * add a IClient to the session
423    * 
424    * add the client to the client list file
425    * @param client client to add to the session
426    */
427   protected void addClient(SimpleClient client) {
428     if (client == null)
429       slog.error("Try to add a null client to the session ");
430     else {
431       log.debug("Adding client " + client.getClientHandle().getClientUrn());
432       getClientWatcherElement().haltWatch();
433       clist.addClient(client.getClientHandle());
434
435       log.debug("Added.");
436       log.debug("Register Client as Active.");
437       try {
438         client.createActiveClientFile();
439       } catch (IOException e) {
440         log.debug("Error during  active client file creation.");
441       }
442       //tracks modification to the client list and readds client to the list
443       getClientWatcherElement().setHandler(new AddClientWatchCallBack(client));
444       getClientWatcherElement().enableWatch();
445
446     }
447   }
448
449   /**
450    * Handler for the client watcher.
451    * 
452    * If (the current client is not in the client list, it is added again;)
453    */
454   private class AddClientWatchCallBack implements WatcherCallBack {
455
456     private SimpleClient client;
457
458     /**
459      *Inits the handler with the client to check in the list
460      * @param client client to monitor in the client list
461      */
462     protected AddClientWatchCallBack(SimpleClient client) {
463       this.client = client;
464     }
465
466     /**
467      * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
468      * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
469      */
470     public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
471       boolean isWatchEnable = watcher.isWatchEnabled();
472       if (lock == null)//no update on the list
473         return isWatchEnable;
474       log.debug("change on the client list ");
475       if (client != null) {
476
477         //checks if the client is not already in the lists
478         ClientHandle[] cl = clist.retrieveClientList(lock);//clist.retrieveClientList();
479         boolean found = false;
480         if (cl != null) {
481           for (int chi = cl.length - 1; !found && chi > -1; chi--) {
482             found = cl[chi].equals(this.client.getClientHandle());
483           }
484
485         }
486         if (!found) {
487           log.debug("client not in the list ");
488           if (log.isDebugEnabled())
489             log
490                 .debug("the client has not been found in the list. Adding it again :"
491                     + cl);
492           addClient(client);
493         } else
494           log.debug("client is in the list");
495
496       }
497       log.debug("isWatchEnable " + isWatchEnable);
498       return isWatchEnable;
499     }
500   }
501
502   /**
503    *  
504    * removes a client from the current session
505    *  removes the client from the session client list
506    *  if the client is the last one from the session  (ClientList), the current session is removed 
507    *  from active session list.
508    *  
509    *  The active should add them self to the client list. To insure to close the session,when the current client is the lact active client,
510    *  clears  the list of clients and when two cycles to insure there is no more active client, that otherwise would have readd themself to the list
511    *  
512    * @param client client to remove
513    */
514   protected void removeClient(SimpleClient client)//IClient client)
515   {
516     if (client == null) {
517       log.error("Null client passed to removeClient");
518       return;
519     }
520     //ClientSessionFileWatcherElement cwe=getClientWatcherElement();
521     //if (cwe!=null && cwe.isWatchEnabled()) {
522     //  cwe.haltWatch();
523     //};
524     //set handler to check is the the last active client of the session
525     //Wait for several watchers cycle to see if the current client was the last client active in the session.
526     //if yes, close the session
527
528     // getClientWatcherElement().setHandler(new RemoveClientWatchCallBack (client));
529     // getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);
530     log.info("remove client from list");
531     if (clistWatchElement!=null)
532     {
533       clistWatchElement.haltWatch();
534       clistWatchElement.watched.unlockFile();
535     }
536     //clist.clearList();
537     //clist.unlockFile();
538     log.info("list cleared");
539     //if (cwe!=null) {
540     // cwe.enableWatch();
541
542     log.debug("Stopping EventGenerator..");
543     client.evgen.stopWatching();
544     // cwe.setHandler(null);
545     // ask to the client to copy application data into the document
546     client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client, null);
547     boolean closeSession = isLastActiveClient(client);
548     if (closeSession) {
549       log.debug("Raising request-to-save event");
550       client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
551       client.evgen._raise(Events.SESSION_SHUTDOWN, null, client
552           .getSessionHandle(), null);
553     }
554     //cwe.haltWatch();
555     client.evgen.stopWatching();
556     try {
557       log.debug("Attempting to release active client locks");
558       client.releaseActiveClientFile();
559     } catch (IOException e) {
560       log.error("error during active file client release");
561     }
562     tidyUp();
563     if (closeSession) {
564       log.debug("Last active client: closing session");
565       log.info("Closing session");
566       getSessionManager().removeSession(client.getSessionHandle());
567     }
568   }
569
570   /**
571    * close every file and stop.
572    */
573   private void tidyUp() {
574     if (clist != null)
575       clist.unlockFile();
576     clist = null;
577     storedocfile.unlockFile();
578     storedocfile = null;
579     closeSessionLog();
580   }
581
582   private boolean isLastActiveClient(SimpleClient client) {
583     log.debug("Testing if current client is the last one.");
584     log
585         .debug("current client lockfile is '" + client.getClientlockFile()
586             + "'");
587     boolean noOtherActiveClient = true;
588     //create, if need,  subdirectory to contain client files
589     File clientlockFileDir = new File(this.sessionDir, clientFileDirectory);
590     if (!clientlockFileDir.exists()) {
591       log
592           .error("Something wrong the active client file does not exits... should not happen");
593       return false;
594     }
595
596     try {
597
598       //no check every file in the directory and try to get lock on it.
599       File[] clientFiles = clientlockFileDir.listFiles();
600       if (clientFiles == null || clientFiles.length == 0) {//there is not file on the directory. the current client should be the last one.
601         return true;
602       }
603
604       for (int i = clientFiles.length - 1; i > -1 && noOtherActiveClient; i--) {
605         File clientFile = clientFiles[i];
606         log.debug("testing file for lock: " + clientFile.getAbsolutePath());
607         if (client.getClientLock().isTargetLockFile(clientFile)) {
608           log.debug("current client file found");
609           continue;
610         }
611         if (clientFile != null && clientFile.exists()) {
612           try {
613             log.debug("Try to acquire a lock on the file");
614             // Get a file channel for the file
615             FileChannel channel = new RandomAccessFile(clientFile, "rw")
616                 .getChannel();
617
618             // Use the file channel to create a lock on the file.
619             // This method blocks until it can retrieve the lock.
620             //  java.nio.channels.FileLock activeClientFilelock = channel.lock();
621
622             // Try acquiring the lock without blocking. This method returns
623             // null or throws an exception if the file is already locked.
624             try {
625               java.nio.channels.FileLock activeClientFilelock = channel
626                   .tryLock();
627
628               //the lock has been acquired. 
629               //the file was not lock and so the corresponding application seems to have die
630               if (activeClientFilelock != null) {
631                 log
632                     .debug("lock obtained : file must be from a crashed application");
633
634                 activeClientFilelock.release();
635                 log.debug("lock released");
636
637                 channel.close();
638                 log.debug("channel closed");
639
640                 //delete file 
641                 clientFile.delete();
642                 log.debug("crashed application file deleted");
643
644               } else {
645                 noOtherActiveClient = false;
646                 log.debug("lock not obtained : another application is active");
647               }
648             } catch (OverlappingFileLockException e) {
649               // File is already locked in this thread or virtual machine
650               //that the expected behaviour
651               log.debug("lock not accessible ", e);
652             }
653           } catch (Exception e) {
654             log.debug("error during lock testing ", e);
655           }
656         }
657       }
658
659     } catch (Exception e) {
660       log.error("error during counting active clients");
661     }
662     return noOtherActiveClient;
663   }
664
665   /**
666    * Handler for the client watcher. after a client have been removed
667    * 
668    * Checks if the client is not the last active one.
669    * 
670    * If (the current client is not in the client list readd it;)
671    */
672   private class RemoveClientWatchCallBack implements WatcherCallBack {
673
674     private SimpleClient client;
675
676     private boolean manualCheckOfClientCount = false;
677
678     /**
679      *Inits the handler with the client to check in the list
680      * @param client client to monitor in the client list
681      */
682     protected RemoveClientWatchCallBack(SimpleClient client) {
683       this.client = client;
684     }
685
686     /**
687      * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
688      * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
689      */
690     public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
691       // if lock is null, no client has been added since last, clear.
692       //the client is then the last client
693       if (client != null) {
694
695         if (lock == null) {
696
697           //checks if the client is not already in the lists
698           //   ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
699
700           boolean islastClient = true;
701           if (manualCheckOfClientCount) {
702             log.debug("manual checking of count of client");
703             //checks if the client is not already in the lists
704             ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
705             if (cl == null || cl.length < 1)
706             //  {//no client has registered as active 
707             {
708               islastClient = true;
709               log.debug("list is empty");
710             } else
711               islastClient = false;
712             log.debug("list is not empty");
713           }
714           //  if(cl == null || cl.length<1 )
715           //  {//no client has registered as active
716           if (islastClient) {
717             //the client is the last one, so close current session
718             log.info("last client removed: closing session");
719             closeSession(client);
720           }
721         } else {
722           log.debug("not the last client found ");
723           //              ask to the client to cpoy application data into the document
724           //         client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null);
725
726           //   /  }
727
728         }
729         log.debug("Stopping EventGenerator..");
730         // TODO: ensure ClientsFile lock is really released!! clist.unlockFile();
731         client.evgen.stopWatching();
732       }
733       watcher.setHandler(null);//Do not check if the client is the last client. watcher will shutdown anyway
734       //  watcher.haltWatch();
735       // watcher.
736       return false;
737     }
738   }
739
740   /**
741    * closes the current session, 
742    * and send an event to the last client to close the document
743    * @param client the last client of the client
744    */
745   private void closeSession(SimpleClient client) {
746     //   close document 
747     client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
748     log.debug("close document request done");
749     closeSession(client.getSessionHandle());
750   }
751
752   /**
753    * CLoses the current session
754    * @param sessionHandle sessionHandle of the session to remove
755    */
756   private void closeSession(SessionHandle sessionHandle) {
757     getSessionManager().removeSession(sessionHandle);
758     log.debug("Session removed");
759   }
760
761   /**
762    * @return the sessionManager
763    */
764   protected SimpleSessionManager getSessionManager() {
765     return sessionManager;
766   }
767
768   /**
769    * @param sessionManager the sessionManager to set
770    */
771   protected void setSessionManager(SimpleSessionManager sessionManager) {
772     this.sessionManager = sessionManager;
773   }
774
775   public ClientsFile getStoreDocFile() {
776     if (storedocfile == null) {
777
778     }
779     return storedocfile;
780   }
781
782   ClientSessionFileWatcherElement clistWatchElement = null;
783
784   /**
785    * get or create a watcher on clist.
786    * @return the contents of clistWatchElement or initialise it
787    */
788   public ClientSessionFileWatcherElement getClientWatcherElement() {
789     if (clistWatchElement == null) {
790       clistWatchElement = new ClientSessionFileWatcherElement(clist, null);
791     }
792     return clistWatchElement;
793   }
794   /**
795    * writes a vector of vorba Ids to the session.
796    * @param modObjects 
797    public void setModObjectList(Vector modObjects) {
798    log.debug("Writing "+modObjects.size()+" ids to ModObjectList");
799    // TODO Auto-generated method stub
800    }
801    **
802    * get current list of modified objects.
803    * @return null or Vector of objects
804    *
805    public Vector getModObjectList() {
806    log.debug("Reading modObjectList");
807    return null;
808    }
809    */
810 }