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

  1. Publisher (The Scanner): Fires an OSGi Event (idempiere/mes/update) containing metadata (Order ID, Qty).
  2. Subscriber (The Dashboard): A ZK UI component registers an OSGi EventHandler.
  3. 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.event is added to your Import-Package in MANIFEST.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.zk bundle. 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!

By Ray Lee (System Analyst)

iDempeire ERP Contributor, 經濟部中小企業處財務管理顧問 李寶瑞

Leave a Reply

Your email address will not be published. Required fields are marked *