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