In modern Manufacturing Execution Systems (MES) or high-frequency logistics environments, the default “polling” mechanism often feels sluggish. Users today expect instant feedback: just like a chat app, when a barcode is scanned in the warehouse, the dashboard in the office should blink immediately.
In this post, we will dive into how to solve “Server Push Delay” in iDempiere by moving from standard ZK EventQueues to a robust OSGi EventAdmin approach, achieving true sub-second updates across browser sessions.
The Problem: “It’s too slow!”
We recently built a Resource KPI Board for a production floor. It displays real-time stats (Target vs. Delivered) for each workstation.
- Operator: Scans a product barcode on an industrial tablet.
- Manager: Watches the KPI Dashboard on a 65-inch TV screen.
Initially, we used the standard ZK EventQueues.APPLICATION. However, users reported lags of 10-30 seconds or, worse, missed updates.
Why does this happen? ZK’s default mechanism sometimes falls back to “piggyback” polling if the client-side configuration isn’t perfect. Furthermore, EventQueues managed strictly within the web container can struggle when business logic fires from a background process or a different OSGi bundle context.
The Solution: OSGi EventAdmin + Server Push
To fix this, we looked at how iDempiere handles its own “Broadcast Messages.” It utilizes OSGi EventAdmin, the native event bus of the OSGi framework. It sits at a lower, system-wide level, making it the perfect bridge between business logic and the UI.
By coupling OSGi Events with ZK’s enableServerPush, we created a lightning-fast, decoupled update cycle.
The Architecture
- Publisher (The Scanner): Fires an OSGi Event (
idempiere/mes/update) containing metadata (Order ID, Qty). - Subscriber (The Dashboard): A ZK UI component registers an OSGi
EventHandler. - The Bridge: When the OSGi event arrives, the handler uses
Executions.schedule()to safely inject the task into the ZK Desktop thread.
Implementation Guide
Core Components
1. MESBroadcastUtil – Event Broadcasting Utility
Java
package tw.ninniku.mrp.util;
import java.util.HashMap;
import java.util.Map;
import org.adempiere.base.event.EventManager;
import org.osgi.service.event.Event;
public class MESBroadcastUtil {
// Define Event Topic
public static final String TOPIC_MES_UPDATE = "idempiere/mes/update";
public static final String PROPERTY_ORDER_ID = "M_Production_ID";
public static final String PROPERTY_QTY = "Qty";
/**
* Send event to the OSGi System Bus
* sendEvent = Synchronous delivery (Recommended for real-time updates)
* postEvent = Asynchronous delivery
*/
public static void publishMESUpdate(int orderId, int qty) {
Map<String, Object> properties = new HashMap<>();
properties.put(PROPERTY_ORDER_ID, orderId);
properties.put(PROPERTY_QTY, qty);
Event event = new Event(TOPIC_MES_UPDATE, properties);
EventManager.getInstance().sendEvent(event);
}
}
2. Model afterSave – Triggering the Event
Trigger the event within the afterSave() method of your Model class:
Java
@Override
protected boolean afterSave(boolean newRecord, boolean success) {
if (success) {
// Send real-time update event
MESBroadcastUtil.publishMESUpdate(get_ID(), 0);
}
return super.afterSave(newRecord, success);
}
3. AbstractKanbanForm – Base Class
Java
package tw.ninniku.mrp.webui.form;
import org.adempiere.base.event.EventManager;
import org.adempiere.webui.panel.ADForm;
import org.adempiere.webui.panel.IFormController;
import org.osgi.service.event.EventHandler;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zul.Hlayout;
import tw.ninniku.mrp.util.MESBroadcastUtil;
public abstract class AbstractKanbanForm extends ADForm
implements IFormController, EventListener<Event> {
protected Hlayout mainLayout = new Hlayout();
private EventHandler subscriber;
/** Subclass must implement: Returns Kanban name for Debugging */
protected abstract String getKanbanName();
/** Subclass must implement: Refreshes Kanban content */
protected abstract void refreshKanban();
/**
* Crucial! Enable Server Push when the page is attached.
*/
@Override
public void onPageAttached(org.zkoss.zk.ui.Page newpage,
org.zkoss.zk.ui.Page oldpage) {
super.onPageAttached(newpage, oldpage);
if (newpage != null) {
newpage.getDesktop().enableServerPush(true);
}
}
@Override
protected void initForm() {
mainLayout.setHflex("1");
mainLayout.setVflex("1");
this.appendChild(mainLayout);
// Render UI immediately
refreshKanban();
// Subscribe to real-time update events
subscribeToEvents();
}
/**
* Subscribe to OSGi EventAdmin events
*/
private void subscribeToEvents() {
try {
// Get current Desktop and enable Server Push
final Desktop myDesktop = Executions.getCurrent().getDesktop();
myDesktop.enableServerPush(true);
subscriber = new EventHandler() {
@Override
public void handleEvent(final org.osgi.service.event.Event event) {
// Check event topic
if (MESBroadcastUtil.TOPIC_MES_UPDATE.equals(event.getTopic())) {
// Check if Desktop is still alive
if (myDesktop.isAlive()) {
try {
// Use Executions.schedule to bridge to the ZK UI thread
Executions.schedule(myDesktop,
new EventListener<Event>() {
public void onEvent(Event evt) {
// UI Safe Zone - UI can be updated here
refreshKanban();
}
}, new Event("updateKanban"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
};
// Register event listener
EventManager.getInstance().register(
MESBroadcastUtil.TOPIC_MES_UPDATE, subscriber);
// Important! Prevent memory leaks - Unregister when component detaches
this.addEventListener("onDetach", (event) -> {
EventManager.getInstance().unregister(subscriber);
});
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public ADForm getForm() {
return this;
}
}
Implementation Steps
Step 1: Create the Event Broadcasting Utility Class
Create MESBroadcastUtil.java to define event topics and sending methods.
Step 2: Trigger the Event in the Model
In the Model class that requires real-time updates, override the afterSave() method and call MESBroadcastUtil.publishMESUpdate().
Step 3: Create the Base Form Class
Create AbstractKanbanForm.java including:
onPageAttached(): To enable Server Push.subscribeToEvents(): To subscribe to events.onDetachlistener: To unregister and prevent memory leaks.
Step 4: Implement the Specific Form
Extend AbstractKanbanForm and implement:
getKanbanName(): Return an identification name.refreshKanban(): Logic to redraw the UI.
Java
public class WLabKanban extends AbstractKanbanForm {
@Override
protected String getKanbanName() {
return "LabKanban";
}
@Override
protected void refreshKanban() {
mainLayout.getChildren().clear();
// Rebuild UI components...
}
}
Important Considerations
1. Server Push Must be Enabled
Ensure you call desktop.enableServerPush(true); otherwise, the server cannot push updates to the client.
2. UI Updates Must Occur in the ZK Thread
OSGi event handlers run on different threads and cannot update the UI directly. You must use Executions.schedule() to bridge the execution:
Java
Executions.schedule(desktop,
new EventListener<Event>() {
public void onEvent(Event evt) {
// Update UI here
}
}, new Event("eventName"));
3. Prevent Memory Leaks
Always unregister the event listener when the component is detached or removed.
4. Check Desktop Vitality
Before processing an event, verify if the Desktop is still alive using myDesktop.isAlive().
Required Import-Package in MANIFEST.MF
Ensure your OSGi bundle includes the following packages:
MANIFEST.MF
Import-Package:
org.adempiere.base.event,
org.adempiere.webui.component,
org.adempiere.webui.panel,
org.osgi.service.event;version="[1.4,2)",
org.zkoss.zk.ui,
org.zkoss.zk.ui.event,
org.zkoss.zul,
...
Would you like me to help you refine the code comments further or generate a WordPress-friendly featured image for this tutorial?
Why is this better?
- Decoupling: Your business logic bundle doesn’t need to depend on the
org.adempiere.ui.zkbundle. It just sends a message to the OSGi bus. - Performance: OSGi EventAdmin is highly optimized for high-frequency internal messaging.
- Reliability: Unlike ZK EventQueues which can be scoped to a session, OSGi events are system-wide.
- Cluster Ready: If you implement a custom
IMessageService, this pattern can be extended to broadcast across multiple physical servers.
Conclusion
Real-time feedback creates a “premium” feel for end-users and increases operational efficiency. By leveraging the OSGi infrastructure already built into iDempiere, you can build incredibly responsive dashboards without the overhead of external WebSocket servers.
Have you used OSGi events for UI updates? Let me know your thoughts or performance tips in the comments below!
