    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.hayabusa.servlet;
    017
    018import jakarta.websocket.OnOpen;
    019import jakarta.websocket.OnClose;
    020import jakarta.websocket.OnMessage;
    021import jakarta.websocket.OnError;
    022import jakarta.websocket.Session;
    023import jakarta.websocket.EndpointConfig;
    024import jakarta.websocket.CloseReason;
    025import jakarta.websocket.server.ServerEndpoint;
    026import java.io.IOException;
    027import java.io.ByteArrayInputStream;
    028import java.io.ByteArrayOutputStream;
    029import java.nio.ByteBuffer;
    030import java.awt.image.BufferedImage;
    031import javax.imageio.ImageIO;
    032import java.util.Set;
    033import java.util.Collections;
    034import java.util.concurrent.ConcurrentHashMap;
    035
    036/**
    037 * http://enterprisegeeks.hatenablog.com/entry/2015/12/17/104815
    038 *
    039 * WebSocketBasicEndpoint.java のソースを参照しています。
    040 *
    041 * 設定が、いくつか必要です。
    042 *   ① /wsdemo をサーバーエンドポイントのURLにしているため
    043 *      WEB-INF/web.xml の security-constraint の web-resource-collection の
    044 *      url-pattern に、/wsdemo を追加する必要がある。
    045 *        &lt;url-pattern&gt;/wsdemo/*&lt;/url-pattern&gt;
    046 *
    047 *   ② コンパイル時(build.xml)のクラスパスの設定に、
    048 *      &lt;pathelement path="${env.CATALINA_HOME}/lib/websocket-api.jar" /&gt;
    049 *      を追加する必要がある。
    050 */
    051@ServerEndpoint(value="/wsdemo")
    052public class WebSocketDemo {
    053
    054        /** クライアントからの全接続を保持するセット */
    055        /**
    056         * クライアントからの全接続を保持するセット
    057         *
    058         * @og.rev 6.8.5.0 (2018/01/09) PMD Variables that are final and static should be all capitals。sessSET → SESS_SET
    059         */
    060        private static final Set<Session> SESS_SET = Collections.newSetFromMap( new ConcurrentHashMap<Session, Boolean>() );
    061
    062        /**
    063         * デフォルトコンストラクター
    064         *
    065         */
    066        public WebSocketDemo() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    067
    068        /**
    069         * 2.クライアントからの接続時にコールされる。
    070         *
    071         * 引数は以下が設定可能だが、メソッド内で使用しないなら省略できる。
    072         *
    073         * @og.rev 6.8.5.0 (2018/01/09) PMD Variables that are final and static should be all capitals。sessSET → SESS_SET
    074         *
    075         * @param client クライアントの接続情報
    076         * @param config 設定情報
    077         */
    078        @OnOpen
    079        public void onOpen( final Session client, final EndpointConfig config ) {
    080                System.out.println( client.getId() + " was connected." );
    081                printSession( client );
    082
    083                SESS_SET.add( client );
    084        }
    085
    086        /**
    087         * 3.クライアントの切断時にコールされる
    088         *
    089         * 引数は使用しなければ省略可能。
    090         *
    091         * @og.rev 6.8.5.0 (2018/01/09) PMD Variables that are final and static should be all capitals。sessSET → SESS_SET
    092         *
    093         * @param client 接続
    094         * @param reason 切断理由
    095         */
    096        @OnClose
    097        public void onClose( final Session client, final CloseReason reason ) {
    098                System.out.println( client.getId() + " was closed by "
    099                                                                +               reason.getCloseCode()
    100                                                                + "[" + reason.getCloseCode().getCode()+"]" );
    101                printSession( client );
    102
    103                SESS_SET.remove( client );
    104        }
    105
    106        /**
    107         * 4.エラー時にコールされる。
    108         *
    109         * 引数は使用しなければ省略可能。
    110         * @param client クライアント接続
    111         * @param error エラー
    112         */
    113        @OnError
    114        public void onError( final Session client, final Throwable error ) {
    115                System.out.println( client.getId() + " was error." );
    116                error.printStackTrace();
    117        }
    118
    119        /**
    120         * 5.テキストメッセージ受信時の処理
    121         *
    122         * 全クライアントにメッセージを送信する。（サンプル）
    123         *
    124         * 引数は使用しなければ省略可能。
    125         * @param text クライアントから送信されたテキスト
    126         * @param client 接続情報
    127         * @throws IOException  なんらかの入出力例外の発生を通知するシグナルを発生させます。
    128         */
    129        @OnMessage
    130        public void onMessage( final String text, final Session client ) throws IOException {
    131                System.out.println( client.getId() + " was message." + text );
    132
    133        //      // sessionが持つendPointに紐付く session を取り出す。
    134        //      for( final Session other : client.getOpenSessions() ) {
    135        //              other.getBasicRemote().sendText( text );
    136        //      }
    137                broadCast( text );              // Setで管理しているopenされたsession (元とかぶる)
    138        }
    139
    140        /**
    141         * すべてのクライアントにメッセージを送信する。
    142         *
    143         * @og.rev 6.8.5.0 (2018/01/09) PMD Variables that are final and static should be all capitals。sessSET → SESS_SET
    144         *
    145         * @param text クライアントに送信するテキスト
    146         */
    147        private static void broadCast( final String text ) {
    148                for( final Session client : SESS_SET) {
    149                        client.getOpenSessions().forEach( sess -> sess.getAsyncRemote().sendText( text ) );
    150                }
    151        }
    152
    153        /**
    154         * 6.バイナリ受信時の処理
    155         *
    156         * 送信元に画像を変換して送り返す。
    157         *
    158         * 引数は使用しなければ省略可能。
    159         * @param buf クライアントから送信されたバイナリ
    160         * @param client 接続情報
    161         * @throws IOException  なんらかの入出力例外の発生を通知するシグナルを発生させます。
    162         */
    163        @OnMessage
    164        public void onMessage( final ByteBuffer buf, final Session client ) throws IOException {
    165                client.getBasicRemote().sendBinary( grayScall( buf ) );
    166        }
    167
    168        /**
    169         * 7.画像をグレースケールに変換する。本筋とは関係ない。
    170         *
    171         * @param input 引数のバイトバッファー
    172         * @return グレースケールに変換されたバイトバッファー
    173         */
    174        private ByteBuffer grayScall( final ByteBuffer input ) throws IOException {
    175                final BufferedImage img = ImageIO.read( new ByteArrayInputStream( input.array() ) );
    176                final BufferedImage glay = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_GRAY );
    177                glay.getGraphics().drawImage( img, 0, 0, null );
    178                final ByteArrayOutputStream bos = new ByteArrayOutputStream();
    179                ImageIO.write( glay, "png", bos );
    180
    181                return ByteBuffer.wrap( bos.toByteArray() );
    182        }
    183
    184        /**
    185         * Session のセッションを標準出力に表示します。
    186         *
    187         * デバッグ用の仮メソッドです。
    188         *
    189         * @param session セッションオブジェクト
    190         */
    191        private void printSession( final Session session ) {
    192                System.out.println( " ID    = " + session.getId() );
    193        //      System.out.println( " Query = " + session.getQueryString() );           // 引数部分のみ
    194                System.out.println( " URI   = " + session.getRequestURI() );            // URI すべて
    195
    196                System.out.println( " RequestParameter : " );                                           // 引数部分を、展開して、Map化
    197                session.getRequestParameterMap().forEach( (k,v) -> {
    198                                System.out.println( "  Key=" + k );
    199                                System.out.println( "  Val=" + java.util.Arrays.toString( v.toArray( new String[v.size()] ) ) );
    200                        }
    201                );
    202        }
    203}