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