All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zkoss.zkex.ui.impl.PollingServerPush Maven / Gradle / Ivy

There is a newer version: 3.6.3
Show newest version
/* PollingServerPush.java

{{IS_NOTE
	Purpose:
		
	Description:
		
	History:
		Fri Aug  3 18:53:21     2007, Created by tomyeh
}}IS_NOTE

Copyright (C) 2007 Potix Corporation. All Rights Reserved.

{{IS_RIGHT
	This program is distributed under GPL Version 2.0 in the hope that
	it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zkex.ui.impl;

import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;

import org.zkoss.lang.D;
import org.zkoss.util.logging.Log;

import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.DesktopUnavailableException;
import org.zkoss.zk.ui.sys.ServerPush;
import org.zkoss.zk.ui.impl.ExecutionCarryOver;
import org.zkoss.zk.ui.util.Configuration;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zk.au.out.AuScript;

/**
 * A server-push implementation that is based on client-polling.
 *
 * @author tomyeh
 * @since 3.0.0
 */
public class PollingServerPush implements ServerPush {
	private static final Log log = Log.lookup(PollingServerPush.class);
	/** Denote a server-push thread gives up the activation (timeout). */
	private static final int GIVEUP = -99;

	private Desktop _desktop;
	/** List of ThreadInfo. */
	private final List _pending = new LinkedList();
	/** The active thread. */
	private ThreadInfo _active;
	/** The info to carray over from onPiggyback to the server-push thread. */
	private ExecutionCarryOver _carryOver;
	/** A mutex that is used by this object to wait for the server-push thread
	 * to complete.
	 */
	private final Object _mutex = new Object();

	/** Returns the JavaScript codes to enable (aka., start) the server push.
	 */
	protected String getStartScript() {
		final String start = _desktop.getWebApp().getConfiguration()
			.getPreference("PollingServerPush.start", null);
		if (start != null)
			return start;

		final String dtid = _desktop.getId();
		final StringBuffer sb = new StringBuffer(128)
			.append("zk.invoke('zkex.ui.cpsp',function(){zkCpsp.start('")
			.append(dtid).append('\'');

		final int v1 = getIntPref("PollingServerPush.delay.min"),
			v2 = getIntPref("PollingServerPush.delay.max");
		if (v1 > 0  && v2 > 0)
			sb.append(',').append(v1).append(',').append(v2);

		return sb.append(");},'").append(dtid).append("');").toString();
	}
	private int getIntPref(String key) {
		final String s = _desktop.getWebApp().getConfiguration()
			.getPreference(key, null);
		if (s != null) {
			try {
				return Integer.parseInt(s);
			} catch (NumberFormatException ex) {
				log.warning("Not a number specified at "+key);
			}
		}
		return -1;
	}
			
	/** Returns the JavaScript codes to disable (aka., stop) the server push.
	 */
	protected String getStopScript() {
		final String stop = _desktop.getWebApp().getConfiguration()
			.getPreference("PollingServerPush.stop", null);
		return stop != null ? stop:
			"zkCpsp.stop('" + _desktop.getId() + "');";
	}


