Save This Page
Home » zk-src-3.5.1 » org » zkoss » zk » ui » impl » [javadoc | source]
    1   /* EventProcessingThreadImpl.java
    2   
    3   {{IS_NOTE
    4   	Purpose:
    5   		
    6   	Description:
    7   		
    8   	History:
    9   		Wed Jul 20 11:24:00     2005, Created by tomyeh
   10   }}IS_NOTE
   11   
   12   Copyright (C) 2005 Potix Corporation. All Rights Reserved.
   13   
   14   {{IS_RIGHT
   15   	This program is distributed under GPL Version 2.0 in the hope that
   16   	it will be useful, but WITHOUT ANY WARRANTY.
   17   }}IS_RIGHT
   18   */
   19   package org.zkoss.zk.ui.impl;
   20   
   21   import java.util.List;
   22   import java.util.LinkedList;
   23   import java.util.Locale;
   24   import java.util.TimeZone;
   25   
   26   import org.zkoss.lang.Threads;
   27   import org.zkoss.lang.Exceptions;
   28   import org.zkoss.util.Locales;
   29   import org.zkoss.util.TimeZones;
   30   import org.zkoss.util.logging.Log;
   31   
   32   import org.zkoss.zk.ui.Execution;
   33   import org.zkoss.zk.ui.Executions;
   34   import org.zkoss.zk.ui.Desktop;
   35   import org.zkoss.zk.ui.Component;
   36   import org.zkoss.zk.ui.UiException;
   37   import org.zkoss.zk.ui.event.Event;
   38   import org.zkoss.zk.ui.util.Configuration;
   39   import org.zkoss.zk.ui.sys.ExecutionCtrl;
   40   import org.zkoss.zk.ui.sys.EventProcessingThread;
   41   
   42   /** Thread to handle events.
   43    * We need to handle events in a separate thread, because it might
   44    * suspend (by calling {@link org.zkoss.zk.ui.sys.UiEngine#wait}), such as waiting
   45    * a modal dialog to complete.
   46    * 
   47    * @author tomyeh
   48    */
   49   public class EventProcessingThreadImpl extends Thread
   50   implements EventProcessingThread {
   51   //	private static final Log log = Log.lookup(EventProcessingThreadImpl.class);
   52   
   53   	/** The processor. */
   54   	private EventProcessor _proc;
   55   	/** Part of the command: locale. */
   56   	private Locale _locale;
   57   	/** Part of the command: time zone. */
   58   	private TimeZone _timeZone;
   59   	/** Part of the result: a list of EventThreadInit instances. */
   60   	private List _evtThdInits;
   61   	/** Part of the result: a list of EventThreadCleanup instances. */
   62   	private List _evtThdCleanups;
   63   	/** Part of the result: a list of EventThreadResume instances. */
   64   	private List _evtThdResumes;
   65   	/** Part of the result. a list of EventThreadSuspend instances. */
   66   	private List _evtThdSuspends;
   67   	/** Result of the result. */
   68   	private Throwable _ex;
   69   	/** Whether the execution is activated. */
   70   	private boolean _acted;
   71   
   72   	private static int _nThd, _nBusyThd;
   73   
   74   	/** The mutex use to notify an event is ready for processing, or
   75   	 * has been processed.
   76   	 */
   77   	private final Object _evtmutex = new Object();
   78   	/** The mutex use to suspend an event processing. */
   79   	private Object _suspmutex;
   80   	/** If null, it means not ceased yet.
   81   	 * If not null, it means it is ceased and it is a text describing the cause.
   82   	 */
   83   	private String _ceased;
   84   	/** Whether not to show message when stopping. */
   85   	private boolean _silent;
   86   	/** Whether it is suspended. */
   87   	private transient boolean _suspended;
   88   
   89   	public EventProcessingThreadImpl() {
   90   //		if (log.debugable()) log.debug("Starting an event processing thread");
   91   		Threads.setDaemon(this, true);
   92   		start();
   93   	}
   94   
   95   	//EventProcessingThread//
   96   	public boolean isCeased() {
   97   		return _ceased != null;
   98   	}
   99   	public boolean isSuspended() {
  100   		return _suspended;
  101   	}
  102   	synchronized public boolean isIdle() {
  103   		return _proc == null;
  104   	}
  105   	public final Event getEvent() {
  106   		return _proc.getEvent();
  107   	}
  108   	public final Component getComponent() {
  109   		return _proc.getComponent();
  110   	}
  111   	public void sendEvent(final Component comp, Event event)
  112   	throws Exception {
  113   //		if (log.finerable()) log.finer("Process sent event: "+event);
  114   		if (event == null || comp == null)
  115   			throw new IllegalArgumentException("Both comp and event must be specified");
  116   		if (!(Thread.currentThread() instanceof EventProcessingThreadImpl))
  117   			throw new IllegalStateException("Only callable when processing an event");
  118   
  119   		final EventProcessor oldproc = _proc;
  120   		_proc = new EventProcessor(_proc.getDesktop(), comp, event);
  121   		try {
  122   			setup();
  123   			process0();
  124   		} finally {
  125   			_proc = oldproc;
  126   			setup();
  127   		}
  128   	}
  129   
  130   	//extra utilities//
  131   	/** Stops the thread. Called only by {@link org.zkoss.zk.ui.sys.UiEngine}
  132   	 * when it is stopping.
  133   	 * <p>Application developers shall use {@link org.zkoss.zk.ui.sys.DesktopCtrl#ceaseSuspendedThread}
  134   	 * instead.
  135   	 *
  136   	 * @param cause a human readable text describing the cause.
  137   	 * If null, an empty string is assumed.
  138   	 */
  139   	public void cease(String cause) {
  140   		synchronized (_evtmutex) {
  141   			_ceased = cause != null ? cause: "";
  142   			_evtmutex.notifyAll();
  143   		}
  144   		if (_suspmutex != null) {
  145   			synchronized (_suspmutex) {
  146   				_suspmutex.notifyAll();
  147   			}
  148   		}
  149   	}
  150   	/** Stops the thread silently. Called by {@link org.zkoss.zk.ui.sys.UiEngine}
  151   	 * to stop abnormally.
  152   	 */
  153   	public void ceaseSilently(String cause) {
  154   		_silent = true;
  155   		cease(cause);
  156   	}
  157   
  158   	/** Returns the number of event threads.
  159   	 */
  160   	public static final int getThreadNumber() {
  161   		return _nThd;
  162   	}
  163   	/** Returns the number of event threads in processing.
  164   	 */
  165   	public static final int getThreadNumberInProcessing() {
  166   		return _nBusyThd;
  167   	}
  168   
  169   	/** Suspends the current thread and Waits until {@link #doResume}
  170   	 * is called.
  171   	 *
  172   	 * <p>Note:
  173   	 * <ul>
  174   	 * <li>It is used internally only for implementing {@link org.zkoss.zk.ui.sys.UiEngine}
  175   	 * Don't call it directly.
  176   	 * <li>Caller must invoke {@link #newEventThreadSuspends}
  177   	 * before calling this method. (Reason: UiEngine might have to store some info
  178   	 * after {@link #newEventThreadSuspends} is called.
  179   	 * <li>The current thread must be {@link EventProcessingThreadImpl}.
  180   	 * <li>It is a static method.
  181   	 * </ul>
  182   	 */
  183   	public static void doSuspend(Object mutex) throws InterruptedException {
  184   		((EventProcessingThreadImpl)Thread.currentThread()).doSuspend0(mutex);
  185   	}
  186   	private void doSuspend0(Object mutex) throws InterruptedException {
  187   //		if (log.finerable()) log.finer("Suspend event processing; "+_proc);
  188   		if (mutex == null)
  189   			throw new IllegalArgumentException("null mutex");
  190   		if (isIdle())
  191   			throw new InternalError("Called without processing event?");
  192   		if (_suspmutex != null)
  193   			throw new InternalError("Suspend twice?");
  194   
  195   		//Spec: locking mutex is optional for app developers
  196   		//so we have to lock it first
  197   		_suspmutex = mutex;
  198   		try {
  199   			synchronized (_suspmutex) {
  200   				_suspended = true;
  201   
  202   				//Bug 1814298: need to call Execution.onDeactivate
  203   				Execution exec = getExecution();
  204   				if (exec != null) {
  205   					((ExecutionCtrl)exec).onDeactivate();
  206   					_acted = false;
  207   				}
  208   
  209   				//let the main thread continue
  210   				synchronized (_evtmutex) {
  211   					_evtmutex.notify();
  212   				}
  213   
  214   				if (_ceased == null) _suspmutex.wait();
  215   			}
  216   		} finally {
  217   			_suspmutex = null;
  218   			_suspended = false; //just in case (such as _ceased)
  219   		}
  220   
  221   		if (_ceased != null)
  222   			throw new InterruptedException(_ceased);
  223   
  224   		//being resumed
  225   		setup();
  226   		Execution exec = getExecution();
  227   		if (exec != null) {
  228   			((ExecutionCtrl)exec).onActivate();
  229   			_acted = true;
  230   		}
  231   
  232   		final List resumes = _evtThdResumes;
  233   		_evtThdResumes = null;
  234   		if (resumes != null && !resumes.isEmpty()) {
  235   			_proc.getDesktop().getWebApp().getConfiguration()
  236   				.invokeEventThreadResumes(
  237   					resumes, getComponent(), getEvent());
  238   				//FUTURE: how to propogate errors to the client
  239   		}
  240   	}
  241   	private Execution getExecution() {
  242   		Execution exec = _proc.getDesktop().getExecution();
  243   		return exec != null ? exec: Executions.getCurrent();
  244   			//just in case that the execution is dead first
  245   	}
  246   	/** Resumes this thread and returns only if the execution (being suspended
  247   	 * by {@link #doSuspend}) completes.
  248   	 *
  249   	 * <p>It executes in the main thread (i.e., the servlet thread).
  250   	 *
  251   	 * @return whether the event has been processed completely or just be suspended
  252   	 */
  253   	public boolean doResume() throws InterruptedException {
  254   		if (this.equals(Thread.currentThread()))
  255   			throw new IllegalStateException("A thread cannot resume itself");
  256   //		if (log.finerable()) log.finer("Resume event processing; "+_proc);
  257   		if (isIdle())
  258   			throw new InternalError("Called without processing event?");
  259   		if (_suspmutex == null)
  260   			throw new InternalError("Resume non-suspended thread?");
  261   
  262   		//Copy first since event thread clean up them, when completed
  263   		final Configuration config =
  264   			_proc.getDesktop().getWebApp().getConfiguration();
  265   		final Component comp = getComponent();
  266   		final Event event = getEvent();
  267   		try {
  268   			_evtThdResumes = config.newEventThreadResumes(comp, event);
  269   
  270   			//Spec: locking mutex is optional for app developers
  271   			//so we have to lock it first
  272   			synchronized (_suspmutex) {
  273   				_suspended = false;
  274   				_suspmutex.notify(); //wake the suspended event thread
  275   			}
  276   
  277   			//wait until the event thread completes or suspends again
  278   			//If complete: isIdle() is true
  279   			//If suspend again: _suspended is true
  280   			synchronized (_evtmutex) {
  281   				if (_ceased == null && !isIdle() && !_suspended)
  282   					_evtmutex.wait();
  283   			}
  284   		} finally {
  285   			//_evtThdCleanups is null if //1) no listener;
  286   			//2) the event thread is suspended again (handled by another doResume)
  287   			invokeEventThreadCompletes(config, comp, event);
  288   		}
  289   
  290   		checkError();
  291   		return isIdle();
  292   	}
  293   
  294   	/** Ask this event thread to process the specified event.
  295   	 *
  296   	 * <p>Used internally to implement {@link org.zkoss.zk.ui.sys.UiEngine}.
  297   	 * Application developers
  298   	 * shall use {@link org.zkoss.zk.ui.event.Events#sendEvent} instead.
  299   	 *
  300   	 * @return whether the event has been processed completely or just be suspended.
  301   	 * Recycle it only if true is returned.
  302   	 */
  303   	public boolean processEvent(Desktop desktop, Component comp, Event event) {
  304   		if (Thread.currentThread() instanceof EventProcessingThreadImpl)
  305   			throw new IllegalStateException("processEvent cannot be called in an event thread");
  306   		if (_ceased != null)
  307   			throw new InternalError("The event thread has beeing stopped. Cause: "+_ceased);
  308   		if (_proc != null)
  309   			throw new InternalError("reentering processEvent not allowed");
  310   
  311   		_locale = Locales.getCurrent();
  312   		_timeZone = TimeZones.getCurrent();
  313   		_ex = null;
  314   
  315   		final EventProcessor proc = new EventProcessor(desktop, comp, event);
  316   			//it also check the correctness of desktop/comp/event
  317   		final Configuration config = desktop.getWebApp().getConfiguration();
  318   		_evtThdInits = config.newEventThreadInits(comp, event);
  319   		try {
  320   			synchronized (_evtmutex) {
  321   				_proc = proc; //Bug 1577842: don't let event thread start (and end) too early
  322   
  323   				_evtmutex.notify(); //ask the event thread to handle it
  324   				if (_ceased == null) {
  325   					_evtmutex.wait();
  326   						//wait until the event thread to complete or suspended
  327   
  328   					if (_suspended) {
  329   						config.invokeEventThreadSuspends(_evtThdSuspends, comp, event);
  330   						_evtThdSuspends = null;
  331   					}
  332   				}
  333   			}
  334   		} catch (InterruptedException ex) {
  335   			throw new UiException(ex);
  336   		} finally {
  337   			//_evtThdCleanups is null if //1) no listener;
  338   			//2) the event thread is suspended (then handled by doResume).
  339   			invokeEventThreadCompletes(config, comp, event);
  340   		}
  341   
  342   		checkError(); //check any error occurs
  343   		return isIdle();
  344   	}
  345   	/** Invokes {@link Configuration#newEventThreadSuspends}.
  346   	 * The caller must execute in the event processing thread.
  347   	 * It is called only for implementing {@link org.zkoss.zk.ui.sys.UiEngine}.
  348   	 * Don't call it directly.
  349   	 */
  350   	public void newEventThreadSuspends(Object mutex) {
  351   		if (_proc == null)
  352   			throw new IllegalStateException();
  353   
  354   		_evtThdSuspends = _proc.getDesktop().getWebApp().getConfiguration()
  355   			.newEventThreadSuspends(getComponent(), getEvent(), mutex);
  356   			//it might throw an exception, so process it before updating
  357   			//_suspended
  358   	}
  359   
  360   	private void invokeEventThreadCompletes(Configuration config,
  361   	Component comp, Event event) throws UiException {
  362   		if (_evtThdCleanups != null && !_evtThdCleanups.isEmpty()) {
  363   			final List errs = _ex != null ? null: new LinkedList();
  364   
  365   			config.invokeEventThreadCompletes(_evtThdCleanups, comp, event, errs);
  366   
  367   			if (errs != null && !errs.isEmpty())
  368   				throw UiException.Aide.wrap((Throwable)errs.get(0));
  369   		}
  370   		_evtThdCleanups = null;
  371   	}
  372   	/** Setup for execution. */
  373   	synchronized private void setup() {
  374   		_proc.setup();
  375   	}
  376   	/** Cleanup for execution. */
  377   	synchronized private void cleanup() {
  378   		_proc.cleanup();
  379   		_proc = null;
  380   	}
  381   	private void checkError() {
  382   		if (_ex != null) { //failed to process
  383   //			if (log.debugable()) log.realCause(_ex);
  384   			final Throwable ex = _ex;
  385   			_ex = null;
  386   			throw UiException.Aide.wrap(ex);
  387   		}
  388   	}
  389   
  390   	//-- Thread --//
  391   	public void run() {
  392   		++_nThd;
  393   		try {
  394   			while (_ceased == null) {
  395   				final boolean evtAvail = !isIdle();
  396   				if (evtAvail) {
  397   					final Configuration config =
  398   						_proc.getDesktop().getWebApp().getConfiguration();
  399   					boolean cleaned = false;
  400   					++_nBusyThd;
  401   					Execution exec = null;
  402   					try {
  403   //						if (log.finerable()) log.finer("Processing event: "+_proc);
  404   
  405   						Locales.setThreadLocal(_locale);
  406   						TimeZones.setThreadLocal(_timeZone);
  407   
  408   						setup();
  409   						exec = getExecution();
  410   						if (exec != null) {
  411   							((ExecutionCtrl)exec).onActivate();
  412   							_acted = true;
  413   						}
  414   
  415   						final boolean b = config.invokeEventThreadInits(
  416   							_evtThdInits, getComponent(), getEvent());
  417   						_evtThdInits = null;
  418   
  419   						if (b) process0();
  420   					} catch (Throwable ex) {
  421   						cleaned = true;
  422   						newEventThreadCleanups(config, ex);
  423   					} finally {
  424   						--_nBusyThd;
  425   
  426   						if (!cleaned) newEventThreadCleanups(config, _ex);
  427   
  428   //						if (log.finerable()) log.finer("Real processing is done: "+_proc);
  429   						if (exec != null && _acted) //_acted is false if suspended is killed
  430   							((ExecutionCtrl)exec).onDeactivate();
  431   						cleanup();
  432   
  433   						Locales.setThreadLocal(_locale = null);
  434   						TimeZones.setThreadLocal(_timeZone = null);
  435   					}
  436   				}
  437   
  438   				synchronized (_evtmutex) {
  439   					if (evtAvail)
  440   						_evtmutex.notify();
  441   						//wake the main thread OR the resuming thread
  442   					if (_ceased == null)
  443   						_evtmutex.wait();
  444   						//wait the main thread to issue another request
  445   				}
  446   			}
  447   
  448   			if (_silent) {
  449   //				if (log.debugable()) log.debug("The event processing thread stops");
  450   			} else {
  451   				System.out.println("The event processing thread stops");
  452   				//Don't use log because it might be stopped
  453   			}
  454   		} catch (InterruptedException ex) {
  455   			if (_silent) {
  456   //				if (log.debugable())
  457   //					log.debug("The event processing thread interrupted: "+Exceptions.getMessage(ex)
  458   //						+"\n"+Exceptions.getBriefStackTrace(ex));
  459   			} else {	
  460   				System.out.println("The event processing thread interrupted: "+Exceptions.getMessage(ex));
  461   				//Don't use log because it might be stopped
  462   			}
  463   		} finally {
  464   			--_nThd;
  465   		}
  466   	}
  467   	/** Invokes {@link Configuration#newEventThreadCleanups}.
  468   	 */
  469   	private void newEventThreadCleanups(Configuration config, Throwable ex) {
  470   		final List errs = new LinkedList();
  471   		if (ex != null) errs.add(ex);
  472   		_evtThdCleanups =
  473   			config.newEventThreadCleanups(getComponent(), getEvent(), errs);
  474   		_ex = errs.isEmpty() ? null: (Throwable)errs.get(0);
  475   			//propogate back the first exception
  476   	}
  477   
  478   	/** Processes the component and event.
  479   	 */
  480   	private void process0() throws Exception {
  481   		if (_proc == null)
  482   			throw new IllegalStateException("Not initialized");
  483   		_proc.process();
  484   	}
  485   
  486   	//-- Object --//
  487   	public String toString() {
  488   		return "[" +getName()+": "+_proc+", ceased="+_ceased+']';
  489   	}
  490   }

Save This Page
Home » zk-src-3.5.1 » org » zkoss » zk » ui » impl » [javadoc | source]