Project Event Propagation - protegeproject/webprotege-next-gen GitHub Wiki
WebProtégé Project Event Propagation
This document explains how project-level events are propagated through the WebProtégé system — from backend dispatch through RabbitMQ and the API gateway, to the WebProtégé client running in the browser.
It is intended to help developers understand the current event-handling pipeline, which replaces the older polling-based approach with a WebSocket-based push mechanism for delivering live project events to connected clients.
1. Overview
WebProtégé supports real-time updates to clients by propagating project events whenever changes occur on the server.
These events are serialized, transmitted through RabbitMQ and the gateway, and ultimately delivered to clients via WebSocket connections, where they trigger UI updates.
Only events that implement the ProjectEvent interface are propagated to clients.
2. Event Packaging
Project events are grouped into a PackagedProjectChangeEvent.
This class encapsulates the project identifier, event identifier, and a list of project events:
PackagedProjectChangeEvent(ProjectId projectId, EventId eventId, List<ProjectEvent> projectEvents)
Bundling related events into a single package allows efficient transmission and coordinated updates on the client side.
3. Dispatching Through RabbitMQ
Instances of PackagedProjectChangeEvent are published to RabbitMQ using an EventDispatcher.
The dispatcher serializes the packaged event and posts it as a message to the message broker.
RabbitMQ acts as the intermediary for event delivery between the WebProtégé backend and the webprotege-gwt-api-gateway service.
4. Event Handling in the Gateway
On the receiving side, the webprotege-gwt-api-gateway listens for event messages from RabbitMQ.
The ProjectChangedEmitterHandler is responsible for processing these messages and forwarding them to connected clients.
Its main responsibilities are:
-
Serialize the events to JSON The list of
projectEventsfrom thePackagedProjectChangeEventis serialized into JSON using an intermediate container class (for example,ProjectEventsQueryResponse). This container wraps the events in aneventsfield, ensuring the JSON structure matches what the client expects. -
Send the serialized JSON to subscribed clients The serialized data is sent through a WebSocket using
SimpleMessagingTemplate. Clients subscribe to the topic:/topic/project-events/{projectId}Messages are delivered using the STOMP protocol — a simple, text-based protocol for WebSocket messaging.
In short, the gateway bridges RabbitMQ event messages to WebSocket messages, allowing connected WebProtégé clients to receive real-time updates.
5. Event Reception and Deserialization in the Client
When a WebProtégé client receives a WebSocket message, it is handled by:
dispatchEventsFromWebsocket(String data)
This method receives the raw JSON payload containing serialized project events.
The client then sends this JSON to the backend for deserialization using Jackson.
On the server, Jackson converts the JSON into Java event objects (ProjectEvent instances and related types).
Once deserialized, these typed event objects are returned to the client using GWT-RPC. On the client side, GWT-RPC performs its usual object deserialization, reconstructing the Java event objects for use within the browser runtime.
Dispatch and Execution Path
To initiate this round trip, the client creates and executes a TranslateEventListAction through the dispatch service.
This action is handled directly by DispatchServiceExecutorImpl using an instanceof check, rather than the standard handler registration mechanism.
After deserialization, the resulting events are wrapped in a GetProjectEventsResult.
For legacy reasons, the serialized type name appears as:
"webprotege.hierarchies.GetProjectEvents"
even though the events are not specific to hierarchies.
6. Client-Side Event Dispatch
After the GetProjectEventsResult is received, the client passes the event list to the EventPollingManager (a legacy component still used for dispatching).
This manager invokes:
public void dispatchEvents(EventList<?> eventList)
to propagate the events to all registered client-side handlers, ensuring that relevant parts of the UI update in response to project changes.
7. Gateway Serialization Details
The following code from ProjectChangedEmitterHandler shows how events are serialized and transmitted to clients.
ProjectEventsQueryResponse is used primarily to ensure the correct JSON structure, while EventTag values are included for backward compatibility but are no longer meaningful.
@Override
public void handleEvent(PackagedProjectChangeEvent event) {
try {
ProjectEventsQueryResponse response = new ProjectEventsQueryResponse();
response.events = new EventList(EventTag.getFirst(), event.projectEvents(), EventTag.get(1));
simpMessagingTemplate.send(
"/topic/project-events/" + event.projectId().id(),
new GenericMessage<>(objectMapper.writeValueAsBytes(response))
);
} catch (Exception e) {
LOGGER.error("Error forwarding the events through websocket", e);
}
}
8. Example Serialized Event Message
Below is an example of the JSON structure sent from the gateway to clients.
Each event includes an @type field to support Jackson deserialization.
{
"@type": "webprotege.hierarchies.GetProjectEvents",
"events": {
"startTag": 0,
"events": [
{
"@type": "webprotege.events.tags.EntityTagsChanged",
"eventId": "becb5212-2942-4d52-b991-829f2f563a04",
"projectId": "9dd84be1-1701-4f12-9fe5-4bd5dc124d2a",
"entity": {
"@type": "Class",
"iri": "http://www.example.org/R8nkIWfNl3cjmTpwi1OTGvv"
},
"tags": []
}
],
"endTag": 1
}
}
9. Summary
WebProtégé’s event propagation pipeline connects several components in sequence:
- Backend —
ProjectEventinstances are packaged in aPackagedProjectChangeEventand dispatched to RabbitMQ. - Gateway — The events are intercepted by the API gateway, serialized to JSON, and broadcast over WebSockets using STOMP to all subscribed clients.
- Client — The raw JSON is deserialized to Java event objects using Jackson on the server, then sent back via GWT-RPC and dispatched to client-side handlers.
This design enables real-time event delivery across multiple clients editing the same project, replacing the legacy polling-based system with a push-based mechanism for improved responsiveness.
Some legacy elements (such as EventPollingManager, event tags, and the old JSON type names) remain for backward compatibility and could be modernized in future refactoring.
10. Remarks and Future Improvements
While the current system achieves reliable real-time event delivery, several areas could be improved:
-
Multiple Round Trips to the Server Because the raw JSON events must be deserialized into Java objects on the backend (using Jackson) and then transmitted again to the client via GWT-RPC, the process involves multiple round trips between the client and server. A more efficient approach would deserialize the events directly in the client (for example, by providing compatible client-side event type mappings), eliminating the intermediate RPC step.
-
Channel Naming The current channel and type names — for example,
webprotege.hierarchies.GetProjectEvents— reference hierarchies, even though these events are not specific to hierarchical data. Channel and type naming should be updated to reflect the general-purpose nature of project events (e.g.,webprotege.projects.GetProjectEventsor similar). -
WebSocket Lifecycle Management The WebSocket connection currently persists even after the project presenter stops. This can result in unnecessary open connections and potential event leakage. The WebSocket should be explicitly closed and cleaned up when the project presenter stops to ensure efficient resource management.
-
Consistent Naming of Serialization and Deserialization Components The various classes responsible for event serialization, deserialization, and transport currently use inconsistent naming conventions (e.g.,
ProjectEventsQueryResponse,TranslateEventListAction,GetProjectEventsResult). These should be harmonized to make their roles clearer and the flow easier to follow. -
Dedicated Handler and Result for Translation The
TranslateEventListActionis currently handled inline via aninstanceofcheck rather than through a proper handler. Introducing a dedicatedTranslateEventListHandlerand a correspondingTranslateEventListResultwould make the design more consistent, improve readability, and reduce coupling with unrelated result types likeGetProjectEventsResult.