2013年12月12日木曜日

GlassFish4.0ならJavaEE7のWebSocketが試せる!

この記事は「GlassFish Advent Calendar 2013」の12日目の記事となります。

昨日はGlassFish Users Group Japanの運営をされています HASUNUMA Kenjiさん(@btnrouge)による「GlassFish配布物のサイズについて」でした。 http://www.coppermine.jp/docs/programming/2013/12/glassfish-distribution.html


はじめまして


私(@itokami1123)は、2年間ほどiPhoneやiPad向けの主にフロント部を担当しておりました(Objective-C、HTML5/CSS3/JavaScript)。

来年から心機一転、JavaEEとSeasar2(Teeda)をすることになりました!
ただいま必死に勉強中です!
Java達人の皆様どうぞよろしくお願いいたします。


本日は、WebSocketについて書かせてください!


GlassFishと言えば

”Java EE 7のリファレンス実装であるGlass Fish 4が,現時点では唯一のJava EE認証済アプリケーションサーバ”

http://www.infoq.com/jp/news/2013/11/glassfish-commercial-dead

そこで、JavaEE7のWebSocketについて書いてみたいと思います。


参考URL


きしだ先生のブログを参考にしました。ありがとうございます!

WebSocketをネタにJava EE 7正式版を試してみる

http://d.hatena.ne.jp/nowokay/20130613

こちらのソースを参考にリアルタイムの投票システムを作ってみたいと思います。


完成予定イメージ


enter image description here

各人が好きなエディタを選ぶとリアルタイムに投票されるもの。
いつか勉強会で集計に使ってみたいです。

あぁ、すみません、まだMacのChromeでしか動作確認してません..


WebSocketサービスの実装


参考記事を少し改造して各人の投票を保存するようにしました。 graphValsに各人のセッションをキーに投票結果を保持しています。

package com.exsample.service;

import java.util.Set;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/vote")
public class VoteWSEndpoint {

    static Map<Session,String> graphVals = Collections.synchronizedMap(new HashMap<Session,String>());

    @OnMessage
    public void onMessage(String message,Session onSession) {

        VoteWSEndpoint.graphVals.put(onSession, message);

        int sub = 0;
        int vim = 0;
        int ema = 0;

        for ( Session session: onSession.getOpenSessions() ){
            String target = VoteWSEndpoint.graphVals.get(session);

            if ( target == null) {
                System.out.println("target is null");

            } else {
                switch (target) {
                    case "sub":
                        sub++;
                        break;
                    case "vim":
                        vim++;
                        break;
                    case "ema":
                        ema++;
                        break;
                    default:
                }
            }

        }

        String retJsonString = "{ " + 
                "\"sub\": " + sub + ", " + 
                "\"vim\": " + vim + ", " + 
                "\"ema\": " + ema + " " + 
                "}";

        for (Session session: onSession.getOpenSessions() ){
            session.getAsyncRemote().sendObject(retJsonString);
        }

    }

}
寺田様ご指摘ありがとうございます!  
getOpenSessionsを使用した版に修正しました!


画面のタグは↓のように作りました。
div.lineが各投票の棒グラフになります。
.js-で始まるクラスはJavaScript制御用で
装飾には使用していません。

<!DOCTYPE html>
<html>
    <head>
        <title>editor</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width">
        <link rel="stylesheet" href="index.css" />
        <script src="jquery.min.js" ></script>
        <script src="Vote.js" ></script>
    </head>
    <body>
        <article class="vote-page" >
            <section class="graph" >
                <div class="graph-item">
                    <div class="title" >Sublime Text</div>
                    <div class="line js-sub-line" ></div>
                </div>
                <div class="graph-item">
                    <div class="title" >Vim</div>
                    <div class="line js-vim-line" ></div>
                </div>
                <div class="graph-item">
                    <div class="title" >Emacs</div>
                    <div class="line js-ema-line" ></div>
                </div>
            </section>
            <section class="editor-select" >
                <label>Sublime Text<input type="radio" name="likeEditor" value="sub" /></label>
                <label>Vim<input type="radio" name="likeEditor"  value="vim" /></label>
                <label>Emacs<input type="radio" name="likeEditor" value="ema" /></label>
            </section>
        </article>
    </body>
</html>


棒グラフは “-webkit-transition: 1s;”を使ってアニメーションさせてます。

htmo,body{
    margin:0;
    padding:0;
    background-color: #f6f6f6;
}

.vote-page{
    margin:16px;
    padding: 16px;
    width: 480px;
    border-radius: 4px;
    background-color: #f9f9f9;
    box-shadow: 0 1px 4px rgba( 0, 0, 0, 0.4);
}

.graph-item{
    height:24px;
    margin:16px;
    padding: 16px;
    box-shadow: 0 1px 1px rgba( 0, 0, 0, 0.2);
}

.graph-item .title{
    padding:0 8px;
    float: left;
    width: 120px;
    height:24px;
    line-height: 24px;
    text-align: right;
    font-size: 16px;
}

.graph-item .line{
    float: left;
    width: 200px;
    height:24px;
    line-height: 24px;
    background-color: #ef9900;
    border-radius: 4px;
    -webkit-transition: 1s;
}

