Client Notifications

In a scout application, typically, the scout client requests some data from the scout server. Sometimes, however, the communication needs to go the other way: The scout server needs to inform the scout client about something. With client notifications it is possible to do so.

clientNotifications
Figure 1. Client Notifications

Examples

Example scenarios for client notifications are:

  • some data shared by client and server has changed (e.g. a cache on the client is no longer up-to-date, or a shared variable has changed)

  • a new incoming phone call is available for a specific client and should be shown in the GUI

  • a user wants to send a message to another user

Scout itself uses client notifications to synchronize code type and permission caches and session shared variables.

Data Flow

A client notification message is just a serializable object. It is published on the server and can be addressed either to all client nodes or only to a specific session or user. On the UI server side, handlers can be used to react upon incoming notifications.

Client notification handlers may change the state of the client model. In case of visible changes in the UI, these changes are automatically reflected in the UI.

In case of multiple server nodes, the client notifications are synchronized using cluster notifications to ensure that all UI servers receive the notifications.

Push Technology

cn long polling
Figure 2. Long Polling

Client notifications are implemented using long polling as described below, because long polling works reliably in most corporate networks with proxy servers between server and client as well as with security policies that do not allow server push.

With long polling, the client requests notifications from the server repeatedly. If no new notifications are available on the server, instead of sending an empty response, the server holds the request open and waits until new notifications are available or a timeout is reached.

In addition to the long polling mechanism, pending client notifications are also transferred to the client along with the response of regular client requests.

Components

A client notification can be published on the server using the ClientNotificationRegistry. Publishing can be done either in a non-transactional or transactional way (only processed, when the transaction is committed).

The UI Server either receives the notifications via the ClientNotificationPoller or in case of transactional notifications together with the response of a regular service request. The notification is then dispatched to the corresponding handler.

When a client notifications is published on the server, it is automatically synchronized with the other server nodes (by default).

cn big picture
Figure 3. Client Notification Big Picture

Multiple Server Nodes

cn multiple servers
Figure 4. Client Notification Multiple Server Nodes

In order to deal with multiple ui-server nodes, the server holds a single notifications queue per ui-server node were all client notifications are added (filtering is not possible on the server anymore as the backend may not know about all users or sessions of a specific ui-server). The ui-server decides how to deal further with these notifications (maybe even ignores them if they are not addressed to one of its users or sessions).

If an ui-server changes its backend server node and connects to another one, a new notification queue is created on this backend server (unless the ui-server is already known there) and the old notification queue on the old backend server is removed after it has expired after a specific timeout (see org.eclipse.scout.rt.server.clientnotification.ClientNotificationProperties.NotificationQueueExpireTime).

  • Notifications which have been added to the old queue after the ui-server consumed them lastly but before the ui-server connects to the new server will not be transferred to the ui-server.

  • If an ui-server would randomly connect to different backend servers multiple notification queues for the specific ui-server may exist (unless a timeout is reached) and one notification may be delivered multiple times to the same ui-server, this should be avoided.

Publishing

Listing 1. Publishing Client Notifications
BEANS.get(ClientNotificationRegistry.class).putForUser("admin", new PersonTableChangedNotification());

There are several options to choose from when publishing a new client notification:

ClientNotificationAddress

The ClientNotificationAddress determines which how the client notification needs to be dispatched and handled. A client notification can be addressed to

  • all nodes

  • all sessions

  • one or more specific session

  • one or more specific user

Transactional vs. Non-transactional

Client notifications can be published in a transactional or non-transactional way.

  • Transactional means that the client notifications are only published once the transaction is committed. If the transaction fails, client notifications are disregarded.

  • Non-transactional means that client notifications are published immediately without considering any transactions.

Distributing to all Cluster Nodes

Generally, it makes sense to distribute the client notifications automatically to all other server cluster nodes (if available). This is achieved using ClusterNotifications. It is however also possible to publish client notifications without cluster distribution. E.g. in case of client notifications already received from other cluster nodes.

Coalescing Notifications

It is possible that a service generates a lot of client notifications that are obsolete once a newer notification is created. In this case a coalescer can be created to reduce the notifications:

Listing 2. Client Notification Coalescer
public class BookmarkNotificationCoalescer implements ICoalescer<BookmarkChangedClientNotification> {

  @Override
  public List<BookmarkChangedClientNotification> coalesce(List<BookmarkChangedClientNotification> notifications) {
    // reduce to one
    return CollectionUtility.arrayList(CollectionUtility.firstElement(notifications));
  }
}

Handling

The ClientNotificationDispatcher is responsible for dispatching the client notifications to the correct handler.

Creating a Client Notification Handler

To create a new client notification handler for a specific client notification, all you need to do is creating a class implementing org.eclipse.scout.rt.shared.notification.INotificationHandler<T>, where T is the type (or subtype) of the notification to handle.

The new handler does not need to be registered anywhere. It is available via jandex class inventory.

Listing 3. Notification Handler for MessageNotifications
public class MessageNotificationHandler implements INotificationHandler<MessageNotification> {

  @Override
  public void handleNotification(final MessageNotification notification) {

Handling Notifications Temporarily

Sometimes it is necessary to start and stop handling notification dynamically, (e.g. when a form is opened) in this case AbstractObservableNotificationHandler can be used to add and remove listeners.

Asynchronous Dispatching

Dispatching is always done asynchronously. However, in case of transactional notifications, a service call blocks until all transactional notifications returned with the service response are handled.

This behavior was implemented to simplify for example the usage of shared caches:

Listing 4. Blocking until notification handling completed
CodeService cs = BEANS.get(CodeService.class);
cs.reloadCodeType(UiThemeCodeType.class);

//client-side reload triggered by client notifications is finished
List<? extends ICode<String>> reloadedCodes = cs.getCodeType(UiThemeCodeType.class).getCodes();

In the example above, it is guaranteed, that the codetype is up-to-date as soon as reloadCodeType is finished.

Updating Scout Model

Notification handlers are never called from a scout model thread. If the scout model needs to be updated when handling notifications, a model job needs to be created for that task.

Listing 5. Notification Handler Creating Model Job
@Override
public void handleNotification(final MessageNotification notification) {
  ModelJobs.schedule(() -> {
    IDesktop desktop = ClientSessionProvider.currentSession().getDesktop();
    // e.g. send dataChanged event to UI listeners
    desktop.dataChanged(notification.getMessage());
  }, ModelJobs.newInput(ClientRunContexts.copyCurrent()));
}
Make sure to always run updates to the scout models in a model job (forms, pages, …​): Use ModelJobs.schedule(…​) where necessary in notification handlers.