implement low level functions for initialising a new vamsas session with a copy of...
[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       log.debug("Raising request-to-save event");
615       client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
616       client.evgen._raise(Events.SESSION_SHUTDOWN, null, client
617           .getSessionHandle(), null);
618     }
619     // cwe.haltWatch();
620     client.evgen.stopWatching();
621     try {
622       log.debug("Attempting to release active client locks");
623       client.releaseActiveClientFile();
624     } catch (IOException e) {
625       log.error("error during active file client release");
626     }
627     tidyUp();
628     if (closeSession) {
629       log.debug("Last active client: closing session");
630       log.info("Closing session");
631       getSessionManager().removeSession(client.getSessionHandle());
632     }
633   }
634
635   /**
636    * close every file and stop.
637    */
638   private void tidyUp() {
639     if (clist != null)
640       clist.unlockFile();
641     clist = null;
642     storedocfile.unlockFile();
643     storedocfile = null;
644     closeSessionLog();
645   }
646
647   private boolean isLastActiveClient(SimpleClient client) {
648     log.debug("Testing if current client is the last one.");
649     log
650         .debug("current client lockfile is '" + client.getClientlockFile()
651             + "'");
652     boolean noOtherActiveClient = true;
653     // create, if need, subdirectory to contain client files
654     File clientlockFileDir = new File(this.sessionDir, clientFileDirectory);
655     if (!clientlockFileDir.exists()) {
656       log
657           .error("Something wrong the active client file does not exits... should not happen");
658       return false;
659     }
660
661     try {
662
663       // no check every file in the directory and try to get lock on it.
664       File[] clientFiles = clientlockFileDir.listFiles();
665       if (clientFiles == null || clientFiles.length == 0) {// there is not file
666                                                             // on the directory.
667                                                             // the current
668                                                             // client should be
669                                                             // the last one.
670         return true;
671       }
672
673       for (int i = clientFiles.length - 1; i > -1 && noOtherActiveClient; i--) {
674         File clientFile = clientFiles[i];
675         log.debug("testing file for lock: " + clientFile.getAbsolutePath());
676         if (client.getClientLock().isTargetLockFile(clientFile)) {
677           log.debug("current client file found");
678           continue;
679         }
680         if (clientFile != null && clientFile.exists()) {
681           try {
682             log.debug("Try to acquire a lock on the file");
683             // Get a file channel for the file
684             FileChannel channel = new RandomAccessFile(clientFile, "rw")
685                 .getChannel();
686
687             // Use the file channel to create a lock on the file.
688             // This method blocks until it can retrieve the lock.
689             // java.nio.channels.FileLock activeClientFilelock = channel.lock();
690
691             // Try acquiring the lock without blocking. This method returns
692             // null or throws an exception if the file is already locked.
693             try {
694               java.nio.channels.FileLock activeClientFilelock = channel
695                   .tryLock();
696
697               // the lock has been acquired.
698               // the file was not lock and so the corresponding application
699               // seems to have die
700               if (activeClientFilelock != null) {
701                 log
702                     .debug("lock obtained : file must be from a crashed application");
703
704                 activeClientFilelock.release();
705                 log.debug("lock released");
706
707                 channel.close();
708                 log.debug("channel closed");
709
710                 // delete file
711                 clientFile.delete();
712                 log.debug("crashed application file deleted");
713
714               } else {
715                 noOtherActiveClient = false;
716                 log.debug("lock not obtained : another application is active");
717               }
718             } catch (OverlappingFileLockException e) {
719               // File is already locked in this thread or virtual machine
720               // that the expected behaviour
721               log.debug("lock not accessible ", e);
722             }
723           } catch (Exception e) {
724             log.debug("error during lock testing ", e);
725           }
726         }
727       }
728
729     } catch (Exception e) {
730       log.error("error during counting active clients");
731     }
732     return noOtherActiveClient;
733   }
734
735   /**
736    * Handler for the client watcher. after a client have been removed
737    * 
738    * Checks if the client is not the last active one.
739    * 
740    * If (the current client is not in the client list readd it;)
741    */
742   private class RemoveClientWatchCallBack implements WatcherCallBack {
743
744     private SimpleClient client;
745
746     private boolean manualCheckOfClientCount = false;
747
748     /**
749      * Inits the handler with the client to check in the list
750      * 
751      * @param client
752      *          client to monitor in the client list
753      */
754     protected RemoveClientWatchCallBack(SimpleClient client) {
755       this.client = client;
756     }
757
758     /**
759      * If the client list is modified, checks if the current is still in the
760      * list. otherwise, readds ti.
761      * 
762      * @return true to enable watcher, or false to disable it in future
763      *         WatcherThread cycles.
764      */
765     public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
766       // if lock is null, no client has been added since last, clear.
767       // the client is then the last client
768       if (client != null) {
769
770         if (lock == null) {
771
772           // checks if the client is not already in the lists
773           // ClientHandle[] cl =
774           // clist.retrieveClientList();//lock);//clist.retrieveClientList();
775
776           boolean islastClient = true;
777           if (manualCheckOfClientCount) {
778             log.debug("manual checking of count of client");
779             // checks if the client is not already in the lists
780             ClientHandle[] cl = clist.retrieveClientList();// lock);//clist.retrieveClientList();
781             if (cl == null || cl.length < 1)
782             // {//no client has registered as active
783             {
784               islastClient = true;
785               log.debug("list is empty");
786             } else
787               islastClient = false;
788             log.debug("list is not empty");
789           }
790           // if(cl == null || cl.length<1 )
791           // {//no client has registered as active
792           if (islastClient) {
793             // the client is the last one, so close current session
794             log.info("last client removed: closing session");
795             closeSession(client);
796           }
797         } else {
798           log.debug("not the last client found ");
799           // ask to the client to cpoy application data into the document
800           // client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null,
801           // client,null);
802
803           // / }
804
805         }
806         log.debug("Stopping EventGenerator..");
807         // TODO: ensure ClientsFile lock is really released!!
808         // clist.unlockFile();
809         client.evgen.stopWatching();
810       }
811       watcher.setHandler(null);// Do not check if the client is the last
812                                 // client. watcher will shutdown anyway
813       // watcher.haltWatch();
814       // watcher.
815       return false;
816     }
817   }
818
819   /**
820    * closes the current session, and send an event to the last client to close
821    * the document
822    * 
823    * @param client
824    *          the last client of the client
825    */
826   private void closeSession(SimpleClient client) {
827     // close document
828     client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
829     log.debug("close document request done");
830     closeSession(client.getSessionHandle());
831   }
832
833   /**
834    * CLoses the current session
835    * 
836    * @param sessionHandle
837    *          sessionHandle of the session to remove
838    */
839   private void closeSession(SessionHandle sessionHandle) {
840     getSessionManager().removeSession(sessionHandle);
841     log.debug("Session removed");
842   }
843
844   /**
845    * @return the sessionManager
846    */
847   protected SimpleSessionManager getSessionManager() {
848     return sessionManager;
849   }
850
851   /**
852    * @param sessionManager
853    *          the sessionManager to set
854    */
855   protected void setSessionManager(SimpleSessionManager sessionManager) {
856     this.sessionManager = sessionManager;
857   }
858
859   public ClientsFile getStoreDocFile() {
860     if (storedocfile == null) {
861
862     }
863     return storedocfile;
864   }
865
866   ClientSessionFileWatcherElement clistWatchElement = null;
867
868   /**
869    * get or create a watcher on clist.
870    * 
871    * @return the contents of clistWatchElement or initialise it
872    */
873   public ClientSessionFileWatcherElement getClientWatcherElement() {
874     if (clistWatchElement == null) {
875       clistWatchElement = new ClientSessionFileWatcherElement(clist, null);
876     }
877     return clistWatchElement;
878   }
879 }