.editor-select{
    padding: 8px;
    margin:8px;
    text-align: center;
}

.editor-select label{
    margin:0 16px;
}

JavaScriptは以下のように作りました。

  • VoteApiServiceクラス
    WebSoketサーバへの通信を行います。

  • GraphModelクラス
    グラフデータを保持します。

  • GraphViewクラス DOMイベントによるユーザ操作の受付
    GraphModelの内容を画面に表示

$(function() {

    var graphModel = new GraphModel();
    new GraphView(graphModel);

});

var VoteApiService = function(graphModel){
    this.socket = null;
    this.graphModel = graphModel;
    this.initialize(graphModel);
};

VoteApiService.prototype.initialize = function(graphModel){

    var host = this._createHostPath();  
    this.socket = new WebSocket(host);

    this.setWsEvents();

};

VoteApiService.prototype.setWsEvents = function(){  
    this.socket.onmessage = $.proxy(this.receiveMsg,this);
	this.socket.onopen = $.proxy(this.stratSession,this);
};

VoteApiService.prototype.save = function(message){
    this.socket.send( message );
};

VoteApiService.prototype.receiveMsg = function(message){
    var json = window.JSON.parse(message.data);
    this.graphModel.setVal(json);
};

VoteApiService.prototype.stratSession = function(){
    this.socket.send( "" );
};

VoteApiService.prototype._createHostPath = function(){
    var path = window.location.href.replace("http:","").replace("index.html","");
    return "ws:" + path + "vote";
};

var GraphModel = function(){

    this.voteApiService = null;

    this.sub = 0;
    this.vim = 0;
    this.ema = 0;
    this.updateCallBack = null;
    this.initialize();

};

GraphModel.prototype.initialize = function(){
    this.voteApiService = new VoteApiService(this);
};

GraphModel.prototype.setUpdateCallBack = function( updateCallBack){
    this.updateCallBack = updateCallBack;
};

GraphModel.prototype.kiminiKimeta = function( editor_val){
    this.voteApiService.save( editor_val);
};

GraphModel.prototype.setVal = function(data){
    this.sub = data.sub;
    this.vim = data.vim;
    this.ema = data.ema;

    if ( this.updateCallBack ){
        this.updateCallBack();
    }

};


var GraphView = function(graphModel,voteApi){

    this.$el = null;

    this.graphModel = graphModel;

    this.voteApi = voteApi;

    this.$subLine = null;
	this.$vimLine = null;
    this.$emaLine = null;

    this.initialize(graphModel);
};

GraphView.prototype.initialize = function(){

    this.graphModel.setUpdateCallBack( $.proxy(this.render,this));

    this.setManagedDom();

    this.setDomEvents();

    this.render();
};

GraphView.prototype.setManagedDom = function(){
    this.$el = $('.vote-page');
    this.$subLine = $('.js-sub-line');
    this.$vimLine = $('.js-vim-line');
    this.$emaLine = $('.js-ema-line');
};

GraphView.prototype.setDomEvents = function(){
    var $editorChks =  this.$el.find('input[name="likeEditor"]');
    $editorChks.change( $.proxy( this.edita_kimetaYo, this) );
};

GraphView.prototype.edita_kimetaYo = function(event){

    var editor = $(event.target).val();
    this.graphModel.kiminiKimeta( editor );
};


GraphView.prototype.render = function(){
    this.$subLine.width( this.graphModel.sub*10 );
	this.$vimLine.width( this.graphModel.vim*10 );
    this.$emaLine.width( this.graphModel.ema*10 ); 
};

ソースにコメントがなくすみません…
コメントをつけてGitHubにあげたいと思います..


まとめ


JavaEE7のWebSocket機能を使えば 簡単に投票システムが作れる事が分かりました。
また、NetBeansを使えば JavaもJavaScriptもサクサクと作成する事ができるのですね。
JAX-RS等もありJavaEE7はJavaScriptエンジニアにも 使いやすいサーバになっていっているのですね!(^_^



参加させて頂きありがとうございました!


Java & GlassFish & JavaEE を深く学んでいきたいとおもいます。
どうぞよろしくお願いいたします!


明日はHASUNUMA Kenjiさん(@btnrouge)です。

2 件のコメント:

  1. ブログへの御投稿ありがとうございます。寺田です。

    1点だけ下記の部分でお伝えしたくてコメントさせて頂きました。

    static Set sessions = Collections.synchronizedSet(new HashSet());

    下記の部分で、ご自身でクライアント一覧を管理しなくても、
    Set sessions = session.getOpenSessions() で
    サーバに接続済みの全クライアントのセッション情報が
    取れるようになっております。
    http://docs.oracle.com/javaee/7/api/javax/websocket/Session.html#getOpenSessions%28%29

    つまり、下記のようにかけるようになっております。
    for (Session session: session.getOpenSessions()){
    session.getAsyncRemote().sendObject(retJsonString);
    }
    }

    ご参考にして頂ければ誠に幸いです。

    返信削除
  2. ご指摘ありがとうございます!!
    今に帰って修正しようと思います!!!

    返信削除