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