1

Want to send notifications to specific client with websockets. Have a scheduled task for sending notifications, but cannot get Principal inside that task. Found this post, but as I know Spring scheduled methods must be parameter-free.

@Scheduled(fixedDelay=5000)
public void sendMessages(Principal principal)
messagingTemplate
    .convertAndSendToUser(principal.getName(), "/queue/horray", "Horray, " + principal.getName() + "!");
}

Is this possible? How can I get websocket principal within scheduled method?

Community
  • 1
  • 1
0bj3ct
  • 1,400
  • 4
  • 22
  • 51

3 Answers3

1

You can not get principal in a scheduled method , because the method call is not initiated by user.

You can follow this approach:

1) Create a websocket end point "/app/events"

2) Let all users to subscribe to that end point

3) Get all the userids you want to send notifications

4) Send notification to single user

simpMessagingTemplate.convertAndSendToUser("userId", "/app/events", "messageEntity"); 

userId: can be actual user id if authenticated or it can be websocket session id.
SUNIL SP
  • 29
  • 2
0

I wrote a workaround for this situation. At first I create a listener for websockets events. In case of subscription request I keep the userId from the request and keep in a ConcurrentHashMap. On the other hand when client disconnects or send unsubscribe request I remove his userId from that Map.

My Listener class:

@Service
public class MyEventListener {

  @Autowired
  private NotificationPublisher notificationPublisher;

  @EventListener({SessionSubscribeEvent.class})
  public void onWebSocketSubscribeEvent(SessionSubscribeEvent event) {
      notificationPublisher.subscribedUsers.put(event.getUser().getName(), Calendar.getInstance().getTimeInMillis());
  }

  @EventListener({SessionUnsubscribeEvent.class})
  public void onWebSocketUnsubscribeEvent(SessionUnsubscribeEvent event) {
      notificationPublisher.subscribedUsers.remove(event.getUser().getName());
  }

  @EventListener({SessionDisconnectEvent.class})
  public void onWebSocketDisconnectEvent(SessionDisconnectEvent event) {
      notificationPublisher.subscribedUsers.remove(event.getUser().getName());
  }
}

Notification publisher class where actual job is running:

public class NotificationPublisher {
  public final Map<String, Long> subscribedUsers = new ConcurrentHashMap<>();

  @Autowired
  private SimpMessagingTemplate messagingTemplate;

  @Autowired
  private MyService myService;

  @Value("${task.notifications.publisher.websocket_timeout_seconds}")
  private int websocketSessionTimeout;

  public void sendDataUpdates() {
    SocketResponseCount count = null;

    for(String key: subscribedUsers.keySet()) {
        long subscribeTime = subscribedUsers.get(key);

        if(Calendar.getInstance().getTimeInMillis() - subscribeTime > websocketSessionTimeout*1000) {
            subscribedUsers.remove(key);
            continue;
        }

        count = myService.getNotificationsCount(key);
        this.messagingTemplate.convertAndSendToUser(key, "/queue/publish",count);
    }
  }
}

Maybe it will help someone

0bj3ct
  • 1,400
  • 4
  • 22
  • 51
0

my solution: I get all user sessions

@Autowired
private SimpMessagingTemplate template;
@Autowired
private MyRepository myRepository;
@Autowired
private SessionRegistry sessionRegistry;



@Scheduled(fixedRate = 5000)
public void greeting() {

    List<SessionInformation> activeSessions = new ArrayList<>();

    for (Object principal : sessionRegistry.getAllPrincipals() )
    {
        activeSessions.addAll( sessionRegistry.getAllSessions( principal, false ) );
    }
    for (SessionInformation session : activeSessions )
    {
        Object principalObj = session.getPrincipal();
        if ( principalObj instanceof CurrentUser)
        {
            CurrentUser user = (CurrentUser) principalObj;
            this.template.convertAndSendToUser(user.getUsername().toUpperCase(),"/queue/reply2",myRepository.findAll());

        }

    }
}