refactoring to enable initialisation of session from an existing vamsas archive and...
[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 sessionDir1
200    * @param sessionDir1
201    */
202   protected VamsasSession(File sessionDir1) throws IOException {
203     this(sessionDir1, null);
204   }
205   /**
206    * sets up the vamsas session files and watchers in sessionDir1
207    * @param sessionDir1 
208    * @param extVamDoc null or an existing archive to initialise the session with
209    * @throws any IOExceptions from creating session directory and files.
210    * @throws error if both extVamDoc and sessionDir1 already exist (cannot import new data into session in this way)
211    */
212   protected VamsasSession(File sessionDir1, File extVamDoc) throws IOException {
213     if (sessionDir1 == null)
214       throw new Error("Null directory for VamsasSession.");
215     if (sessionDir1.exists()) {
216       if (extVamDoc!=null && extVamDoc.exists())
217         throw new Error("Client Initialisation Error: Cannot join an existing session directory with an  existing vamsas document to import.");
218       if (!sessionDir1.isDirectory() || !sessionDir1.canWrite()
219           || !sessionDir1.canRead())
220         throw new IOException("Cannot access '" + sessionDir1
221             + "' as a read/writable Directory.");
222       if (!checkSessionFiles(sessionDir1))
223         log
224             .warn("checkSessionFiles() returned false. Possible client implementation error");
225       this.sessionDir = sessionDir1;
226       initSessionObjects();
227       slog.debug("Initialising additional VamsasSession instance");
228       log.debug("Attached to VamsasSession in " + sessionDir1);
229       //} 
230     } else {
231       // start from scratch
232       if (!sessionDir1.mkdir())
233         throw new IOException("Failed to make VamsasSession directory in "
234             + sessionDir1);
235       createSessionFiles(extVamDoc);
236       initSessionObjects();
237       slog.debug("Session directory created.");
238       log.debug("Initialised VamsasSession in " + sessionDir1);
239     }
240   }
241
242   /**
243    * tests presence of existing sessionfiles files in dir
244    * @param dir
245    * @return
246    */
247   private boolean checkSessionFiles(File dir) throws IOException {
248     File c_file = new File(dir, CLIENT_LIST);
249     File v_doc = new File(dir, VAMSAS_OBJ);
250     if (c_file.exists() && v_doc.exists())
251       return true;
252     return false;
253   }
254
255   /**
256    * create new empty files in dir
257    *
258    */
259   private void createSessionFiles() throws IOException {
260     createSessionFiles(null);
261   }
262   /**
263    * 
264    * @param extVamDoc null or an existing vamsas document to initialise session with
265    * @throws IOException
266    */
267   private void createSessionFiles(File extVamDoc) throws IOException {
268     if (sessionDir == null)
269       throw new IOException(
270           "Invalid call to createSessionFiles() with null sessionDir");
271     File c_file = new File(sessionDir, CLIENT_LIST);
272     File v_doc = new File(sessionDir, VAMSAS_OBJ);
273     if (!c_file.exists() && c_file.createNewFile())
274       log.debug("Created new ClientFile " + c_file); // don't care if this works or not
275     if (!v_doc.exists())
276       {
277       if (extVamDoc==null)
278         {
279           if (v_doc.createNewFile())
280           {
281             log.debug("Created new Vamsas Session Document File " + v_doc);
282           } else {
283             log.warn("Didn't create Vamsas Session Document file in "+v_doc);
284           }
285         } else {
286           log.debug("Creating new session document from "+extVamDoc);
287           try {
288             SessionFile sesdoc = new SessionFile(new File(sessionDir, VAMSAS_OBJ));
289             SessionFile extdoc = new SessionFile(extVamDoc);
290             sesdoc.updateFrom(null, extdoc);
291           } catch (Exception e)
292           {
293             v_doc.delete();
294             v_doc.createNewFile();
295             log.warn("Problem initialising new session ("+v_doc+") from existing vamsas document ("+extVamDoc+")",e);
296             throw new IOException("Couldn't initialise session from existing vamsas document");
297           }
298           log.debug("Session document initialised from "+extVamDoc);
299         }
300       }
301   }
302
303   /**
304    * construct SessionFile objects and watchers for each
305    */
306   private void initSessionObjects() throws IOException {
307     createSessionFiles();
308     if (clist != null || vamArchive != null)
309       throw new IOException(
310           "initSessionObjects called for initialised VamsasSession object.");
311     clist = new ClientsFile(new File(sessionDir, CLIENT_LIST));
312     vamArchive = new VamsasFile(new File(sessionDir, VAMSAS_OBJ));
313     storedocfile = new ClientsFile(new File(sessionDir, CLOSEANDSAVE_FILE));
314     initLog();
315   }
316
317   /**
318    * make a new watcher object for the clientFile
319    * @return new ClientFile watcher instance
320    */
321   public FileWatcher getClientWatcher() {
322     return new FileWatcher(clist.sessionFile);
323   }
324
325   /**
326    * make a new watcher object for the vamsas Document
327    * @return new ClientFile watcher instance
328    */
329   public FileWatcher getDocWatcher() {
330     return new FileWatcher(vamArchive.sessionFile);
331   }
332
333   FileWatcher store_doc_file = null;
334
335   public ClientsFile storedocfile = null;
336
337   /**
338    * make a new watcher object for the messages file
339    * @return new watcher instance
340    */
341   public FileWatcher getStoreWatcher() {
342     return new FileWatcher(new File(sessionDir, CLOSEANDSAVE_FILE));
343
344   }
345
346   /**
347    * write to the StoreWatcher file to indicate that a storeDocumentRequest has been made.
348    * The local client's storeWatcher FileWatcher object is updated so the initial change is not registered.
349    * @param client
350    * @param user
351    * @return
352    */
353   public void addStoreDocumentRequest(ClientHandle client, UserHandle user)
354       throws IOException {
355     // TODO: replace this with clientsFile mechanism
356     SessionFile sfw = new SessionFile(new File(sessionDir, CLOSEANDSAVE_FILE));
357     while (!sfw.lockFile())
358       log.debug("Trying to get lock for " + CLOSEANDSAVE_FILE);
359     RandomAccessFile sfwfile = sfw.fileLock.getRaFile();
360     sfwfile.setLength(0); // wipe out any old info.
361     // TODO: rationalise what gets written to this file (ie do we want other clients to read the id of the requestor?)
362     sfwfile.writeUTF(client.getClientUrn() + ":" + user.getFullName() + "@"
363         + user.getOrganization());
364     sfw.unlockFile();
365     if (store_doc_file != null)
366       store_doc_file.setState();
367     slog.info("FinalizeAppData request from " + user.getFullName() + " using "
368         + client.getClientUrn() + "");
369   }
370
371   /**
372    * create a new session with an existing vamsas Document - by copying it into the session.
373    * @param archive
374    */
375   public void setVamsasDocument(File archive) throws IOException {
376     log.debug("Transferring vamsas data from " + archive + " to session:"
377         + vamArchive.sessionFile);
378     SessionFile xtantdoc = new SessionFile(archive);
379     vamArchive.updateFrom(null, xtantdoc);
380     // LATER: decide if session archive provenance should be updated to reflect access.
381     // TODO: soon! do a proper import objects from external file 
382     log.debug("Transfer complete.");
383   }
384
385   /**
386    * write session as a new vamsas Document (this will overwrite any existing file without warning)
387    * TODO: test
388    * TODO: verify that lock should be released for vamsas document.
389    * @param destarchive
390    */
391   protected void writeVamsasDocument(File destarchive, Lock extlock)
392       throws IOException {
393     log.debug("Transferring vamsas data from " + vamArchive.sessionFile
394         + " to session:" + destarchive);
395     SessionFile newdoc = new SessionFile(destarchive);
396     if (extlock == null && !vamArchive.lockFile())
397       while (!vamArchive.lockFile())
398         log.info("Trying to get lock for " + vamArchive.sessionFile);
399     // TODO: LATER: decide if a provenance entry should be written in the 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 be made (because they don't have a lockManager, ie NFS/Unix, etc).
402     vamArchive.unLock();
403     newdoc.unlockFile();
404     log.debug("Transfer complete.");
405   }
406
407   /**
408    * extant archive IO handler
409    */
410   VamsasArchive _va = null;
411
412   /**
413    * Creates a VamsasArchive Vobject for accessing and updating document
414    * Note: this will lock the Vamsas Document for exclusive access to the client.
415    * @return session vamsas document
416    * @throws IOException if locks fail or vamsas document read fails.
417    */
418   protected VamsasArchive getVamsasDocument() throws IOException {
419     // check we haven't already done this once - probably should be done by caller
420     if (_va != null)
421       return _va;
422     // patiently wait for a lock on the document. (from ArchiveClient.getUpdateable())
423     long tries = 5000;
424     while (vamArchive.getLock() == null && --tries > 0) {
425       //       Thread.sleep(1);
426       log.debug("Trying to get a document lock for the " + tries + "'th time.");
427     }
428     if (tries == 0)
429       throw new IOException("Failed to get lock for vamsas archive.");
430
431     VamsasArchive va = new VamsasArchive(vamArchive.sessionFile, false, true,
432         vamArchive);
433
434     return va;
435   }
436
437   /**
438    * Unlocks the vamsas archive session document after it has been closed.
439    * @throws IOException
440    */
441   protected void unlockVamsasDocument() throws IOException {
442     if (_va != null)
443       _va.closeArchive();
444     _va = null;
445     if (vamArchive != null)
446       vamArchive.unLock();
447
448   }
449
450   /**
451    * create a uniquely named uk.ac.vamsas.client.simpleclient.ClientsFile.addClient(ClientHandle)ile in the session Directory
452    * @see java.io.File.createTempFile
453    * @param pref Prefix for name
454    * @param suff Suffix for name
455    * @return SessionFile object configured for the new file (of length zero)
456    * @throws IOException
457    */
458   protected SessionFile getTempSessionFile(String pref, String suff)
459       throws IOException {
460     File tfile = File.createTempFile(pref, suff, sessionDir);
461     SessionFile tempFile = new SessionFile(tfile);
462     return tempFile;
463   }
464
465   /**
466    * add a IClient to the session
467    * 
468    * add the client to the client list file
469    * @param client client to add to the session
470    */
471   protected void addClient(SimpleClient client) {
472     if (client == null)
473       slog.error("Try to add a null client to the session ");
474     else {
475       log.debug("Adding client " + client.getClientHandle().getClientUrn());
476       getClientWatcherElement().haltWatch();
477       clist.addClient(client.getClientHandle());
478
479       log.debug("Added.");
480       log.debug("Register Client as Active.");
481       try {
482         client.createActiveClientFile();
483       } catch (IOException e) {
484         log.debug("Error during  active client file creation.");
485       }
486       //tracks modification to the client list and readds client to the list
487       getClientWatcherElement().setHandler(new AddClientWatchCallBack(client));
488       getClientWatcherElement().enableWatch();
489
490     }
491   }
492
493   /**
494    * Handler for the client watcher.
495    * 
496    * If (the current client is not in the client list, it is added again;)
497    */
498   private class AddClientWatchCallBack implements WatcherCallBack {
499
500     private SimpleClient client;
501
502     /**
503      *Inits the handler with the client to check in the list
504      * @param client client to monitor in the client list
505      */
506     protected AddClientWatchCallBack(SimpleClient client) {
507       this.client = client;
508     }
509
510     /**
511      * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
512      * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
513      */
514     public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
515       boolean isWatchEnable = watcher.isWatchEnabled();
516       if (lock == null)//no update on the list
517         return isWatchEnable;
518       log.debug("change on the client list ");
519       if (client != null) {
520
521         //checks if the client is not already in the lists
522         ClientHandle[] cl = clist.retrieveClientList(lock);//clist.retrieveClientList();
523         boolean found = false;
524         if (cl != null) {
525           for (int chi = cl.length - 1; !found && chi > -1; chi--) {
526             found = cl[chi].equals(this.client.getClientHandle());
527           }
528
529         }
530         if (!found) {
531           log.debug("client not in the list ");
532           if (log.isDebugEnabled())
533             log
534                 .debug("the client has not been found in the list. Adding it again :"
535                     + cl);
536           addClient(client);
537         } else
538           log.debug("client is in the list");
539
540       }
541       log.debug("isWatchEnable " + isWatchEnable);
542       return isWatchEnable;
543     }
544   }
545
546   /**
547    *  
548    * removes a client from the current session
549    *  removes the client from the session client list
550    *  if the client is the last one from the session  (ClientList), the current session is removed 
551    *  from active session list.
552    *  
553    *  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,
554    *  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
555    *  
556    * @param client client to remove
557    */
558   protected void removeClient(SimpleClient client)//IClient client)
559   {
560     if (client == null) {
561       log.error("Null client passed to removeClient");
562       return;
563     }
564     //ClientSessionFileWatcherElement cwe=getClientWatcherElement();
565     //if (cwe!=null && cwe.isWatchEnabled()) {
566     //  cwe.haltWatch();
567     //};
568     //set handler to check is the the last active client of the session
569     //Wait for several watchers cycle to see if the current client was the last client active in the session.
570     //if yes, close the session
571
572     // getClientWatcherElement().setHandler(new RemoveClientWatchCallBack (client));
573     // getClientWatcherElement().setTimeoutBeforeLastCycle(this.watchCycleCountBeforeLastClient);
574     log.info("remove client from list");
575     if (clistWatchElement!=null)
576     {
577       clistWatchElement.haltWatch();
578       clistWatchElement.watched.unlockFile();
579     }
580     //clist.clearList();
581     //clist.unlockFile();
582     log.info("list cleared");
583     //if (cwe!=null) {
584     // cwe.enableWatch();
585
586     log.debug("Stopping EventGenerator..");
587     client.evgen.stopWatching();
588     // cwe.setHandler(null);
589     // ask to the client to copy application data into the document
590     client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client, null);
591     boolean closeSession = isLastActiveClient(client);
592     if (closeSession) {
593       log.debug("Raising request-to-save event");
594       client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
595       client.evgen._raise(Events.SESSION_SHUTDOWN, null, client
596           .getSessionHandle(), null);
597     }
598     //cwe.haltWatch();
599     client.evgen.stopWatching();
600     try {
601       log.debug("Attempting to release active client locks");
602       client.releaseActiveClientFile();
603     } catch (IOException e) {
604       log.error("error during active file client release");
605     }
606     tidyUp();
607     if (closeSession) {
608       log.debug("Last active client: closing session");
609       log.info("Closing session");
610       getSessionManager().removeSession(client.getSessionHandle());
611     }
612   }
613
614   /**
615    * close every file and stop.
616    */
617   private void tidyUp() {
618     if (clist != null)
619       clist.unlockFile();
620     clist = null;
621     storedocfile.unlockFile();
622     storedocfile = null;
623     closeSessionLog();
624   }
625
626   private boolean isLastActiveClient(SimpleClient client) {
627     log.debug("Testing if current client is the last one.");
628     log
629         .debug("current client lockfile is '" + client.getClientlockFile()
630             + "'");
631     boolean noOtherActiveClient = true;
632     //create, if need,  subdirectory to contain client files
633     File clientlockFileDir = new File(this.sessionDir, clientFileDirectory);
634     if (!clientlockFileDir.exists()) {
635       log
636           .error("Something wrong the active client file does not exits... should not happen");
637       return false;
638     }
639
640     try {
641
642       //no check every file in the directory and try to get lock on it.
643       File[] clientFiles = clientlockFileDir.listFiles();
644       if (clientFiles == null || clientFiles.length == 0) {//there is not file on the directory. the current client should be the last one.
645         return true;
646       }
647
648       for (int i = clientFiles.length - 1; i > -1 && noOtherActiveClient; i--) {
649         File clientFile = clientFiles[i];
650         log.debug("testing file for lock: " + clientFile.getAbsolutePath());
651         if (client.getClientLock().isTargetLockFile(clientFile)) {
652           log.debug("current client file found");
653           continue;
654         }
655         if (clientFile != null && clientFile.exists()) {
656           try {
657             log.debug("Try to acquire a lock on the file");
658             // Get a file channel for the file
659             FileChannel channel = new RandomAccessFile(clientFile, "rw")
660                 .getChannel();
661
662             // Use the file channel to create a lock on the file.
663             // This method blocks until it can retrieve the lock.
664             //  java.nio.channels.FileLock activeClientFilelock = channel.lock();
665
666             // Try acquiring the lock without blocking. This method returns
667             // null or throws an exception if the file is already locked.
668             try {
669               java.nio.channels.FileLock activeClientFilelock = channel
670                   .tryLock();
671
672               //the lock has been acquired. 
673               //the file was not lock and so the corresponding application seems to have die
674               if (activeClientFilelock != null) {
675                 log
676                     .debug("lock obtained : file must be from a crashed application");
677
678                 activeClientFilelock.release();
679                 log.debug("lock released");
680
681                 channel.close();
682                 log.debug("channel closed");
683
684                 //delete file 
685                 clientFile.delete();
686                 log.debug("crashed application file deleted");
687
688               } else {
689                 noOtherActiveClient = false;
690                 log.debug("lock not obtained : another application is active");
691               }
692             } catch (OverlappingFileLockException e) {
693               // File is already locked in this thread or virtual machine
694               //that the expected behaviour
695               log.debug("lock not accessible ", e);
696             }
697           } catch (Exception e) {
698             log.debug("error during lock testing ", e);
699           }
700         }
701       }
702
703     } catch (Exception e) {
704       log.error("error during counting active clients");
705     }
706     return noOtherActiveClient;
707   }
708
709   /**
710    * Handler for the client watcher. after a client have been removed
711    * 
712    * Checks if the client is not the last active one.
713    * 
714    * If (the current client is not in the client list readd it;)
715    */
716   private class RemoveClientWatchCallBack implements WatcherCallBack {
717
718     private SimpleClient client;
719
720     private boolean manualCheckOfClientCount = false;
721
722     /**
723      *Inits the handler with the client to check in the list
724      * @param client client to monitor in the client list
725      */
726     protected RemoveClientWatchCallBack(SimpleClient client) {
727       this.client = client;
728     }
729
730     /**
731      * If the client list is modified, checks if the current is still in the list. otherwise, readds ti.
732      * @return true to enable watcher, or false to disable it in future WatcherThread cycles.
733      */
734     public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
735       // if lock is null, no client has been added since last, clear.
736       //the client is then the last client
737       if (client != null) {
738
739         if (lock == null) {
740
741           //checks if the client is not already in the lists
742           //   ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
743
744           boolean islastClient = true;
745           if (manualCheckOfClientCount) {
746             log.debug("manual checking of count of client");
747             //checks if the client is not already in the lists
748             ClientHandle[] cl = clist.retrieveClientList();//lock);//clist.retrieveClientList();
749             if (cl == null || cl.length < 1)
750             //  {//no client has registered as active 
751             {
752               islastClient = true;
753               log.debug("list is empty");
754             } else
755               islastClient = false;
756             log.debug("list is not empty");
757           }
758           //  if(cl == null || cl.length<1 )
759           //  {//no client has registered as active
760           if (islastClient) {
761             //the client is the last one, so close current session
762             log.info("last client removed: closing session");
763             closeSession(client);
764           }
765         } else {
766           log.debug("not the last client found ");
767           //              ask to the client to cpoy application data into the document
768           //         client.evgen._raise(Events.DOCUMENT_FINALIZEAPPDATA, null, client,null);
769
770           //   /  }
771
772         }
773         log.debug("Stopping EventGenerator..");
774         // TODO: ensure ClientsFile lock is really released!! clist.unlockFile();
775         client.evgen.stopWatching();
776       }
777       watcher.setHandler(null);//Do not check if the client is the last client. watcher will shutdown anyway
778       //  watcher.haltWatch();
779       // watcher.
780       return false;
781     }
782   }
783
784   /**
785    * closes the current session, 
786    * and send an event to the last client to close the document
787    * @param client the last client of the client
788    */
789   private void closeSession(SimpleClient client) {
790     //   close document 
791     client.evgen._raise(Events.DOCUMENT_REQUESTTOCLOSE, null, client, null);
792     log.debug("close document request done");
793     closeSession(client.getSessionHandle());
794   }
795
796   /**
797    * CLoses the current session
798    * @param sessionHandle sessionHandle of the session to remove
799    */
800   private void closeSession(SessionHandle sessionHandle) {
801     getSessionManager().removeSession(sessionHandle);
802     log.debug("Session removed");
803   }
804
805   /**
806    * @return the sessionManager
807    */
808   protected SimpleSessionManager getSessionManager() {
809     return sessionManager;
810   }
811
812   /**
813    * @param sessionManager the sessionManager to set
814    */
815   protected void setSessionManager(SimpleSessionManager sessionManager) {
816     this.sessionManager = sessionManager;
817   }
818
819   public ClientsFile getStoreDocFile() {
820     if (storedocfile == null) {
821
822     }
823     return storedocfile;
824   }
825
826   ClientSessionFileWatcherElement clistWatchElement = null;
827
828   /**
829    * get or create a watcher on clist.
830    * @return the contents of clistWatchElement or initialise it
831    */
832   public ClientSessionFileWatcherElement getClientWatcherElement() {
833     if (clistWatchElement == null) {
834       clistWatchElement = new ClientSessionFileWatcherElement(clist, null);
835     }
836     return clistWatchElement;
837   }
838   /**
839    * writes a vector of vorba Ids to the session.
840    * @param modObjects 
841    public void setModObjectList(Vector modObjects) {
842    log.debug("Writing "+modObjects.size()+" ids to ModObjectList");
843    // TODO Auto-generated method stub
844    }
845    **
846    * get current list of modified objects.
847    * @return null or Vector of objects
848    *
849    public Vector getModObjectList() {
850    log.debug("Reading modObjectList");
851    return null;
852    }
853    */
854 }