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
6 * Earlier versions have also been incorporated into Jalview version 2.4
\r
7 * since 2008, and TOPALi version 2 since 2007.
\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
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
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
22 package uk.ac.vamsas.client.simpleclient;
\r
24 import java.beans.PropertyChangeListener;
\r
25 import java.beans.PropertyChangeSupport;
\r
26 import java.util.Hashtable;
\r
28 import org.apache.commons.logging.Log;
\r
29 import org.apache.commons.logging.LogFactory;
\r
31 import uk.ac.vamsas.client.Events;
\r
34 * monitors watcher objects and generates events.
\r
36 public class EventGeneratorThread {
\r
37 private static Log log = LogFactory.getLog(EventGeneratorThread.class);
\r
39 private SimpleClient client;
\r
41 private Hashtable handlers; // manager object
\r
43 private VamsasSession session;
\r
46 * thread watching all the session's file objects
\r
48 protected VamsasFileWatcherThread watchThread = null;
\r
51 * Watcher element for list of all the clientHandles for the session
\r
53 protected SessionFileWatcherElement clientfile = null;
\r
56 * the session's vamsasDocument
\r
58 protected VamsasFileWatcherElement vamsasfile = null;
\r
61 * written to by client when its app calls storeDocument.
\r
63 protected SessionFileWatcherElement storeFile = null;
\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
72 log.debug("Creating VamsasFileWatcherThread.");
\r
73 watchThread = new VamsasFileWatcherThread(this);
\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
83 * clientfile.setHandler(new WatcherCallBack() {
\r
85 * public boolean handleWatchEvent(WatcherElement watcher, Lock lock) {
\r
86 * return clientListChanged(watcher, lock); } });
\r
88 watchThread.addElement(clientfile);
\r
90 final EventGeneratorThread evgen = this;
\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
100 watchThread.addElement(vamsasfile);
\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
109 log.debug("Initializing storeDocFile flag watcher");
\r
113 log.debug("Watchers inited.");
\r
117 * Call registered handlers for a vamsas session event
\r
119 * @param handlerEvent
\r
122 * property name to pass to handler
\r
124 * old value of property to pass
\r
126 * new value of property to pass
\r
127 * @return true if event generation did not raise any exceptions.
\r
129 boolean _raise(String handlerEvent, String property, Object oldval,
\r
131 PropertyChangeSupport h = (PropertyChangeSupport) handlers
\r
132 .get(handlerEvent);
\r
134 log.debug("Triggering:" + handlerEvent);
\r
136 h.firePropertyChange(property, oldval, newval);
\r
137 } catch (Exception e) {
\r
138 log.warn("Client Exception during handling of " + handlerEvent, e);
\r
140 } catch (Error e) {
\r
142 .error("Serious! Client Error during handling of " + handlerEvent,
\r
146 log.debug("Finished :" + handlerEvent);
\r
148 log.debug("No handlers for raised " + handlerEvent);
\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
163 // expect client to write to document so update watcher state on return
\r
164 vamsasfile.getWatcher().setState();
\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
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
184 .info("Recovering from errors or exceptions generated by client application");
\r
185 if (client.cdocument != null) {
\r
187 client.tidyAwaySessionDocumentState();
\r
188 } catch (Exception e) {
\r
191 "Exception generated by vamsas library - when tidying away session document:",
\r
193 } catch (Error e) {
\r
196 "LIBRARY Implementation error - when tidying away session document:",
\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
208 if (client.cdocument != null) {
\r
210 .warn("Implementation Error ? ClientDocument instance has not been closed or updated by handler!");
\r
213 * try { client._session.getVamsasDocument().closeArchive(); } catch
\r
215 * {log.warn("Unexpected exception when closing document after update."
\r
219 // TODO: check documentChanged */
\r
220 log.debug("Ignoring documentChanged event for " + client.getSessionUrn());
\r
222 return continueWatching;
\r
225 boolean ownsf = false;
\r
228 * Moved to SimpleClientSessionManager scans all watchers and fires
\r
229 * changeEvents if necessary
\r
231 * @return number of events generated.
\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
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
246 * public static final String CLIENT_CREATION =
\r
247 * "uk.ac.vamsas.client.events.clientCreateEvent";
\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
255 // again - as the test.
\r
256 watchlock.release();
\r
262 * Events raised by IClient and propagated to others in session
\r
266 * number of milliseconds between any file state check.
\r
268 long POLL_UNIT = 20;
\r
270 protected void wait(int u) {
\r
273 long l = System.currentTimeMillis() + POLL_UNIT * u;
\r
274 while (System.currentTimeMillis() < l)
\r
278 private boolean block_document_updates = false;
\r
280 int STORE_WAIT = 5; // how many units before we decide all clients have
\r
281 // finalized their appdatas
\r
283 private boolean in_want_to_store_phase = false;
\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
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
296 protected Lock want_to_store() {
\r
297 if (in_want_to_store_phase) {
\r
299 .error("client error: want_to_store called again before first call has completed.");
\r
302 in_want_to_store_phase = true;
\r
303 // TODO: test the storeDocumentRequest mechanism
\r
305 * / watchThread.haltWatchers();
\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
312 log.debug("Sending Store Document Request");
\r
314 session.addStoreDocumentRequest(client.getClientHandle(), client
\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
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
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
338 Thread.sleep(watchThread.WATCH_SLEEP);
\r
339 } catch (InterruptedException e) {
\r
340 log.debug("interrupted.");
\r
342 if (sfwatcher.hasChanged() || vfwatcher.hasChanged()) {
\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
355 Thread.sleep(watchThread.WATCH_SLEEP);
\r
356 } catch (InterruptedException e) {
\r
357 log.debug("interrupted.");
\r
359 if (sfwatcher.hasChanged() || vfwatcher.hasChanged())
\r
365 log.debug("finished waiting.");
\r
366 in_want_to_store_phase = false;
\r
367 return session.vamArchive.getLock();
\r
371 * count handlers for a particular vamsas 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
377 protected int countHandlersFor(String event) {
\r
378 if (handlers.containsKey(event)) {
\r
379 PropertyChangeSupport handler = (PropertyChangeSupport) handlers
\r
381 PropertyChangeListener[] listeners;
\r
382 if (handler != null)
\r
383 return ((listeners = handler.getPropertyChangeListeners()) == null) ? -1
\r
384 : listeners.length;
\r
389 public void disableDocumentWatch() {
\r
390 vamsasfile.haltWatch();
\r
393 public boolean isDocumentWatchEnabled() {
\r
394 return (vamsasfile != null) && vamsasfile.isWatchEnabled();
\r
397 public void enableDocumentWatch() {
\r
398 vamsasfile.enableWatch();
\r
401 public boolean isWatcherAlive() {
\r
402 return watchThread != null && watchThread.running && watchThread.isAlive();
\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
415 * called to start the session watching thread which generates events
\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
424 public void stopWatching() {
\r
425 interruptWatching();
\r
426 watchThread.haltWatchers();
\r