	//ServerPush//
	public void start(Desktop desktop) {
		if (_desktop != null) {
			log.warning("Ignored: Sever-push already started");
			return;
		}

		_desktop = desktop;
		Clients.response(new AuScript(null, getStartScript()));
	}
	public void stop() {
		if (_desktop == null) {
			log.warning("Ignored: Sever-push not started");
			return;
		}

		final boolean inexec = Executions.getCurrent() != null;
		if (inexec) //Bug 1815480: don't send if timeout
			Clients.response(new AuScript(null, getStopScript()));

		_desktop = null; //to cause DesktopUnavailableException being thrown

		synchronized (_pending) {
			for (Iterator it = _pending.iterator(); it.hasNext();) {
				final ThreadInfo info = (ThreadInfo)it.next();
				synchronized (info) {
					info.notify();
				}
			}
			_pending.clear();
		}
		//if inexec, either in working thread, or other event listener
		//if in working thread, we cannot notify here (too early to wake).
		//if other listener, no need notify (since onPiggyback not running)
		if (!inexec) {
			synchronized (_mutex) {
				_mutex.notify(); //wake up onPiggyback
			}
		}
	}
	/** Sets the delay between each polling request.
	 * 

Default: use the preference called * PollingServerPush.delay.min * PollingServerPush.delay.max, * and PollingServerPush.delay.factor. * If not defined, min is 1000, max is 15000, and factor is 5. */ public void setDelay(int min, int max, int factor) { Clients.response( new AuScript(null, "zkau.setSPushInfo('" + _desktop.getId() + "',{min:" + min + ",max:" + max + ",factor:" + factor + "})")); } public void onPiggyback() { final Configuration config = _desktop.getWebApp().getConfiguration(); long tmexpired = 0; for (int cnt = 0; !_pending.isEmpty();) { //Don't hold the client too long. //In addition, an ill-written code might activate again //before onPiggyback returns. It causes dead-loop in this case. if (tmexpired == 0) { //first time tmexpired = System.currentTimeMillis() + (config.getMaxProcessTime() >> 1); cnt = _pending.size() + 3; } else if (--cnt < 0 || System.currentTimeMillis() > tmexpired) { break; } final ThreadInfo info; synchronized (_pending) { if (_pending.isEmpty()) return; //nothing to do info = (ThreadInfo)_pending.remove(0); } //Note: we have to sync _mutex before info. Otherwise, //sync(info) might cause deactivate() to run before _mutex.wait synchronized (_mutex) { _carryOver = new ExecutionCarryOver(_desktop); synchronized (info) { if (info.nActive == GIVEUP) continue; //give up and try next info.nActive = 1; //granted info.notify(); } if (_desktop == null) //just in case break; try { _mutex.wait(); //wait until the server push is done } catch (InterruptedException ex) { throw UiException.Aide.wrap(ex); } } } } public boolean activate(long timeout) throws InterruptedException, DesktopUnavailableException { final Thread curr = Thread.currentThread(); if (_active != null && _active.thread.equals(curr)) { //re-activate ++_active.nActive; return true; } final ThreadInfo info = new ThreadInfo(curr); synchronized (_pending) { if (_desktop != null) _pending.add(info); } boolean loop; do { loop = false; synchronized (info) { if (_desktop != null) { if (info.nActive == 0) //not granted yet info.wait(timeout <= 0 ? 10*60*1000: timeout); if (info.nActive <= 0) { //not granted boolean bTimeout = timeout > 0; boolean bDead = _desktop == null || !_desktop.isAlive(); if (bTimeout || bDead) { //not timeout info.nActive = GIVEUP; //denote timeout (and give up) synchronized (_pending) { //undo pending _pending.remove(info); } if (bDead) throw new DesktopUnavailableException("Stopped"); return false; //timeout } log.debug("Executions.activate() took more than 10 minutes"); loop = true; //try again } } } } while (loop); if (_desktop == null) throw new DesktopUnavailableException("Stopped"); _carryOver.carryOver(); _active = info; return true; //Note: we don't mimic inEventListener since 1) ZK doesn't assume it //2) Window depends on it } public void deactivate() { if (_active != null && Thread.currentThread().equals(_active.thread)) { if (--_active.nActive <= 0) { _carryOver.cleanup(); _carryOver = null; _active.nActive = 0; //just in case _active = null; //wake up onPiggyback synchronized (_mutex) { _mutex.notify(); } try {Thread.sleep(100);} catch (Throwable ex) {} //to minimize the chance that the server-push thread //activate again, before onPiggback polls next _pending } } } /** The info of a server-push thread. * It is also a mutex used to start a pending server-push thread. */ private static class ThreadInfo { private final Thread thread; /** # of activate() was called. */ private int nActive; private ThreadInfo(Thread thread) { this.thread = thread; } public String toString() { return "[" + thread + ',' + nActive + ']'; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy