IDEMPIERE-175 Performance: Use atmosphere ( long pooling, NIO ) server push

This commit is contained in:
Heng Sin Low 2012-04-26 23:15:44 +08:00
parent d1f5d4e3e9
commit 409453dca8
9 changed files with 131 additions and 57 deletions

View File

@ -1,3 +1,22 @@
/**
This software is licensed under the Apache 2 license, quoted below.
Copyright 2012 Joonas Javanainen <joonas@jawsy.fi>
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/
package fi.jawsy.jawwa.zk.atmosphere;
import java.util.concurrent.atomic.AtomicReference;
@ -21,12 +40,12 @@ import org.zkoss.zk.ui.util.Clients;
/**
* 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.
* Adapted from https://github.com/Gekkio/jawwa/tree/develop/zk-atmosphere version 0.3.1-SNAPSHOT
*/
public class AtmosphereServerPush implements ServerPush {
private static final String ON_ACTIVATE_DESKTOP = "onActivateDesktop";
public static final int DEFAULT_TIMEOUT = 1000 * 60 * 5;
private final AtomicReference<Desktop> desktop = new AtomicReference<Desktop>();
@ -61,7 +80,7 @@ public class AtmosphereServerPush implements ServerPush {
EventListener<Event> task = new EventListener<Event>() {
@Override
public void onEvent(Event event) throws Exception {
if (event.getName().equals("onNewData"))
if (event.getName().equals(ON_ACTIVATE_DESKTOP))
{
synchronized (_mutex) {
_carryOver = new ExecutionCarryOver(desktop.get());
@ -82,7 +101,7 @@ public class AtmosphereServerPush implements ServerPush {
};
synchronized (info) {
Executions.schedule(desktop.get(), task, new Event("onNewData"));
Executions.schedule(desktop.get(), task, new Event(ON_ACTIVATE_DESKTOP));
if (info.nActive == 0)
info.wait(timeout <= 0 ? 10*60*1000: timeout);
}
@ -130,8 +149,7 @@ public class AtmosphereServerPush implements ServerPush {
@Override
public boolean isActive() {
// throw new UnsupportedOperationException("isActive is not supported by AtmosphereServerPush");
return true;
return _active != null && _active.nActive > 0;
}
@Override

View File

@ -1,3 +1,22 @@
/**
This software is licensed under the Apache 2 license, quoted below.
Copyright 2012 Joonas Javanainen <joonas@jawsy.fi>
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations under
the License.
*/
package fi.jawsy.jawwa.zk.atmosphere;
import java.io.IOException;
@ -18,6 +37,7 @@ import org.zkoss.zk.ui.sys.WebAppCtrl;
/**
* Atmosphere handler that integrates Atmosphere with ZK server push.
* Adapted from https://github.com/Gekkio/jawwa/tree/develop/zk-atmosphere version 0.3.1-SNAPSHOT
*/
public class ZkAtmosphereHandler implements AtmosphereHandler {

View File

@ -41,6 +41,8 @@ import org.adempiere.webui.component.Window;
import org.adempiere.webui.desktop.IDesktop;
import org.adempiere.webui.session.SessionManager;
import org.adempiere.webui.theme.ThemeManager;
import org.adempiere.webui.util.IServerPushCallback;
import org.adempiere.webui.util.ServerPushTemplate;
import org.compiere.acct.Doc;
import org.compiere.model.GridWindowVO;
import org.compiere.model.Lookup;
@ -744,42 +746,48 @@ public final class AEnv
}
/**
* Execute task that required access to the zk desktop
* Execute synchronous task in UI thread.
* @param runnable
*/
public static void executeDesktopTask(Runnable runnable) {
boolean inUIThread = Executions.getCurrent() != null;
boolean desktopActivated = false;
Desktop desktop = null;
try {
if (!inUIThread) {
desktop = (Desktop) Env.getCtx().get(AdempiereWebUI.ZK_DESKTOP_SESSION_KEY);
if (desktop == null)
return;
//1 second timeout
if (Executions.activate(desktop, 1000)) {
desktopActivated = true;
} else {
return;
}
}
public static void executeDesktopTask(final Runnable runnable) {
Desktop desktop = getDesktop();
ServerPushTemplate template = new ServerPushTemplate(desktop);
template.execute(new IServerPushCallback() {
@Override
public void updateUI() {
runnable.run();
} catch (Exception e) {
throw new RuntimeException(e.getLocalizedMessage(), e);
} finally {
if (!inUIThread && desktopActivated) {
Executions.deactivate(desktop);
}
}
});
}
/**
* Execute asynchronous task in UI thread.
* @param runnable
*/
public static void executeAsyncDesktopTask(final Runnable runnable) {
Desktop desktop = getDesktop();
ServerPushTemplate template = new ServerPushTemplate(desktop);
template.executeAsync(new IServerPushCallback() {
@Override
public void updateUI() {
runnable.run();
}
});
}
/**
* Get current desktop
* @return Desktop
*/
public static Desktop getDesktop() {
boolean inUIThread = Executions.getCurrent() != null;
return inUIThread ? Executions.getCurrent().getDesktop()
: (Desktop) Env.getCtx().get(AdempiereWebUI.ZK_DESKTOP_SESSION_KEY);
}
/**
* @return true if running on a tablet
*/
public static boolean isTablet() {
IDesktop appDesktop = SessionManager.getAppDesktop();
return appDesktop != null ? appDesktop.getClientInfo().tablet : false;

View File

@ -219,7 +219,7 @@ public class DPActivities extends DashboardPanel implements EventListener {
noOfWorkflow = getWorkflowCount();
if (isShowUnprocessed) noOfUnprocessed = getUnprocessedCount();
template.execute(this);
template.executeAsync(this);
}
@Override

View File

@ -241,7 +241,7 @@ public class DPRecentItems extends DashboardPanel implements EventListener<Event
@Override
public void refresh(ServerPushTemplate template)
{
template.execute(this);
template.executeAsync(this);
}
@Override

View File

@ -169,7 +169,7 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
}
};
ServerPushTemplate template = new ServerPushTemplate(layout.getDesktop());
template.execute(callback);
template.executeAsync(callback);
}
};
@ -226,7 +226,7 @@ public class DefaultDesktop extends TabbedDesktop implements MenuListener, Seria
noOfRequest = DPActivities.getRequestCount();
noOfWorkflow = DPActivities.getWorkflowCount();
template.execute(this);
template.executeAsync(this);
}
/**

View File

@ -205,7 +205,7 @@ public class NavBar2Desktop extends TabbedDesktop implements MenuListener, Seria
}
};
ServerPushTemplate template = new ServerPushTemplate(layout.getDesktop());
template.execute(callback);
template.executeAsync(callback);
}
};
@ -256,7 +256,7 @@ public class NavBar2Desktop extends TabbedDesktop implements MenuListener, Seria
noOfRequest = DPActivities.getRequestCount();
noOfWorkflow = DPActivities.getWorkflowCount();
template.execute(this);
template.executeAsync(this);
}
/**

View File

@ -207,7 +207,7 @@ public class NavBarDesktop extends TabbedDesktop implements MenuListener, Serial
}
};
ServerPushTemplate template = new ServerPushTemplate(layout.getDesktop());
template.execute(callback);
template.executeAsync(callback);
}
};
@ -271,7 +271,7 @@ public class NavBarDesktop extends TabbedDesktop implements MenuListener, Serial
noOfRequest = DPActivities.getRequestCount();
noOfWorkflow = DPActivities.getWorkflowCount();
template.execute(this);
template.executeAsync(this);
}
/**

View File

@ -39,13 +39,12 @@ public class ServerPushTemplate {
}
/**
* Execute callback in UI thread
* Execute asynchronous task in UI thread. This is implemented
* using Executions.schedule and will return immediately
* @param callback
*/
public void execute(final IServerPushCallback callback) {
boolean inUIThread = Executions.getCurrent() != null;
public void executeAsync(final IServerPushCallback callback) {
try {
if (!inUIThread) {
EventListener<Event> task = new EventListener<Event>() {
@Override
public void onEvent(Event event) throws Exception {
@ -53,9 +52,6 @@ public class ServerPushTemplate {
}
};
Executions.schedule(desktop, task, new Event("onExecute"));
} else {
callback.updateUI();
}
} catch (DesktopUnavailableException de) {
throw de;
} catch (Exception e) {
@ -63,6 +59,38 @@ public class ServerPushTemplate {
}
}
/**
* Execute synchronous task in UI thread. This is implemented
* using Executions.activate/deactivate and will only return after the
* invoked task have ended. For better scalability, if possible, you
* should use executeAsync instead.
* @param callback
*/
public void execute(IServerPushCallback callback) {
boolean inUIThread = Executions.getCurrent() != null;
boolean desktopActivated = false;
try {
if (!inUIThread) {
//10 minutes timeout
if (Executions.activate(desktop, 10 * 60 * 1000)) {
desktopActivated = true;
} else {
throw new DesktopUnavailableException("Timeout activating desktop.");
}
}
callback.updateUI();
} catch (DesktopUnavailableException de) {
throw de;
} catch (Exception e) {
throw new AdempiereException("Failed to update client in server push worker thread.", e);
} finally {
if (!inUIThread && desktopActivated) {
Executions.deactivate(desktop);
}
}
}
/**
*
* @return desktop