Spring-MVC, Cometd : Broadcasting without @Listener

325 Views Asked by At

I am working on a Spring-MVC application and thanks to users on SO, we already have a working Cometd chat functionality. Another functionality we have in the application is notifications, but we would like to integrate Real-time notifications as soon as they happen, kinda like what Facebook has.

Basically the idea is, whenever a new notification is created, it will be saved in the database, and its information from the backend has to be passed to the notifications for logged in users on unique channel for each user.

I would like to know if this approach will work, as it will take me some doing to route notifications to the chat class. Please note, I don't have an interface for the ChatServiceImpl class too. Is that okay? Enough talking, here's code :

ChatServiceImpl :

@Named
@Singleton
@Service
public class ChatServiceImpl {
    @Inject
    private BayeuxServer bayeux;

    @Session
    private ServerSession serverSession;


    public void sendNotification(Notification notification,int id
// And then I send notification here like below, by extracting information from the notification object.

 ServerChannel serverChannel = bayeux.createChannelIfAbsent("/person/notification/" + id).getReference();
        serverChannel.setPersistent(true);
        serverChannel.publish(serverSession, output);
        }
    }

The above class has no interface, so I was planning to use the method as follows :

@Service
@Transactional
public class GroupCanvasServiceImpl implements GroupCanvasService{
    private ChatServiceImpl chatService;

   public void someMethod(){
   chatService.sendNotification(notification, id);
}
}

BayeuxInitializer :

@Component
public class BayeuxInitializer implements DestructionAwareBeanPostProcessor, ServletContextAware
{
    private BayeuxServer bayeuxServer;
    private ServerAnnotationProcessor processor;

    @Inject
    private void setBayeuxServer(BayeuxServer bayeuxServer)
    {
        this.bayeuxServer = bayeuxServer;
    }

    @PostConstruct
    private void init()
    {

        this.processor = new ServerAnnotationProcessor(bayeuxServer);
    }

    @PreDestroy
    private void destroy()
    {
        System.out.println("Bayeux in PreDestroy");
    }

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException
    {
        processor.processDependencies(bean);
        processor.processConfigurations(bean);
        processor.processCallbacks(bean);
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String name) throws BeansException
    {
        return bean;
    }

    public void postProcessBeforeDestruction(Object bean, String name) throws BeansException
    {
        processor.deprocessCallbacks(bean);
    }

    @Bean(initMethod = "start", destroyMethod = "stop")
    public BayeuxServer bayeuxServer()
    {
        return new BayeuxServerImpl();
    }

    public void setServletContext(ServletContext servletContext)
    {
        servletContext.setAttribute(BayeuxServer.ATTRIBUTE, bayeuxServer);
    }
}

Kindly let me know if this approach is okay. Thanks a lot.

1

There are 1 best solutions below

2
On BEST ANSWER

The @Listener annotation is meant for methods that handle messages received from remote clients.

If you only need to send server-to-client messages, you don't strictly need to annotate any method with @Listener: it is enough that you retrieve the ServerChannel you want to publish to, and use it to publish the message.

In your particular case, it seems that you don't really need to broadcast a message on a channel for multiple subscribers, but you only need to send a message to a particular client, identified by the id parameter.

If that's the case, then it's probably better to just use peer-to-peer messaging in this way:

public void sendNotification(Notification notification, int id)
{
    ServerSession remoteClient = retrieveSessionFromId(id);
    remoteClient.deliver(serverSession, "/person/notification", notification);
}

This solution has the advantage to create a lot less channels (you don't need a channel per id).

Even better, you can replace the /person/notification channel (which is a broadcast channel) with a service channel such as /service/notification. In this way, it is clear that the channel used to convey notifications is for peer-to-peer communication (because service channels cannot be used to broadcast messages).

The retrieveSessionFromId() method is something that you have to map upon user login, see for example the documentation about CometD authentication.