Hi Martin,
Sure, there is a solution to notify screens from business logic and persistence layers. It’s based on Spring’s application events and works within a single server instance.
Firstly, let’s consider a simple solution with the UiEventPublisher
bean provided by the framework. It delivers events to screens opened by the current user in a web browser tab. In other words, if a user clicks some button in a browser tab, and this button click eventually invokes the code that sends an event, the event will be delivered to screens opened in that tab.
Create an event class:
package com.company.app.entity;
import org.springframework.context.ApplicationEvent;
public class FooChangedEvent extends ApplicationEvent {
public FooChangedEvent(Object source) {
super(source);
}
}
Create an event listener in the screen which should be notified. In the example below it’s the main screen:
public class MainScreen extends Screen implements Window.HasWorkArea {
// ...
@Autowired
private Notifications notifications;
@EventListener
private void fooChanged(FooChangedEvent event) {
notifications.create().withCaption("A Foo instance has been changed").show();
}
Send the event using the UiEventPublisher
:
@Component
public class FooEventListener {
@Autowired
private UiEventPublisher uiEventPublisher;
@TransactionalEventListener
public void onFooChangedAfterCommit(EntityChangedEvent<Foo> event) {
uiEventPublisher.publishEvent(new FooChangedEvent(this));
}
}
If you want to deliver an event to all currently connected users and to all their browser tabs, you need to create a more complex publisher in your project. Below is a working example - it’s a bean that registers all Vaadin sessions created on the server and iterates through them when sending events:
package com.company.app;
import com.vaadin.server.VaadinSession;
import io.jmix.ui.App;
import io.jmix.ui.AppUI;
import io.jmix.ui.event.AppInitializedEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Component
public class GlobalUiEventPublisher {
private static final Logger log = LoggerFactory.getLogger(GlobalUiEventPublisher.class);
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final List<WeakReference<VaadinSession>> sessions = new ArrayList<>();
@EventListener
public void onAppStart(AppInitializedEvent event) {
lock.writeLock().lock();
try {
sessions.add(new WeakReference<>(VaadinSession.getCurrent()));
} finally {
lock.writeLock().unlock();
}
}
public void publishEvent(ApplicationEvent event) {
ArrayList<VaadinSession> activeSessions = new ArrayList<>();
int removed = 0;
lock.readLock().lock();
try {
for (Iterator<WeakReference<VaadinSession>> iterator = sessions.iterator(); iterator.hasNext(); ) {
WeakReference<VaadinSession> reference = iterator.next();
VaadinSession session = reference.get();
if (session != null) {
activeSessions.add(session);
} else {
lock.readLock().unlock();
lock.writeLock().lock();
try {
iterator.remove();
lock.readLock().lock();
} finally {
lock.writeLock().unlock();
}
removed++;
}
}
} finally {
lock.readLock().unlock();
}
if (removed > 0) {
log.debug("Removed {} Vaadin sessions", removed);
}
log.debug("Sending {} to {} Vaadin sessions", event, activeSessions.size());
for (VaadinSession session : activeSessions) {
// obtain lock on session state
session.access(() -> {
if (session.getState() == VaadinSession.State.OPEN) {
// active app in this session
App app = App.getInstance();
// notify all opened web browser tabs
List<AppUI> appUIs = app.getAppUIs();
for (AppUI ui : appUIs) {
if (!ui.isClosing()) {
// work in context of UI
ui.accessSynchronously(() -> {
ui.getUiEventsMulticaster().multicastEvent(event);
});
}
}
}
});
}
}
}
Use this publisher instead of the built-in one:
@Component
public class FooEventListener {
@Autowired
private GlobalUiEventPublisher globalUiEventPublisher;
@TransactionalEventListener
public void onFooChangedAfterCommit(EntityChangedEvent<Foo> event) {
globalUiEventPublisher.publishEvent(new FooChangedEvent(this));
}
}
Regards,
Konstantin