Spring WebSocket @SendToSession: envía un mensaje a una sesión específica

¿Es posible enviar un mensaje a una sesión específica?

Tengo un websocket no autenticado entre clientes y un servlet Spring. Necesito enviar un mensaje no solicitado a una conexión específica cuando finaliza un trabajo asíncrono.

@Controller
public class WebsocketTest {


     @Autowired
    public SimpMessageSendingOperations messagingTemplate;

    ExecutorService executor = Executors.newSingleThreadExecutor();

    @MessageMapping("/start")
    public void start(SimpMessageHeaderAccessor accessor) throws Exception {
        String applicantId=accessor.getSessionId();        
        executor.submit(() -> {
            //... slow job
            jobEnd(applicantId);
        });
    }

    public void jobEnd(String sessionId){
        messagingTemplate.convertAndSend("/queue/jobend"); //how to send only to that session?
    }
}

Como puede ver en este código, el cliente puede iniciar un trabajo asíncrono y cuando finaliza, necesita el mensaje final. Obviamente, solo necesito enviar un mensaje al solicitante y no transmitirlo a todos. Sería genial tener un@SendToSession anotación omessagingTemplate.convertAndSendToSession método.

ACTUALIZAR

Intenté esto:

messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));

Pero esto se transmite a todas las sesiones, no solo a la especificada.

ACTUALIZACIÓN 2

Prueba con el método convertAndSendToUser (). Esta prueba es un truco del tutorial oficial de Spring:https://spring.io/guides/gs/messaging-stomp-websocket/

Este es el código del servidor:

@Controller
public class WebsocketTest {

    @PostConstruct
    public void init(){
        ScheduledExecutorService statusTimerExecutor=Executors.newSingleThreadScheduledExecutor();
        statusTimerExecutor.scheduleAtFixedRate(new Runnable() {                
            @Override
            public void run() {
                messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"));
            }
        }, 5000,5000, TimeUnit.MILLISECONDS);
    } 

     @Autowired
        public SimpMessageSendingOperations messagingTemplate;
}

y este es el código del cliente:

function connect() {
            var socket = new WebSocket('ws://localhost:8080/hello');
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function(frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                stompClient.subscribe('/user/queue/test', function(greeting){
                    console.log(JSON.parse(greeting.body));
                });
            });
        }

Lamentablemente, el cliente no recibe su respuesta por sesión cada 5000 ms como se esperaba. Estoy seguro de que "1" es un Id. De sesión válido para el segundo cliente conectado porque lo veo en modo de depuración conSimpMessageHeaderAccessor.getSessionId()

ESCENARIO DE ANTECEDENTES

Quiero crear una barra de progreso para un trabajo remoto, el cliente le pide al servidor un trabajo asíncrono y verifica su progreso mediante un mensaje de WebSocket enviado desde el servidor. Esto NO es una carga de archivos sino un cálculo remoto, por lo que solo el servidor conoce el progreso de cada trabajo. Necesito enviar un mensaje a una sesión específica porque cada trabajo se inicia por sesión. El cliente solicita un cálculo remoto El servidor inicia este trabajo y, para cada paso del trabajo, responde al cliente solicitante con su estado de progreso del trabajo. El cliente recibe mensajes sobre su trabajo y crea una barra de progreso / estado. Es por eso que necesito mensajes por sesión. También podría usar mensajes por usuario, pero Springno provee por usuario mensajes no solicitados. (No se puede enviar un mensaje de usuario con Spring Websocket)

SOLUCIÓN DE TRABAJO

 __      __ ___   ___  _  __ ___  _  _   ___      ___   ___   _    _   _  _____  ___  ___   _  _ 
 \ \    / // _ \ | _ \| |/ /|_ _|| \| | / __|    / __| / _ \ | |  | | | ||_   _||_ _|/ _ \ | \| |
  \ \/\/ /| (_) ||   /| ' <  | | | .` || (_ |    \__ \| (_) || |__| |_| |  | |   | || (_) || .` |
   \_/\_/  \___/ |_|_\|_|\_\|___||_|\_| \___|    |___/ \___/ |____|\___/   |_|  |___|\___/ |_|\_|

A partir de la solución UPDATE2, tuve que completar el método convertAndSendToUser con el último parámetro (MessageHeaders):

messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));

dóndecreateHeaders() es este método:

private MessageHeaders createHeaders(String sessionId) {
        SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
        headerAccessor.setSessionId(sessionId);
        headerAccessor.setLeaveMutable(true);
        return headerAccessor.getMessageHeaders();
    }

Respuestas a la pregunta(3)

Su respuesta a la pregunta