applied LGPLv3 and source code formatting.
[vamsas.git] / src / uk / ac / vamsas / client / simpleclient / EventGeneratorThread.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.beans.PropertyChangeListener;\r
25 import java.beans.PropertyChangeSupport;\r
26 import java.util.Hashtable;\r
27 \r
28 import org.apache.commons.logging.Log;\r
29 import org.apache.commons.logging.LogFactory;\r
30 \r
31 import uk.ac.vamsas.client.Events;\r
32 \r
33 /**\r
34  * monitors watcher objects and generates events.\r
35  */\r
36 public class EventGeneratorThread {\r
37   private static Log log = LogFactory.getLog(EventGeneratorThread.class);\r
38 \r
39   private SimpleClient client;\r
40 \r
41   private Hashtable handlers; // manager object\r
42 \r
43   private VamsasSession session;\r
44 \r
45   /**\r
46    * thread watching all the session's file objects\r
47    */\r
48   protected VamsasFileWatcherThread watchThread = null;\r
49 \r
50   /**\r
51    * Watcher element for list of all the clientHandles for the session\r
52    */\r
53   protected SessionFileWatcherElement clientfile = null;\r
54 \r
55   /**\r
56    * the session's vamsasDocument\r
57    */\r
58   protected VamsasFileWatcherElement vamsasfile = null;\r
59 \r
60   /**\r
61    * written to by client when its app calls storeDocument.\r
62    */\r
63   protected SessionFileWatcherElement storeFile = null;\r
64 \r
65   EventGeneratorThread(VamsasSession s, SimpleClient _client,\r
66       Hashtable eventhandlers) {\r
67     if (eventhandlers == null || s == null || _client == null)\r
68       throw new Error("Null arguments to EventGeneratorThread constructor.");\r
69     handlers = eventhandlers;\r
70     session = s;\r
71     client = _client;\r
72     log.debug("Creating VamsasFileWatcherThread.");\r
73     watchThread = new VamsasFileWatcherThread(this);\r
74     initWatchers();\r
75   }\r
76 \r
77   private void initWatchers() {\r
78     if (clientfile == null) {\r
79       log.debug("Initializing clientfile Watcher");\r
80       clientfile = session.getClientWatcherElement();\r
81       // handler is set in the Vamsas session\r
82       /*\r
83        * clientfile.setHandler(new WatcherCallBack() {\r
84        * \r
85        * public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {\r
86        * return clientListChanged(watcher, lock); } });\r
87        */\r
88       watchThread.addElement(clientfile);\r
89     }\r
90     final EventGeneratorThread evgen = this;\r
91 \r
92     if (vamsasfile == null) {\r
93       log.debug("Initializing VamsasFileWatcher");\r
94       vamsasfile = new VamsasFileWatcherElement(session.vamArchive,\r
95           new WatcherCallBack() {\r
96             public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {\r
97               return evgen.documentChanged(lock);\r
98             }\r
99           });\r
100       watchThread.addElement(vamsasfile);\r
101     }\r
102     if (storeFile == null) {\r
103       storeFile = new SessionFileWatcherElement(session.getStoreDocFile(),\r
104           new WatcherCallBack() {\r
105             public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {\r
106               return evgen.storeDocRequest(lock);\r
107             }\r
108           });\r
109       log.debug("Initializing storeDocFile flag watcher");\r
110     }\r
111     /*\r
112     */\r
113     log.debug("Watchers inited.");\r
114   }\r
115 \r
116   /**\r
117    * Call registered handlers for a vamsas session event\r
118    * \r
119    * @param handlerEvent\r
120    *          a named event\r
121    * @param property\r
122    *          property name to pass to handler\r
123    * @param oldval\r
124    *          old value of property to pass\r
125    * @param newval\r
126    *          new value of property to pass\r
127    * @return true if event generation did not raise any exceptions.\r
128    */\r
129   boolean _raise(String handlerEvent, String property, Object oldval,\r
130       Object newval) {\r
131     PropertyChangeSupport h = (PropertyChangeSupport) handlers\r
132         .get(handlerEvent);\r
133     if (h != null) {\r
134       log.debug("Triggering:" + handlerEvent);\r
135       try {\r
136         h.firePropertyChange(property, oldval, newval);\r
137       } catch (Exception e) {\r
138         log.warn("Client Exception during handling of " + handlerEvent, e);\r
139         return false;\r
140       } catch (Error e) {\r
141         log\r
142             .error("Serious! Client Error during handling of " + handlerEvent,\r
143                 e);\r
144         return false;\r
145       }\r
146       log.debug("Finished  :" + handlerEvent);\r
147     } else\r
148       log.debug("No handlers for raised " + handlerEvent);\r
149     return true;\r
150   }\r
151 \r
152   protected boolean storeDocRequest(Lock lock) {\r
153     if (log.isDebugEnabled())\r
154       log.debug("StoreDocRequest on "\r
155           + (lock == null ? (lock.isLocked() ? "" : "Invalid ") : "Non-")\r
156           + "Existing lock");\r
157     // TODO: define the storeFile semaphore mechanism : file exists - all\r
158     // clients inform their apps, and then the client that wrote the file should\r
159     // delete the file (it should hold the lock to it).\r
160     if (storeFile.getWatcher().exists) {\r
161       _raise(Events.DOCUMENT_FINALIZEAPPDATA, client.getSessionUrn(), null,\r
162           client);\r
163       // expect client to write to document so update watcher state on return\r
164       vamsasfile.getWatcher().setState();\r
165       lock.release();\r
166     }\r
167     return true;\r
168   }\r
169 \r
170   protected boolean documentChanged(Lock doclock) {\r
171     boolean continueWatching = true;\r
172     if (!block_document_updates) {\r
173       session.vamArchive.fileLock = doclock;\r
174       if (client.pickmanager != null)\r
175         client.pickmanager.setPassThru(false);\r
176       if (log.isDebugEnabled()) {\r
177         log.debug("Initiating a documentChanged event. Document is "\r
178             + (client.cdocument == null ? "closed" : "open"));\r
179       }\r
180       // TODO: decide if individual object update handlers are called as well as\r
181       // overall event handler\r
182       if (!_raise(Events.DOCUMENT_UPDATE, client.getSessionUrn(), null, client)) {\r
183         log\r
184             .info("Recovering from errors or exceptions generated by client application");\r
185         if (client.cdocument != null) {\r
186           try {\r
187             client.tidyAwaySessionDocumentState();\r
188           } catch (Exception e) {\r
189             log\r
190                 .warn(\r
191                     "Exception generated by vamsas library - when tidying away session document:",\r
192                     e);\r
193           } catch (Error e) {\r
194             log\r
195                 .error(\r
196                     "LIBRARY Implementation error - when tidying away session document:",\r
197                     e);\r
198           }\r
199         }\r
200 \r
201       }\r
202       if (client.pickmanager != null)\r
203         client.pickmanager.setPassThru(true);\r
204       if (log.isDebugEnabled()) {\r
205         log.debug("Finished handling a documentChanged event. Document is "\r
206             + (client.cdocument == null ? "closed" : "open"));\r
207       }\r
208       if (client.cdocument != null) {\r
209         log\r
210             .warn("Implementation Error ?  ClientDocument instance has not been closed or updated by handler!");\r
211       }\r
212       /*\r
213        * try { client._session.getVamsasDocument().closeArchive(); } catch\r
214        * (Exception e)\r
215        * {log.warn("Unexpected exception when closing document after update."\r
216        * ,e);};\r
217        */\r
218     } else {\r
219       // TODO: check documentChanged */\r
220       log.debug("Ignoring documentChanged event for " + client.getSessionUrn());\r
221     }\r
222     return continueWatching;\r
223   }\r
224 \r
225   boolean ownsf = false;\r
226 \r
227   /**\r
228    * Moved to SimpleClientSessionManager scans all watchers and fires\r
229    * changeEvents if necessary\r
230    * \r
231    * @return number of events generated.\r
232    */\r
233   private boolean clientListChanged(WatcherElement clientfile, Lock watchlock) {\r
234     log.debug("ClientListChanged handler called for "\r
235         + clientfile.getWatcher().getSubject());\r
236     // could make this general - but for now keep simple\r
237     if (watchlock != null) {\r
238       // TODO: compare new client list to old list version. is it changed ?\r
239       // see what happened to the clientfile - compare our internal version with\r
240       // the one in the file, or just send the updated list out...?\r
241       //\r
242       /**\r
243        * Generated when a new vamsas client is attached to a session (Handle is\r
244        * passed) Note: the newly created client does not receive the event.\r
245        * \r
246        * public static final String CLIENT_CREATION =\r
247        * "uk.ac.vamsas.client.events.clientCreateEvent";\r
248        */\r
249       // as the test\r
250       /**\r
251        * Generated when a vamsas client leaves a session (Handle is passed to\r
252        * all others). public static final String CLIENT_FINALIZATION =\r
253        * "uk.ac.vamsas.client.events.clientFinalizationEvent";\r
254        */\r
255       // again - as the test.\r
256       watchlock.release();\r
257     }\r
258     return true;\r
259   }\r
260 \r
261   /**\r
262    * Events raised by IClient and propagated to others in session\r
263    */\r
264 \r
265   /**\r
266    * number of milliseconds between any file state check.\r
267    */\r
268   long POLL_UNIT = 20;\r
269 \r
270   protected void wait(int u) {\r
271     if (u <= 0)\r
272       u = 1;\r
273     long l = System.currentTimeMillis() + POLL_UNIT * u;\r
274     while (System.currentTimeMillis() < l)\r
275       ;\r
276   }\r
277 \r
278   private boolean block_document_updates = false;\r
279 \r
280   int STORE_WAIT = 5; // how many units before we decide all clients have\r
281                       // finalized their appdatas\r
282 \r
283   private boolean in_want_to_store_phase = false;\r
284 \r
285   /**\r
286    * client App requests offline storage of vamsas data. Call blocks whilst\r
287    * other apps do any appData finalizing and then returns (after locking the\r
288    * vamsasDocument in the session) Note - the calling app may also receive\r
289    * events through the EventGeneratorThread for document updates.\r
290    * \r
291    * @return Lock for session.vamArchive\r
292    * @param STORE_WAIT\r
293    *          indicates how lock the call will block for when nothing appears to\r
294    *          be happening to the session.\r
295    */\r
296   protected Lock want_to_store() {\r
297     if (in_want_to_store_phase) {\r
298       log\r
299           .error("client error: want_to_store called again before first call has completed.");\r
300       return null;\r
301     }\r
302     in_want_to_store_phase = true;\r
303     // TODO: test the storeDocumentRequest mechanism\r
304     /*\r
305      * / watchThread.haltWatchers();\r
306      */\r
307     log.debug("Stopping document_update watcher");\r
308     vamsasfile.haltWatch();\r
309     // block_document_updates=true;\r
310     log.debug("Cleared flag for ignoring document_update requests");\r
311 \r
312     log.debug("Sending Store Document Request");\r
313     try {\r
314       session.addStoreDocumentRequest(client.getClientHandle(), client\r
315           .getUserHandle());\r
316     } catch (Exception e) {\r
317       log.warn("Whilst writing StoreDocumentRequest for "\r
318           + client.getClientHandle().getClientUrn() + " "\r
319           + client.getUserHandle(), e);\r
320       log.info("trying to continue after storeDocumentRequest exception.");\r
321     }\r
322     log.debug("Waiting for other apps to do FinalizeApp handling.");\r
323     // LATER: refine this semaphore process\r
324     // to make a robust signalling mechanism:\r
325     // app1 requests, app1..n do something (or don't - they may be dead),\r
326     // app1 realises all apps have done their thing, it then continues with\r
327     // synchronized data.\r
328     // this probably needs two files - a request file,\r
329     // and a response file which is acknowledged by the app1 requestor for each\r
330     // app.\r
331     // eventually, no more responses are received for the request, and the app\r
332     // can then only continue with its store.\r
333     FileWatcher sfwatcher = session.getStoreWatcher();\r
334     FileWatcher vfwatcher = session.getDocWatcher();\r
335     int units = 0; // zero if updates occured over a sleep period\r
336     while (units < STORE_WAIT) {\r
337       try {\r
338         Thread.sleep(watchThread.WATCH_SLEEP);\r
339       } catch (InterruptedException e) {\r
340         log.debug("interrupted.");\r
341       }\r
342       if (sfwatcher.hasChanged() || vfwatcher.hasChanged()) {\r
343         units = 0;\r
344       } else {\r
345         units++;\r
346       }\r
347     }\r
348 \r
349     block_document_updates = false;\r
350     vamsasfile.enableWatch();\r
351     log.debug("Cleared flag for ignoring document_update requests");\r
352     // wait around again (until our own watcher has woken up and synchronized).\r
353     while (units < STORE_WAIT) {\r
354       try {\r
355         Thread.sleep(watchThread.WATCH_SLEEP);\r
356       } catch (InterruptedException e) {\r
357         log.debug("interrupted.");\r
358       }\r
359       if (sfwatcher.hasChanged() || vfwatcher.hasChanged())\r
360         units = 0;\r
361       else\r
362         units++;\r
363     }\r
364 \r
365     log.debug("finished waiting.");\r
366     in_want_to_store_phase = false;\r
367     return session.vamArchive.getLock();\r
368   }\r
369 \r
370   /**\r
371    * count handlers for a particular vamsas event\r
372    * \r
373    * @param event\r
374    *          string enumeration from uk.ac.vamsas.client.Events\r
375    * @return -1 for an invalid event, otherwise the number of handlers\r
376    */\r
377   protected int countHandlersFor(String event) {\r
378     if (handlers.containsKey(event)) {\r
379       PropertyChangeSupport handler = (PropertyChangeSupport) handlers\r
380           .get(event);\r
381       PropertyChangeListener[] listeners;\r
382       if (handler != null)\r
383         return ((listeners = handler.getPropertyChangeListeners()) == null) ? -1\r
384             : listeners.length;\r
385     }\r
386     return -1;\r
387   }\r
388 \r
389   public void disableDocumentWatch() {\r
390     vamsasfile.haltWatch();\r
391   }\r
392 \r
393   public boolean isDocumentWatchEnabled() {\r
394     return (vamsasfile != null) && vamsasfile.isWatchEnabled();\r
395   }\r
396 \r
397   public void enableDocumentWatch() {\r
398     vamsasfile.enableWatch();\r
399   }\r
400 \r
401   public boolean isWatcherAlive() {\r
402     return watchThread != null && watchThread.running && watchThread.isAlive();\r
403   }\r
404 \r
405   public void interruptWatching() {\r
406     if (watchThread != null && watchThread.isAlive()) {\r
407       // TODO: find a way of interrupting watcher in a way that prevents file IO\r
408       // being interrupted\r
409       watchThread.interrupt();\r
410     }\r
411 \r
412   }\r
413 \r
414   /**\r
415    * called to start the session watching thread which generates events\r
416    */\r
417   public void startWatching() {\r
418     enableDocumentWatch();\r
419     watchThread.start();\r
420     while (!watchThread.running && watchThread.isAlive())\r
421       log.debug("Waiting until watcher is really started.");\r
422   }\r
423 \r
424   public void stopWatching() {\r
425     interruptWatching();\r
426     watchThread.haltWatchers();\r
427 \r
428   }\r
429 \r
430 }\r