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
1. The Utility Class: MESBroadcastUtil
First, create a helper to handle the raw OSGi event posting. This ensures your topic names and property keys are centralized.
Java
package tw.idempiere.mes.util;
import java.util.HashMap;
import java.util.Map;
import org.adempiere.base.event.EventManager;
import org.osgi.service.event.Event;
public class MESBroadcastUtil {
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";
/**
* Publish an event to the OSGi system bus.
* This can be called from any bundle.
*/
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().postEvent(event);
}
}
2. The Publisher: Firing the Event
Wherever your business logic resides (e.g., a Callout, ModelValidator, or a Process), fire the event after the transaction is committed.
Java
if (saveSuccessful) {
// Notify the system instantly!
MESBroadcastUtil.publishMESUpdate(mProduction.get_ID(), scannedQty);
}
3. The Subscriber: The ZK Dashboard
This is where the magic happens. Since the OSGi event runs in a system thread, you cannot touch ZK components directly. You must use Executions.schedule.
Note: Ensure
org.osgi.service.eventis added to yourImport-PackageinMANIFEST.MF.
Java
import org.adempiere.base.event.EventManager;
import org.osgi.service.event.EventHandler;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;
public class WKpiDashboard extends AdForm {
private EventHandler subscriber;
@Override
protected void initForm() {
// 1. MUST enable Server Push for this specific Desktop
this.getDesktop().enableServerPush(true);
subscribeToEvents();
}
private void subscribeToEvents() {
final Desktop myDesktop = this.getDesktop();
subscriber = new EventHandler() {
@Override
public void handleEvent(final org.osgi.service.event.Event event) {
if (MESBroadcastUtil.TOPIC_MES_UPDATE.equals(event.getTopic())) {
final Integer orderId = (Integer) event.getProperty(MESBroadcastUtil.PROPERTY_ORDER_ID);
// Check if the browser tab is still open
if (myDesktop != null && myDesktop.isAlive()) {
// Bridge the OSGi thread to the ZK Desktop thread
Executions.schedule(myDesktop, (zEvent) -> {
// UI-safe zone
refreshDashboard(orderId);
Clients.showNotification("Live Update: New Scan Received!");
}, new org.zkoss.zk.ui.event.Event("onUpdate"));
}
}
}
};
// Register the listener via iDempiere EventManager
EventManager.getInstance().register(MESBroadcastUtil.TOPIC_MES_UPDATE, subscriber);
// IMPORTANT: Prevent Memory Leaks!
this.addEventListener("onDetach", (event) -> {
EventManager.getInstance().unregister(subscriber);
});
}
}
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!
