001: /* EventProcessingThreadImpl.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Wed Jul 20 11:24:00 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zk.ui.impl;
020:
021: import java.util.List;
022: import java.util.LinkedList;
023: import java.util.Locale;
024: import java.util.TimeZone;
025:
026: import org.zkoss.lang.Threads;
027: import org.zkoss.lang.Exceptions;
028: import org.zkoss.util.Locales;
029: import org.zkoss.util.TimeZones;
030: import org.zkoss.util.logging.Log;
031:
032: import org.zkoss.zk.ui.Execution;
033: import org.zkoss.zk.ui.Executions;
034: import org.zkoss.zk.ui.Desktop;
035: import org.zkoss.zk.ui.Component;
036: import org.zkoss.zk.ui.UiException;
037: import org.zkoss.zk.ui.event.Event;
038: import org.zkoss.zk.ui.util.Configuration;
039: import org.zkoss.zk.ui.sys.ExecutionCtrl;
040: import org.zkoss.zk.ui.sys.EventProcessingThread;
041:
042: /** Thread to handle events.
043: * We need to handle events in a separate thread, because it might
044: * suspend (by calling {@link org.zkoss.zk.ui.sys.UiEngine#wait}), such as waiting
045: * a modal dialog to complete.
046: *
047: * @author tomyeh
048: */
049: public class EventProcessingThreadImpl extends Thread implements
050: EventProcessingThread {
051: // private static final Log log = Log.lookup(EventProcessingThreadImpl.class);
052:
053: /** The processor. */
054: private EventProcessor _proc;
055: /** Part of the command: locale. */
056: private Locale _locale;
057: /** Part of the command: time zone. */
058: private TimeZone _timeZone;
059: /** Part of the result: a list of EventThreadInit instances. */
060: private List _evtThdInits;
061: /** Part of the result: a list of EventThreadCleanup instances. */
062: private List _evtThdCleanups;
063: /** Part of the result: a list of EventThreadResume instances. */
064: private List _evtThdResumes;
065: /** Part of the result. a list of EventThreadSuspend instances. */
066: private List _evtThdSuspends;
067: /** Result of the result. */
068: private Throwable _ex;
069: /** Whether the execution is activated. */
070: private boolean _acted;
071:
072: private static int _nThd, _nBusyThd;
073:
074: /** The mutex use to notify an event is ready for processing, or
075: * has been processed.
076: */
077: private final Object _evtmutex = new Object();
078: /** The mutex use to suspend an event processing. */
079: private Object _suspmutex;
080: /** If null, it means not ceased yet.
081: * If not null, it means it is ceased and it is a text describing the cause.
082: */
083: private String _ceased;
084: /** Whether not to show message when stopping. */
085: private boolean _silent;
086: /** Whether it is suspended. */
087: private transient boolean _suspended;
088:
089: public EventProcessingThreadImpl() {
090: // if (log.debugable()) log.debug("Starting an event processing thread");
091: Threads.setDaemon(this , true);
092: start();
093: }
094:
095: //EventProcessingThread//
096: public boolean isCeased() {
097: return _ceased != null;
098: }
099:
100: public boolean isSuspended() {
101: return _suspended;
102: }
103:
104: synchronized public boolean isIdle() {
105: return _proc == null;
106: }
107:
108: public final Event getEvent() {
109: return _proc.getEvent();
110: }
111:
112: public final Component getComponent() {
113: return _proc.getComponent();
114: }
115:
116: public void sendEvent(final Component comp, Event event)
117: throws Exception {
118: // if (log.finerable()) log.finer("Process sent event: "+event);
119: if (event == null || comp == null)
120: throw new IllegalArgumentException(
121: "Both comp and event must be specified");
122: if (!(Thread.currentThread() instanceof EventProcessingThreadImpl))
123: throw new IllegalStateException(
124: "Only callable when processing an event");
125:
126: final EventProcessor oldproc = _proc;
127: _proc = new EventProcessor(_proc.getDesktop(), comp, event);
128: try {
129: setup();
130: process0();
131: } finally {
132: _proc = oldproc;
133: setup();
134: }
135: }
136:
137: //extra utilities//
138: /** Stops the thread. Called only by {@link org.zkoss.zk.ui.sys.UiEngine}
139: * when it is stopping.
140: * <p>Application developers shall use {@link org.zkoss.zk.ui.sys.DesktopCtrl#ceaseSuspendedThread}
141: * instead.
142: *
143: * @param cause a human readable text describing the cause.
144: * If null, an empty string is assumed.
145: */
146: public void cease(String cause) {
147: synchronized (_evtmutex) {
148: _ceased = cause != null ? cause : "";
149: _evtmutex.notifyAll();
150: }
151: if (_suspmutex != null) {
152: synchronized (_suspmutex) {
153: _suspmutex.notifyAll();
154: }
155: }
156: }
157:
158: /** Stops the thread silently. Called by {@link org.zkoss.zk.ui.sys.UiEngine}
159: * to stop abnormally.
160: */
161: public void ceaseSilently(String cause) {
162: _silent = true;
163: cease(cause);
164: }
165:
166: /** Returns the number of event threads.
167: */
168: public static final int getThreadNumber() {
169: return _nThd;
170: }
171:
172: /** Returns the number of event threads in processing.
173: */
174: public static final int getThreadNumberInProcessing() {
175: return _nBusyThd;
176: }
177:
178: /** Suspends the current thread and Waits until {@link #doResume}
179: * is called.
180: *
181: * <p>Note:
182: * <ul>
183: * <li>It is used internally only for implementing {@link org.zkoss.zk.ui.sys.UiEngine}
184: * Don't call it directly.
185: * <li>Caller must invoke {@link #newEventThreadSuspends}
186: * before calling this method. (Reason: UiEngine might have to store some info
187: * after {@link #newEventThreadSuspends} is called.
188: * <li>The current thread must be {@link EventProcessingThreadImpl}.
189: * <li>It is a static method.
190: * </ul>
191: */
192: public static void doSuspend(Object mutex)
193: throws InterruptedException {
194: ((EventProcessingThreadImpl) Thread.currentThread())
195: .doSuspend0(mutex);
196: }
197:
198: private void doSuspend0(Object mutex) throws InterruptedException {
199: // if (log.finerable()) log.finer("Suspend event processing; "+_proc);
200: if (mutex == null)
201: throw new IllegalArgumentException("null mutex");
202: if (isIdle())
203: throw new InternalError("Called without processing event?");
204: if (_suspmutex != null)
205: throw new InternalError("Suspend twice?");
206:
207: //Spec: locking mutex is optional for app developers
208: //so we have to lock it first
209: _suspmutex = mutex;
210: try {
211: synchronized (_suspmutex) {
212: _suspended = true;
213:
214: //Bug 1814298: need to call Execution.onDeactivate
215: Execution exec = getExecution();
216: if (exec != null) {
217: ((ExecutionCtrl) exec).onDeactivate();
218: _acted = false;
219: }
220:
221: //let the main thread continue
222: synchronized (_evtmutex) {
223: _evtmutex.notify();
224: }
225:
226: if (_ceased == null)
227: _suspmutex.wait();
228: }
229: } finally {
230: _suspmutex = null;
231: _suspended = false; //just in case (such as _ceased)
232: }
233:
234: if (_ceased != null)
235: throw new InterruptedException(_ceased);
236:
237: //being resumed
238: setup();
239: Execution exec = getExecution();
240: if (exec != null) {
241: ((ExecutionCtrl) exec).onActivate();
242: _acted = true;
243: }
244:
245: final List resumes = _evtThdResumes;
246: _evtThdResumes = null;
247: if (resumes != null && !resumes.isEmpty()) {
248: _proc.getDesktop().getWebApp().getConfiguration()
249: .invokeEventThreadResumes(resumes, getComponent(),
250: getEvent());
251: //FUTURE: how to propogate errors to the client
252: }
253: }
254:
255: private Execution getExecution() {
256: Execution exec = _proc.getDesktop().getExecution();
257: return exec != null ? exec : Executions.getCurrent();
258: //just in case that the execution is dead first
259: }
260:
261: /** Resumes this thread and returns only if the execution (being suspended
262: * by {@link #doSuspend}) completes.
263: *
264: * <p>It executes in the main thread (i.e., the servlet thread).
265: *
266: * @return whether the event has been processed completely or just be suspended
267: */
268: public boolean doResume() throws InterruptedException {
269: if (this .equals(Thread.currentThread()))
270: throw new IllegalStateException(
271: "A thread cannot resume itself");
272: // if (log.finerable()) log.finer("Resume event processing; "+_proc);
273: if (isIdle())
274: throw new InternalError("Called without processing event?");
275: if (_suspmutex == null)
276: throw new InternalError("Resume non-suspended thread?");
277:
278: //Copy first since event thread clean up them, when completed
279: final Configuration config = _proc.getDesktop().getWebApp()
280: .getConfiguration();
281: final Component comp = getComponent();
282: final Event event = getEvent();
283: try {
284: _evtThdResumes = config.newEventThreadResumes(comp, event);
285:
286: //Spec: locking mutex is optional for app developers
287: //so we have to lock it first
288: synchronized (_suspmutex) {
289: _suspended = false;
290: _suspmutex.notify(); //wake the suspended event thread
291: }
292:
293: //wait until the event thread completes or suspends again
294: //If complete: isIdle() is true
295: //If suspend again: _suspended is true
296: synchronized (_evtmutex) {
297: if (_ceased == null && !isIdle() && !_suspended)
298: _evtmutex.wait();
299: }
300: } finally {
301: //_evtThdCleanups is null if //1) no listener;
302: //2) the event thread is suspended again (handled by another doResume)
303: invokeEventThreadCompletes(config, comp, event);
304: }
305:
306: checkError();
307: return isIdle();
308: }
309:
310: /** Ask this event thread to process the specified event.
311: *
312: * <p>Used internally to implement {@link org.zkoss.zk.ui.sys.UiEngine}.
313: * Application developers
314: * shall use {@link org.zkoss.zk.ui.event.Events#sendEvent} instead.
315: *
316: * @return whether the event has been processed completely or just be suspended.
317: * Recycle it only if true is returned.
318: */
319: public boolean processEvent(Desktop desktop, Component comp,
320: Event event) {
321: if (Thread.currentThread() instanceof EventProcessingThreadImpl)
322: throw new IllegalStateException(
323: "processEvent cannot be called in an event thread");
324: if (_ceased != null)
325: throw new InternalError(
326: "The event thread has beeing stopped. Cause: "
327: + _ceased);
328: if (_proc != null)
329: throw new InternalError(
330: "reentering processEvent not allowed");
331:
332: _locale = Locales.getCurrent();
333: _timeZone = TimeZones.getCurrent();
334: _ex = null;
335:
336: final EventProcessor proc = new EventProcessor(desktop, comp,
337: event);
338: //it also check the correctness of desktop/comp/event
339: final Configuration config = desktop.getWebApp()
340: .getConfiguration();
341: _evtThdInits = config.newEventThreadInits(comp, event);
342: try {
343: synchronized (_evtmutex) {
344: _proc = proc; //Bug 1577842: don't let event thread start (and end) too early
345:
346: _evtmutex.notify(); //ask the event thread to handle it
347: if (_ceased == null) {
348: _evtmutex.wait();
349: //wait until the event thread to complete or suspended
350:
351: if (_suspended) {
352: config.invokeEventThreadSuspends(
353: _evtThdSuspends, comp, event);
354: _evtThdSuspends = null;
355: }
356: }
357: }
358: } catch (InterruptedException ex) {
359: throw new UiException(ex);
360: } finally {
361: //_evtThdCleanups is null if //1) no listener;
362: //2) the event thread is suspended (then handled by doResume).
363: invokeEventThreadCompletes(config, comp, event);
364: }
365:
366: checkError(); //check any error occurs
367: return isIdle();
368: }
369:
370: /** Invokes {@link Configuration#newEventThreadSuspends}.
371: * The caller must execute in the event processing thread.
372: * It is called only for implementing {@link org.zkoss.zk.ui.sys.UiEngine}.
373: * Don't call it directly.
374: */
375: public void newEventThreadSuspends(Object mutex) {
376: if (_proc == null)
377: throw new IllegalStateException();
378:
379: _evtThdSuspends = _proc.getDesktop().getWebApp()
380: .getConfiguration().newEventThreadSuspends(
381: getComponent(), getEvent(), mutex);
382: //it might throw an exception, so process it before updating
383: //_suspended
384: }
385:
386: private void invokeEventThreadCompletes(Configuration config,
387: Component comp, Event event) throws UiException {
388: if (_evtThdCleanups != null && !_evtThdCleanups.isEmpty()) {
389: final List errs = _ex != null ? null : new LinkedList();
390:
391: config.invokeEventThreadCompletes(_evtThdCleanups, comp,
392: event, errs);
393:
394: if (errs != null && !errs.isEmpty())
395: throw UiException.Aide.wrap((Throwable) errs.get(0));
396: }
397: _evtThdCleanups = null;
398: }
399:
400: /** Setup for execution. */
401: synchronized private void setup() {
402: _proc.setup();
403: }
404:
405: /** Cleanup for execution. */
406: synchronized private void cleanup() {
407: _proc.cleanup();
408: _proc = null;
409: }
410:
411: private void checkError() {
412: if (_ex != null) { //failed to process
413: // if (log.debugable()) log.realCause(_ex);
414: final Throwable ex = _ex;
415: _ex = null;
416: throw UiException.Aide.wrap(ex);
417: }
418: }
419:
420: //-- Thread --//
421: public void run() {
422: ++_nThd;
423: try {
424: while (_ceased == null) {
425: final boolean evtAvail = !isIdle();
426: if (evtAvail) {
427: final Configuration config = _proc.getDesktop()
428: .getWebApp().getConfiguration();
429: boolean cleaned = false;
430: ++_nBusyThd;
431: Execution exec = null;
432: try {
433: // if (log.finerable()) log.finer("Processing event: "+_proc);
434:
435: Locales.setThreadLocal(_locale);
436: TimeZones.setThreadLocal(_timeZone);
437:
438: setup();
439: exec = getExecution();
440: if (exec != null) {
441: ((ExecutionCtrl) exec).onActivate();
442: _acted = true;
443: }
444:
445: final boolean b = config
446: .invokeEventThreadInits(_evtThdInits,
447: getComponent(), getEvent());
448: _evtThdInits = null;
449:
450: if (b)
451: process0();
452: } catch (Throwable ex) {
453: cleaned = true;
454: newEventThreadCleanups(config, ex);
455: } finally {
456: --_nBusyThd;
457:
458: if (!cleaned)
459: newEventThreadCleanups(config, _ex);
460:
461: // if (log.finerable()) log.finer("Real processing is done: "+_proc);
462: if (exec != null && _acted) //_acted is false if suspended is killed
463: ((ExecutionCtrl) exec).onDeactivate();
464: cleanup();
465:
466: Locales.setThreadLocal(_locale = null);
467: TimeZones.setThreadLocal(_timeZone = null);
468: }
469: }
470:
471: synchronized (_evtmutex) {
472: if (evtAvail)
473: _evtmutex.notify();
474: //wake the main thread OR the resuming thread
475: if (_ceased == null)
476: _evtmutex.wait();
477: //wait the main thread to issue another request
478: }
479: }
480:
481: if (_silent) {
482: // if (log.debugable()) log.debug("The event processing thread stops");
483: } else {
484: System.out.println("The event processing thread stops");
485: //Don't use log because it might be stopped
486: }
487: } catch (InterruptedException ex) {
488: if (_silent) {
489: // if (log.debugable())
490: // log.debug("The event processing thread interrupted: "+Exceptions.getMessage(ex)
491: // +"\n"+Exceptions.getBriefStackTrace(ex));
492: } else {
493: System.out
494: .println("The event processing thread interrupted: "
495: + Exceptions.getMessage(ex));
496: //Don't use log because it might be stopped
497: }
498: } finally {
499: --_nThd;
500: }
501: }
502:
503: /** Invokes {@link Configuration#newEventThreadCleanups}.
504: */
505: private void newEventThreadCleanups(Configuration config,
506: Throwable ex) {
507: final List errs = new LinkedList();
508: if (ex != null)
509: errs.add(ex);
510: _evtThdCleanups = config.newEventThreadCleanups(getComponent(),
511: getEvent(), errs);
512: _ex = errs.isEmpty() ? null : (Throwable) errs.get(0);
513: //propogate back the first exception
514: }
515:
516: /** Processes the component and event.
517: */
518: private void process0() throws Exception {
519: if (_proc == null)
520: throw new IllegalStateException("Not initialized");
521: _proc.process();
522: }
523:
524: //-- Object --//
525: public String toString() {
526: return "[proc=" + _proc + ", ceased=" + _ceased + ']';
527: }
528: }
|