Class UiEventPublisher
SseEmitters for live UI updates. It is one
listener of the framework's EntityChangedEvent — the single funnel both write paths (the
generic controllers and repository.save) publish to (issues #28, #29) — so the live stream
reflects programmatic saves too, not just back-office edits.
Per-subscriber RBAC (#190). Each stream is keyed by the read-authorities its
viewer held at subscribe time, and an event is delivered only when those roles grant read
access to the event's entity — so a viewer never receives change- or presence-notifications for
entities their role can't read. Roles are captured up front because the fan-out runs off the request
thread (the event-publishing thread, or the ClusterUiBridge relay for peer-node events),
where SecurityContextHolder no longer holds the subscriber's authentication; see
UiAccessService.canReceiveEvent(java.util.Set, String, String).
-
Constructor Summary
ConstructorsConstructorDescriptionUiEventPublisher(UiAccessService access) UiEventPublisher(UiAccessService access, boolean devMode) -
Method Summary
Modifier and TypeMethodDescriptionbooleanWhether this publisher was wired for a live-development server.voidFans anEntityChangedEventout to every open SSE stream whose viewer may read it.voidRoute a committed process-task inbox invalidation only to affected users, roles, and admins.voidvoidvoidpublishNotification(String recipientId, Map<String, Object> payload) Pushes onenotificationevent to every open stream owned byrecipientId— the per-user counterpart of the role-filteredpublish(java.lang.String, java.lang.String, java.lang.String, java.lang.Object)fan-out.voidFans the current viewer set of one record out as apresenceevent, to every open stream whose viewer may read that record — record-level collaboration markers.voidPush a payload-free task inbox invalidation.voidDev-mode live reload on demand: tells every connected browser to full-reload right now, without waiting for a context restart.org.springframework.web.servlet.mvc.method.annotation.SseEmitterOpen a stream for a viewer holdingroles(capture them withUiAccessService.roles(java.security.Principal)on the request thread).org.springframework.web.servlet.mvc.method.annotation.SseEmitterOpen a stream for a viewer holdingrolesand identified for notification routing byuserId(their identity record id, or username for an unlinked login; may benullfor a guest, whose stream then receives no per-user notifications).org.springframework.web.servlet.mvc.method.annotation.SseEmitterOpen a stream with separate identity-record and authentication usernames.
-
Constructor Details
-
UiEventPublisher
-
UiEventPublisher
-
-
Method Details
-
subscribe
public org.springframework.web.servlet.mvc.method.annotation.SseEmitter subscribe(Set<String> roles) Open a stream for a viewer holdingroles(capture them withUiAccessService.roles(java.security.Principal)on the request thread). The role set is snapshotted: every event is filtered against it for the life of the connection. -
subscribe
public org.springframework.web.servlet.mvc.method.annotation.SseEmitter subscribe(Set<String> roles, String userId) Open a stream for a viewer holdingrolesand identified for notification routing byuserId(their identity record id, or username for an unlinked login; may benullfor a guest, whose stream then receives no per-user notifications). Roles are snapshotted and filter every broadcast event;userIdroutesnotificationevents addressed to it. -
subscribe
public org.springframework.web.servlet.mvc.method.annotation.SseEmitter subscribe(Set<String> roles, String userId, String username) Open a stream with separate identity-record and authentication usernames. Notifications route byuserId; process task assignments route by the principalusername. -
isDevMode
public boolean isDevMode()Whether this publisher was wired for a live-development server. -
publishReload
public void publishReload()Dev-mode live reload on demand: tells every connected browser to full-reload right now, without waiting for a context restart. Complements the automaticbootIdreload — use it when something changed that no restart announces (static assets, a rebuilt widget bundle) or to refresh immediately after a batch of edits. No-op outside dev mode, so nothing can yank a production page out from under a user. -
onEntityChanged
Fans anEntityChangedEventout to every open SSE stream whose viewer may read it. Registered as a Spring@EventListener, so anything that publishes the event (both write paths) reaches the browser — no direct coupling to the controllers. -
onProcessTasksChanged
Route a committed process-task inbox invalidation only to affected users, roles, and admins. -
publishProcessTasksChanged
public void publishProcessTasksChanged(String instanceId, Set<String> audienceUsers, Set<String> audienceRoles) Push a payload-free task inbox invalidation. Candidate assignments remain server-side; an eligible browser learns only that its own authenticated/api/tasksview is stale. -
publish
-
publish
-
publishPresence
public void publishPresence(String kind, String entityName, String id, List<Map<String, String>> viewers) Fans the current viewer set of one record out as apresenceevent, to every open stream whose viewer may read that record — record-level collaboration markers. Each viewer is a{userId, displayName}map. Sent only when a record's viewer set changes (a join or a leave), never on a bare heartbeat.Its
entityTypeis the distinct sentinel"presence", not the record'scatalog/documentkind — exactly as comment events use"comment"— so the list/detail/dashboard surfaces, which refetch on a row change to their entity, never mistake a presence ping for one. The record's routekind(catalogs/documents) andentityNameride alongside so the ambient-presence store can map a viewed record to its nav item and list rows; the marker surfaces match onid(globally unique). The read check uses thatkind/entityName(thepresencesentinel is not a real entity type), so a viewer is never told that someone is viewing a record in an entity they can't open (#190). -
publishNotification
Pushes onenotificationevent to every open stream owned byrecipientId— the per-user counterpart of the role-filteredpublish(java.lang.String, java.lang.String, java.lang.String, java.lang.Object)fan-out. A notification is addressed to exactly one person (it was raised for them and already persisted), so delivery routes on the subscriber'suserIdrather than an entity read check: the recipient may have several streams open (tabs/devices) and all of them light up; nobody else's does.Its
entityTypeis the distinct sentinel"notification"— likepresenceandcomment— so the list/detail/dashboard surfaces never mistake it for a row change to one of their entities. Thepayloadis the wire shape the client's notification store consumes (id,type,title,link, …); when its display fields are absent (a peer-node event trimmed to fit the cluster payload cap) the client refetches the feed.
-