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