diff --git a/org.adempiere.ui.zk/.classpath b/org.adempiere.ui.zk/.classpath
index e5e200230c..97012df4ab 100644
--- a/org.adempiere.ui.zk/.classpath
+++ b/org.adempiere.ui.zk/.classpath
@@ -1,8 +1,13 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.adempiere.ui.zk/META-INF/MANIFEST.MF b/org.adempiere.ui.zk/META-INF/MANIFEST.MF
index 2b05b7bf43..265a1e0172 100644
--- a/org.adempiere.ui.zk/META-INF/MANIFEST.MF
+++ b/org.adempiere.ui.zk/META-INF/MANIFEST.MF
@@ -3,7 +3,20 @@ Bundle-ManifestVersion: 2
Bundle-Name: Zk Web Client
Bundle-SymbolicName: org.adempiere.ui.zk;singleton:=true
Bundle-Version: 1.0.0.qualifier
-Web-ContextPath: webui
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Import-Package: javax.servlet,
+ javax.servlet.http,
+ metainfo.zk,
+ org.apache.commons.codec.binary,
+ org.apache.ecs,
+ org.apache.ecs.xhtml,
+ org.osgi.framework;version="1.5.0"
+Bundle-ClassPath: WEB-INF/classes/,
+ WEB-INF/lib/atmosphere-runtime-0.9.jar,
+ WEB-INF/lib/atmosphere-compat-jbossweb-0.9.jar,
+ WEB-INF/lib/atmosphere-compat-tomcat-0.9.jar,
+ WEB-INF/lib/atmosphere-compat-tomcat7-0.9.jar,
+ WEB-INF/lib/slf4j-api-1.6.1.jar
Export-Package: metainfo.zk,
org.adempiere.webui,
org.adempiere.webui.acct,
@@ -27,22 +40,31 @@ Export-Package: metainfo.zk,
org.adempiere.webui.session,
org.adempiere.webui.theme,
org.adempiere.webui.util,
- org.adempiere.webui.window
+ org.adempiere.webui.window,
+ org.atmosphere.cache,
+ org.atmosphere.client,
+ org.atmosphere.config,
+ org.atmosphere.container,
+ org.atmosphere.container.version,
+ org.atmosphere.cpr,
+ org.atmosphere.di,
+ org.atmosphere.handler,
+ org.atmosphere.util,
+ org.atmosphere.util.uri,
+ org.atmosphere.websocket,
+ org.atmosphere.websocket.protocol,
+ org.jboss.servlet.http,
+ org.apache.catalina,
+ org.apache.catalina.comet,
+ org.slf4j.helpers,
+ org.slf4j.spi
Require-Bundle: org.adempiere.report.jasper;bundle-version="1.0.0",
org.adempiere.base;bundle-version="1.0.0",
org.adempiere.report.jasper.library;bundle-version="1.0.0",
org.adempiere.ui;bundle-version="1.0.0",
org.zkoss.zk.library;bundle-version="6.0.0"
-Bundle-RequiredExecutionEnvironment: JavaSE-1.6
-Eclipse-ExtensibleAPI: true
-Import-Package: javax.servlet,
- javax.servlet.http,
- metainfo.zk,
- org.apache.commons.codec.binary,
- org.apache.ecs,
- org.apache.ecs.xhtml,
- org.osgi.framework;version="1.5.0"
-Bundle-ActivationPolicy: lazy
Bundle-Activator: org.adempiere.webui.WebUIActivator
-Bundle-ClassPath: WEB-INF/classes/
+Bundle-ActivationPolicy: lazy
+Eclipse-ExtensibleAPI: true
Eclipse-RegisterBuddy: org.zkoss.zk.library
+Web-ContextPath: webui
diff --git a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java
new file mode 100644
index 0000000000..b6cdf89995
--- /dev/null
+++ b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java
@@ -0,0 +1,366 @@
+package fi.jawsy.jawwa.zk.atmosphere;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.atmosphere.cpr.AtmosphereResource;
+import org.zkoss.lang.Library;
+import org.zkoss.util.logging.Log;
+import org.zkoss.zk.au.out.AuScript;
+import org.zkoss.zk.ui.Desktop;
+import org.zkoss.zk.ui.DesktopUnavailableException;
+import org.zkoss.zk.ui.Execution;
+import org.zkoss.zk.ui.Executions;
+import org.zkoss.zk.ui.UiException;
+import org.zkoss.zk.ui.event.Event;
+import org.zkoss.zk.ui.event.EventListener;
+import org.zkoss.zk.ui.impl.ExecutionCarryOver;
+import org.zkoss.zk.ui.sys.DesktopCtrl;
+import org.zkoss.zk.ui.sys.Scheduler;
+import org.zkoss.zk.ui.sys.ServerPush;
+import org.zkoss.zk.ui.util.Clients;
+import org.zkoss.zk.ui.util.Configuration;
+
+/**
+ * ZK server push implementation based on Atmosphere.
+ *
+ * Only supports asynchronous updates (Executions.schedule) and will throw exceptions if synchronous updates
+ * (Executions.activate/deactivate) is attempted.
+ */
+public class AtmosphereServerPush implements ServerPush {
+ private static final Log log = Log.lookup(AtmosphereServerPush.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;
+// private final int _min, _max, _factor;
+ /** A mutex that is used by this object to wait for the server-push thread
+ * to complete.
+ */
+ private final Object _mutex = new Object();
+
+ public static final int DEFAULT_TIMEOUT = 1000 * 60 * 5;
+ private final AtomicReference resource = new AtomicReference();
+ private final int timeout;
+
+ public AtmosphereServerPush() {
+// this(-1, -1, -1);
+// }
+//
+// public AtmosphereServerPush(int min, int max, int factor) {
+// _min = min;
+// _max = max;
+// _factor = factor;
+
+ String timeoutString = Library.getProperty("fi.jawsy.jawwa.zk.atmosphere.timeout");
+ if (timeoutString == null || timeoutString.length() == 0) {
+ timeout = DEFAULT_TIMEOUT;
+ } else {
+ timeout = Integer.valueOf(timeoutString);
+ }
+ }
+
+ protected void startClientPush() {
+// Clients.response("zk.clientpush", new AuScript(null, getStartScript()));
+ Clients.response("jawwa.atmosphere.serverpush", new AuScript(null, getStartScript()));
+ }
+
+ protected void stopClientPush() {
+// Clients.response("zk.clientpush", new AuScript(null, getStopScript()));
+ Clients.response("jawwa.atmosphere.serverpush", new AuScript(null, getStopScript()));
+ }
+
+ protected String getStartScript() {
+// final String start = _desktop.getWebApp().getConfiguration()
+// .getPreference("PollingServerPush.start", null);
+// if (start != null)
+// return start;
+//
+// final StringBuffer sb = new StringBuffer(128)
+// .append("zk.load('zk.cpsp');zk.afterLoad(function(){zk.cpsp.start('")
+// .append(_desktop.getId()).append('\'');
+//
+// final int min = _min > 0 ? _min: getIntPref("PollingServerPush.delay.min"),
+// max = _max > 0 ? _max: getIntPref("PollingServerPush.delay.max"),
+// factor = _factor > 0 ? _factor: getIntPref("PollingServerPush.delay.factor");
+// if (min > 0 || max > 0 || factor > 0)
+// sb.append(',').append(min).append(',').append(max)
+// .append(',').append(factor);
+//
+// return sb.append(");});").toString();
+
+ int clientTimeout = timeout + 1000 * 60;
+ return "jawwa.atmosphere.startServerPush('" + _desktop.getId() + "', " + clientTimeout + ");";
+ }
+
+// 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;
+// }
+
+ protected String getStopScript() {
+// final String stop = _desktop.getWebApp().getConfiguration()
+// .getPreference("PollingServerPush.stop", null);
+// return stop != null ? stop:
+// "zk.cpsp.stop('" + _desktop.getId() + "');";
+ return "jawwa.atmosphere.stopServerPush('" + _desktop.getId() + "');";
+ }
+
+ @Override
+ public boolean isActive() {
+ return _active != null && _active.nActive > 0;
+ }
+
+ @Override
+ public void start(Desktop desktop) {
+ if (_desktop != null) {
+ log.warning("Ignored: Sever-push already started");
+ return;
+ }
+
+ _desktop = desktop;
+ startClientPush();
+ }
+
+ @Override
+ public void stop() {
+ if (_desktop == null) {
+ log.warning("Ignored: Sever-push not started");
+ return;
+ }
+
+ final Execution exec = Executions.getCurrent();
+ final boolean inexec = exec != null && exec.getDesktop() == _desktop;
+ //it might be caused by DesktopCache expunge (when creating another desktop)
+ try {
+ if (inexec && _desktop.isAlive()) //Bug 1815480: don't send if timeout
+ stopClientPush();
+ commitResponse();
+ } finally {
+ _desktop = null; //to cause DesktopUnavailableException being thrown
+ wakePending();
+
+ //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
+ }
+ }
+ }
+ }
+
+ private void wakePending() {
+ synchronized (_pending) {
+ for (ThreadInfo info: _pending) {
+ synchronized (info) {
+ info.notify();
+ }
+ }
+ _pending.clear();
+ }
+ }
+
+ @Override
+ 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 = _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);
+ }
+ }
+ }
+ }
+
+ @Override
+ public
+ void schedule(EventListener listener, T event, Scheduler scheduler) {
+ scheduler.schedule(listener, event); //delegate back
+ commitResponse();
+ }
+
+ @Override
+ 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
+ }
+
+ @Override
+ public boolean deactivate(boolean stop) {
+ boolean stopped = false;
+ if (_active != null &&
+ Thread.currentThread().equals(_active.thread)) {
+ if (--_active.nActive <= 0) {
+ if (stop)
+ stopClientPush();
+
+ _carryOver.cleanup();
+ _carryOver = null;
+ _active.nActive = 0; //just in case
+ _active = null;
+
+ if (stop) {
+ wakePending();
+ _desktop = null;
+ stopped = true;
+ }
+
+ //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
+ }
+ }
+ return stopped;
+ }
+
+ public void clearResource(AtmosphereResource resource) {
+ this.resource.compareAndSet(resource, null);
+ }
+
+ private void commitResponse() {
+ AtmosphereResource resource = this.resource.getAndSet(null);
+ if (resource != null) {
+ resource.resume();
+ }
+ }
+
+ public void updateResource(AtmosphereResource resource) {
+ commitResponse();
+
+ boolean shouldSuspend = true;
+ if (_desktop == null) {
+ return;
+ }
+
+ if (_desktop instanceof DesktopCtrl)
+ {
+ DesktopCtrl desktopCtrl = (DesktopCtrl) _desktop;
+ shouldSuspend = !desktopCtrl.scheduledServerPush();
+ }
+
+ if (shouldSuspend) {
+ resource.suspend(timeout, false);
+ this.resource.set(resource);
+ } else {
+ this.resource.set(null);
+ }
+ }
+
+ 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 + ']';
+ }
+ }
+
+}
diff --git a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java
new file mode 100644
index 0000000000..d93d3b6a1b
--- /dev/null
+++ b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java
@@ -0,0 +1,120 @@
+package fi.jawsy.jawwa.zk.atmosphere;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.atmosphere.cpr.AtmosphereHandler;
+import org.atmosphere.cpr.AtmosphereRequest;
+import org.atmosphere.cpr.AtmosphereResource;
+import org.atmosphere.cpr.AtmosphereResourceEvent;
+import org.atmosphere.cpr.AtmosphereResponse;
+import org.zkoss.zk.ui.Desktop;
+import org.zkoss.zk.ui.Session;
+import org.zkoss.zk.ui.WebApp;
+import org.zkoss.zk.ui.http.WebManager;
+import org.zkoss.zk.ui.sys.DesktopCtrl;
+import org.zkoss.zk.ui.sys.ServerPush;
+import org.zkoss.zk.ui.sys.WebAppCtrl;
+
+/**
+ * Atmosphere handler that integrates Atmosphere with ZK server push.
+ */
+public class ZkAtmosphereHandler implements AtmosphereHandler {
+ private String err;
+
+ private AtmosphereServerPush getServerPush(AtmosphereResource resource) {
+ AtmosphereRequest request = resource.getRequest();
+
+ Session session = WebManager.getSession(resource.getAtmosphereConfig().getServletContext(), request, false);
+
+ if (session == null)
+ {
+ err = "Could not find session";
+ return null;
+ }
+ else
+ {
+ String desktopId = request.getParameter("dtid");
+ if (desktopId == null || desktopId.length() == 0)
+ {
+ err = "Could not find desktop id";
+ return null;
+ }
+
+ WebApp webApp = session.getWebApp();
+ if (webApp instanceof WebAppCtrl)
+ {
+ WebAppCtrl webAppCtrl = (WebAppCtrl) webApp;
+ Desktop desktop = webAppCtrl.getDesktopCache(session).getDesktopIfAny(desktopId);
+ if (desktop == null)
+ {
+ err = "Could not find desktop";
+ return null;
+ }
+
+ if (desktop instanceof DesktopCtrl)
+ {
+ DesktopCtrl desktopCtrl = (DesktopCtrl) desktop;
+
+ ServerPush serverPush = desktopCtrl.getServerPush();
+ if (serverPush == null)
+ {
+ err = "Server push is not enabled";
+ return null;
+ }
+
+ if (desktopCtrl.getServerPush() instanceof AtmosphereServerPush)
+ {
+ AtmosphereServerPush atmosphereServerPush = (AtmosphereServerPush) serverPush;
+ if (atmosphereServerPush != null)
+ return atmosphereServerPush;
+ }
+
+ err = "Server push implementation is not AtmosphereServerPush";
+ return null;
+ }
+
+ err = "Desktop does not implement DesktopCtrl";
+ return null;
+ }
+
+ err = "Webapp does not implement WebAppCtrl";
+ return null;
+ }
+ }
+
+ @Override
+ public void onRequest(AtmosphereResource resource) throws IOException {
+ AtmosphereResponse response = resource.getResponse();
+
+ response.setContentType("text/plain");
+
+ AtmosphereServerPush serverPush = getServerPush(resource);
+ if (serverPush == null)
+ {
+ response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ response.getWriter().write(err);
+ return;
+ }
+
+ serverPush.updateResource(resource);
+ }
+
+ @Override
+ public void onStateChange(AtmosphereResourceEvent event) throws IOException {
+ AtmosphereResource resource = event.getResource();
+
+ if (event.isCancelled() || event.isResumedOnTimeout()) {
+ AtmosphereServerPush serverPush = getServerPush(resource);
+ if (serverPush != null)
+ serverPush.clearResource(resource);
+ }
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+
+}
diff --git a/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml b/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml
index d1c89d0c29..ac27fe4f25 100644
--- a/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml
+++ b/org.adempiere.ui.zk/WEB-INF/src/metainfo/zk/lang-addon.xml
@@ -38,4 +38,6 @@ Copyright (C) 2007 Ashley G Ramdass (ADempiere WebUI).
+
+
diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/serverpush.js b/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/serverpush.js
new file mode 100644
index 0000000000..0f24ca8cab
--- /dev/null
+++ b/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/serverpush.js
@@ -0,0 +1,79 @@
+(function() {
+ jawwa.atmosphere.startServerPush = function(dtid, timeout) {
+ var dt = zk.Desktop.$(dtid);
+ if (dt._serverpush)
+ dt._serverpush.stop();
+
+ var spush = new jawwa.atmosphere.ServerPush(dt, timeout);
+ spush.start();
+ };
+ jawwa.atmosphere.stopServerPush = function(dtid) {
+ var dt = zk.Desktop.$(dtid);
+ if (dt._serverpush)
+ dt._serverpush.stop();
+ };
+ jawwa.atmosphere.ServerPush = zk.$extends(zk.Object, {
+ desktop: null,
+ active: false,
+ timeout: 300000,
+ delay: 1000,
+ failures: 0,
+
+ $init: function(desktop, timeout) {
+ this.desktop = desktop;
+ this.timeout = timeout;
+ },
+ _schedule: function() {
+ if (this.failures < 10) {
+ var delay = this.delay * Math.pow(2, Math.min(this.failures, 7));
+ setTimeout(this.proxy(this._send), delay);
+ } else {
+ this.stop();
+ }
+ },
+ _send: function() {
+ if (!this.active)
+ return;
+
+ var me = this;
+ var jqxhr = $.ajax({
+ url: zk.ajaxURI("/comet", {
+ au: true
+ }),
+ type: "GET",
+ cache: false,
+ async: true,
+ global: false,
+ data: {
+ dtid: this.desktop.id
+ },
+ accepts: "text/plain",
+ dataType: "text/plain",
+ timeout: me.timeout,
+ error: function(jqxhr, textStatus, errorThrown) {
+ me.failures += 1;
+ me._schedule();
+ },
+ success: function(data) {
+ zAu.cmd0.echo(me.desktop);
+ me.failures = 0;
+ me._schedule();
+ }
+ });
+ this._req = jqxhr;
+ },
+ start: function() {
+ this.desktop._serverpush = this;
+ this.active = true;
+ this._send();
+ },
+ stop: function() {
+ this.desktop._serverpush = null;
+ this.active = false;
+ if (this._req) {
+ this._req.abort();
+ this._req = null;
+ }
+ }
+ });
+})();
diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/zk.wpd b/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/zk.wpd
new file mode 100644
index 0000000000..e1579d8edc
--- /dev/null
+++ b/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/zk.wpd
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/org.adempiere.ui.zk/WEB-INF/web.xml b/org.adempiere.ui.zk/WEB-INF/web.xml
index ca7a4fb357..50bef1871a 100644
--- a/org.adempiere.ui.zk/WEB-INF/web.xml
+++ b/org.adempiere.ui.zk/WEB-INF/web.xml
@@ -1,6 +1,27 @@
ADempiere WebUI
+
+
+ AtmosphereServlet
+ org.atmosphere.cpr.AtmosphereServlet
+
+ org.atmosphere.cpr.AtmosphereHandler
+ fi.jawsy.jawwa.zk.atmosphere.ZkAtmosphereHandler
+
+
+ org.atmosphere.cpr.AtmosphereHandler.contextRoot
+ /zkau/comet
+
+ 0
+
+ true
+
+
+ AtmosphereServlet
+ /zkau/comet
+
+
diff --git a/org.adempiere.ui.zk/WEB-INF/zk.xml b/org.adempiere.ui.zk/WEB-INF/zk.xml
index bd21c33785..130a87c055 100644
--- a/org.adempiere.ui.zk/WEB-INF/zk.xml
+++ b/org.adempiere.ui.zk/WEB-INF/zk.xml
@@ -51,7 +51,8 @@
-->
ajax
- org.zkoss.zk.ui.impl.PollingServerPush
+
+ fi.jawsy.jawwa.zk.atmosphere.AtmosphereServerPush
diff --git a/org.adempiere.ui.zk/build.properties b/org.adempiere.ui.zk/build.properties
index 54b4380e78..927cbe06e4 100644
--- a/org.adempiere.ui.zk/build.properties
+++ b/org.adempiere.ui.zk/build.properties
@@ -10,7 +10,12 @@ bin.includes = META-INF/,\
zul/,\
timeout.zul,\
plugin.xml,\
- WEB-INF/classes/
+ WEB-INF/classes/,\
+ WEB-INF/lib/atmosphere-runtime-0.9.jar,\
+ WEB-INF/lib/atmosphere-compat-jbossweb-0.9.jar,\
+ WEB-INF/lib/atmosphere-compat-tomcat-0.9.jar,\
+ WEB-INF/lib/atmosphere-compat-tomcat7-0.9.jar,\
+ WEB-INF/lib/slf4j-api-1.6.1.jar
src.includes = WEB-INF/classes/,\
WEB-INF/tld/,\
WEB-INF/web.xml,\
@@ -25,5 +30,5 @@ src.includes = WEB-INF/classes/,\
zul/
bin.excludes = WEB-INF/src/,\
WEB-INF/web-2.5.xml
-source.. = WEB-INF/src/
+jars.compile.order =