Wicket8 + Tomcat9 + Websocket native broadcast

1年以上前、broadcast ALL での WebSocketBehavior の方法は確立していた。
Tomcat 8.x + Wicket8 で WebSocket native - Oboe吹きプログラマの黙示録
見直してみれば、、
org.apache.wicket.protocol.ws.api.WebSocketPushBroadcaster
broadcastAll(Application application, IWebSocketPushMessage message) を使ってたわけだ。
→ 接続してる全てに送る方法だった。

接続しているもの中で任意に特定して送信するのは、
broadcast(ConnectedMessage connection, IWebSocketPushMessage message)
を使う。
org.apache.wicket.protocol.ws.api.message.ConnectedMessage
このコンストラクタは、ConnectedMessage(Application application, String sessionId, IKey key)
よくよく注意すれば、この IKey :org.apache.wicket.protocol.ws.api.registry.IKey は、
受信するページを接続した時のイベントメソッド onConnect から ConnectedMessage が渡されるので、
ConnectedMessage の getKey() メソッドで取得できる!

受信するページのセッションIDとこの IKey が管理できてればいいわけだ。
そこで安易で原始的な方法だが、、、以下シングルトンでの管理クラスを用意
接続したページを開いた時刻も認識できるようにしたいので、LocalDateTime値も保存

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.wicket.protocol.ws.api.message.ConnectedMessage;
import org.apache.wicket.protocol.ws.api.registry.IKey;

public final class WebsocketManager{
   private static WebsocketManager inst;
   private Map<String, LocalDateTime> connectedMap;
   private Map<String, IKey> keyMap;

   private WebsocketManager(){
      connectedMap = new HashMap<>();
      keyMap = new HashMap<>();
   }
   public static synchronized WebsocketManager getInstance(){
      if (inst==null) inst = new WebsocketManager();
      return inst;
   }
   /**
    * 受信ページ接続
    * @param message ConnectedMessage
    */
   public void ready(ConnectedMessage message){
      connectedMap.put(message.getSessionId(), LocalDateTime.now());
      keyMap.put(message.getSessionId(), message.getKey());
   }
   /**
    * sessionId → IKey参照.
    * @param sessionId
    * @return Optional<IKey>
    */
   public Optional<IKey> getIKeyOption(String sessionId){
      return Optional.ofNullable(keyMap.get(sessionId));
   }
   /**
    * sessionId → IKey参照.
    * @param sessionId
    * @return Optional<IKey>
    */
   public IKey getIKey(String sessionId){
      return keyMap.get(sessionId);
   }
   public void remove(ClosedMessage message){
      connectedMap.remove(message.getSessionId());
      keyMap.remove(message.getSessionId());
   }
   public void destroy(){
      connectedMap.clear();
      keyMap.clear();
   }
   public int count(){
      return connectedMap.size();
   }
   /**
    * セッションID Stream 取得
    * @return Stream<String>
    */
   public Stream<String> idstream(){
      return keyMap.keySet().stream();
   }
   public List<String> idlist(){
      return keyMap.keySet().stream().collect(Collectors.toList());
   }
}

extends WebApplication にて、、

@Override
protected void init(){
   WebsocketManager.getInstance();
   // TODO
}
@Override
protected void onDestroy(){
   super.onDestroy();
   WebsocketManager.getInstance().destroy();
}

受信するPageのコンストラクタで、、WebSocketBehavior を追加
  ( onMessage は動かなかった。→ なぜか不明)

add(new WebSocketBehavior(){
   @Override
   protected void onConnect(ConnectedMessage message){
      super.onConnect(message);
      WebsocketManager.getInstance().ready(message);
   }
   @Override
   protected void onClose(ClosedMessage message){
      WebsocketManager.getInstance().remove(message);
   }
   @Override
   public void onException(Component component, RuntimeException exception){
      
   }
});

受信するPage として、onEvent をオーバーライド

@Override
public void onEvent(IEvent<?> event){
   if (event.getPayload() instanceof WebSocketPushPayload){
      WebSocketPushPayload wsEvent = (WebSocketPushPayload)event.getPayload();
      WebSocketRequestHandler handler = wsEvent.getHandler();

      // FeedMessage という任意の受信データを受け取る
      FeedMessage feed = (FeedMessage)wsEvent.getMessage();
      
      String message = Optional.ofNullable(feed.getMessage()).orElse("");
      // TODO

      // ページに書いたコンテナ更新
      handler.add(messageLabel);
      handler.appendJavaScript("alert('" + message + "');");
   }
}

送受信メッセージ を定義するクラス FeedMessage

import org.apache.wicket.protocol.ws.api.message.IWebSocketPushMessage;
public class FeedMessage implements IWebSocketPushMessage{
   public String message;
   public String getMessage(){
      return message;
   }
}

接続している受信Page 全てに送る場合、、、broadcastAll 実行

FeedMessage message = new FeedMessage();
message.message = text;

WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(getApplication());
WebSocketPushBroadcaster broadcaster = new WebSocketPushBroadcaster(webSocketSettings.getConnectionRegistry());
broadcaster.broadcastAll(getApplication(), message);


特定の受信Page だけに送る場合、、、broadcast 実行

String sessionId = // 予め管理している接続セッションID管理 WebsocketManager から特定する

WebSocketSettings webSocketSettings = WebSocketSettings.Holder.get(getApplication());
WebSocketPushBroadcaster broadcaster = new WebSocketPushBroadcaster(webSocketSettings.getConnectionRegistry());
broadcaster.broadcast(new ConnectedMessage(getApplication(), sessionId, WebsocketManager.getInstance().getIKey(sessionId)), message);

尚、これを動くようにした時、
org.apache.wicket.protocol.ws.javax.JavaxWebSocketFilter による WebFilter を web.xml には、
定義しないで動いたし、WebSocket EndPoint を定義しているわけでもない。

だから厳密には、WebSocket の通信とは言えないかもしれけど、
任意のPUSHを実行したいという要望には、充分、応えられる機能だ。