GlassFish」タグアーカイブ

eclipseのglassfish用プラグイン

Eclipseのアップデートを行ったところ「http://dlc.sun.com.edgesuite.net/glassfish/eclipse/kepler/ のリポジトリーが見つかりません。」と怒られた。

 

アドレス少し削って

http://dlc.sun.com.edgesuite.net/glassfish/eclipse/

とすると

Attention – GlassFish plugins will be distributed exclusively as part of the OEPE package.

To get information about OEPE, please visit OEPE page on OTN.

と出てきました。

少し前にtwitterで言ってたかも。。

 

というわけでプラグインから削除

WebSocketにてJSON形式のデータをJavaFXとやり取りしてみる。の4

先ほどに続いて送受信部分

送信処理

  1. 画面の送信ボタンからSingleController#handleSendActionが呼ばれる。
  2. SingleController#handleSendActionから@ClientEndpointのsendメソッド(自分で作った普通のメソッド)を呼び出す。
  3. sendメソッド内でSession.getAsyncRemote().sendObject(dataObj)を呼び出す
  4. encodersに指定してあるClientEncoderクラスのencodeメソッドが呼び出される。
  5. encodeメソッドでエンコードされたデータ(JSON形式の文字列)がServerEndpointに送信される。

エコープログラムなのでServerEndpointで受信されたデータは(若干加工され)ClientEndpointに戻される。そのため、続けて受信処理が走る。

 

受信処理

  1. サーバからのデータがPushされる。
  2. @ClientEndpointのdecodersに指定してあるClientDecoderクラスのwillDecodeメソッドが呼び出され入力値がチェックされる。
  3. willDecodeの結果がtrueならば続けてdecodeメソッドが呼び出されてジェネリクスで指定した型(ClientData)にデコードされる。
  4. デコードされたデータを引数に@ClientEndpointの@OnMessageが呼び出される。ここはJavaFXのスレッド配下ではなく、WebSocketのスレッドに移っている。そのため、JavaFXのコントロールを直接操作することはできない。
  5. @OnMessageの中でPlatform.runLaterを呼び、JavaFXの画面操作(接続時処理ViewObj#write)の呼び出しを登録(?)する。
  6. JavaFXスレッドに戻り、JavaFXの画面操作が実行される。

(コントローラーの関連する部分)

public class SingleController implements Initializable {
    @FXML
    private void handleSendAction(ActionEvent event) {
        System.out.println("SingleController#handleSendAction");
        wsClient.send(msgInput.getText(), sess);
    }
}

(ClientEndpointの関連する部分)

@ClientEndpoint( 
   decoders = { ClientDecoder.class }, 
   encoders = { ClientEncoder.class })
public class WSJsonSingleClient {

    @OnMessage
    public void onMessage(ClientData dataObj) {
        System.out.println("WSJsonSingleClient#onMessage");
        final ClientData channeled = dataObj;
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                viewObj.write(
                        channeled.getSessionId(), 
                        channeled.getMessageTimeString(), 
                        channeled.getMessage());
            };
        });
    }

    public void send(String text,Session sess) {
        System.out.println("WSJsonSingleClient#send");
        ClientData dataObj = new ClientData(text);
        sess.getAsyncRemote().sendObject(dataObj);
    }
}

(ClientEncoder)

public class ClientEncoder implements Encoder.Text<ClientData> {

    @Override
    public void init(EndpointConfig paramEndpointConfig) {
        System.out.println("ClientEncoder#init");
    }

    @Override
    public void destroy() {
        System.out.println("ClientEncoder#destroy");
    }

    @Override
    public String encode(ClientData paramData) throws EncodeException {
        System.out.println("ClientEncoder#encode");
        JsonObject model = Json.createObjectBuilder()
                   .add("message", paramData.getMessage())
                   .add("messageTime",paramData.getMessageTime())
                   .build();
        return model.toString();
    }
}

(ClientDecoder)

public class ClientDecoder implements Decoder.Text<ClientData> {

    @Override
    public void destroy() {
        System.out.println("ClientDecoder#destroy");
    }

    @Override
    public void init(EndpointConfig arg0) {
        System.out.println("ClientDecoder#init");
    }

    @Override
    public ClientData decode(String inputString) throws DecodeException {
        System.out.println("ClientDecoder#decode");
        try{
            JsonObject jsonObject = Json.createReader(new StringReader(inputString)).readObject();
            return  new ClientData(jsonObject);
        } catch(Exception e){
            e.printStackTrace();
            throw new DecodeException(inputString,"ClientDecoder#decode失敗", (Throwable)e);
        }
    }

    @Override
    public boolean willDecode(String inputString) {
        try {
            System.out.println("ClientDecoder#willDecode");
            Json.createReader(new StringReader(inputString)).readObject();
            return true;
        } catch (JsonException ex) {
            ex.printStackTrace();
            return false;
        }
    }
}

github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE

WebSocketにてJSON形式のデータをJavaFXとやり取りしてみる。の3

先ほどの続き

接続処理

  1. 画面の開始ボタンからSingleController#handleStartButtonActionが呼ばれる。
  2. handleStartButtonAction内でClientManager#connectToServerを呼びWebSocketの起動を開始する。
  3. WebSocketの@OnOpenが呼ばれる。ここはJavaFXのスレッド配下ではなく、WebSocketのスレッドに移っている。そのため、JavaFXのコントロールを直接操作することはできない。
  4. @OnOpenの中でPlatform.runLaterを呼び、JavaFXの画面操作(接続時処理ViewObj#open)の呼び出しを登録(?)する。
  5. JavaFXスレッドに戻り、JavaFXの画面操作が実行される。

切断処理1

  1. 画面の終了ボタンからSingleController#handleStoptButtonActionが呼ばれる。
  2. Session#closeを呼び出し、WebSocketの終了処理を開始する。
  3. 切断処理2に続く

 

切断処理2

  1. 「切断処理1」または「ServerEndpointからの切断」等により、WebSocketの@OnCloseが呼ばれる。ここはJavaFXのスレッド配下ではなく、WebSocketのスレッドに移っている。そのため、JavaFXのコントロールを直接操作することはできない。
  2. @OnCloseの中でPlatform.runLaterを呼び、JavaFXの画面操作(接続時処理ViewObj#close)の呼び出しを登録(?)する。
  3. JavaFXスレッドに戻り、JavaFXの画面操作が実行される。

(コントローラーの関連する部分)

public class SingleController implements Initializable {

    private WSJsonSingleClient wsClient = null;
    private Session sess = null;

    // 略

    @FXML
    private void handleStartButtonAction(ActionEvent event) {
        System.out.println("SingleController#handleStartButtonAction");
        try {
            ClientManager m = org.glassfish.tyrus.client.ClientManager
                    .createClient();
            URI clientURI = new URI("ws://localhost:8080/first/hellojson/");
            sess = m.connectToServer(wsClient, clientURI);

        } catch (DeploymentException | URISyntaxException e) {
            Logger.getLogger(SampleController.class.getName()).log(
                    Level.SEVERE, null, e);
        }
    }

    @FXML
    private void handleStoptButtonAction(ActionEvent event) {
        System.out.println("SingleController#handleStoptButtonAction");
        try {
            sess.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

   // 略
}

(ClientEndpointの関連する部分)

@ClientEndpoint( decoders = { ClientDecoder.class }, encoders = { ClientEncoder.class })
public class WSJsonSingleClient {
    // 略
    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WSJsonSingleClient#onOpen");
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                viewObj.open();
            };
        });
    }

    // 略

    @OnClose
    public void closeConnection(Session session) {
        System.out.println("WSJsonSingleClient#closeConnection");
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                viewObj.close();
            };
        });
    }
    // 略
}

github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE

JSON送受信部分へ

 

WebSocketにてJSON形式のデータをJavaFXとやり取りしてみる。の2

先ほどの続き

前提条件

  • JavaFXのコントロール(ラベルとかボタンとか)はJavaFXのスレッド配下からでないと原則(※1)操作できない(と思う)
  • WebSocketはプログラム本体とは別のスレッドでデータの送受信を行う。
  • WebSocketのスレッド内からPlatform#runLaterを呼ぶことで、JavaFXのスレッド配下で行わせたい処理を登録できる。
  • 今の実装だと、接続状況の排他制御がきちんとされていないので、「サーバからの切断」と「クライアントからの起動」のシーケンスが重なったときとかにうまく動かないケースがでてくる。(はず)

※1 こちらにあるようにコントロールに結びついたObservableListを別スレッドから操作した場合は、その操作が画面に反映される。ラベルとかボタンとかのコントロールで試した範囲ではJavaFXのスレッドかどうかチェックにかかってうまくいかなかったがやりようはあるかもしれない。

ApplicationクラスとFXML

Applicationクラスは起動するだけ。

public class WSJsonSingle extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("SingleJson.fxml"));
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

 

FXMLはfx:idふって、onActionを紐付けている程度。

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane prefHeight="363.0" prefWidth="502.0" xmlns:fx="http://javafx.com/fxml" fx:controller="jp.co.epea.wsclient.SingleController">
  <children>
    <Label alignment="CENTER" contentDisplay="CENTER" prefHeight="43.0" prefWidth="502.0" text="JSON送受信" />
    <VBox layoutY="43.0" prefHeight="331.0" prefWidth="502.0">
      <children>
        <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
          <children>
            <Button fx:id="startButton" mnemonicParsing="false" onAction="#handleStartButtonAction" text="開始" />
            <Button fx:id="stopButton" mnemonicParsing="false" onAction="#handleStoptButtonAction" text="終了" />
            <Label fx:id="statusLabel" text="ステータス" />
          </children>
        </HBox>
        <HBox prefHeight="100.0" prefWidth="200.0">
          <children>
            <TextField fx:id="msgInput" editable="false" prefWidth="200.0" />
            <Button fx:id="sendButton" mnemonicParsing="false" onAction="#handleSendAction" text="送信" />
          </children>
        </HBox>
        <VBox prefHeight="200.0" prefWidth="100.0">
          <children>
            <HBox prefHeight="100.0" prefWidth="200.0">
              <children>
                <Label text="発言者:" />
                <Label fx:id="nameLabel" text="発言者初期値(起動時に変更)" />
              </children>
            </HBox>
            <HBox prefHeight="100.0" prefWidth="200.0">
              <children>
                <Label text="発言日時:" />
                <Label fx:id="timeLabel" text="発言日時初期値(起動時に変更)" />
              </children>
            </HBox>
            <HBox prefHeight="100.0" prefWidth="200.0">
              <children>
                <Label text="発言内容:" />
                <Label fx:id="msgLabel" text="発言内容初期値(起動時に変更)" />
              </children>
            </HBox>
          </children>
        </VBox>
      </children>
    </VBox>
  </children>
</AnchorPane>

起動処理

WSJsonSingle#mainから。

画面項目の初期化と、ClientEndpointのインスタンス化(まだ接続しない)。

public class SingleController implements Initializable {

    private WSJsonSingleClient wsClient = null;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        // コントロールをまとめたクラスを作って
        ViewObj obj = new ViewObj(nameLabel, timeLabel, msgLabel, statusLabel,
                msgInput, startButton, stopButton, sendButton);
        // ClientEndpointに渡してインスタンス化
        wsClient = new WSJsonSingleClient(obj);
    }
}

public class ViewObj {
    private Label nameLabel;
   // 略
    ViewObj(Label nameLabel, Label timeLabel, Label msgLabel,
            Label statusLabel, TextField msgInput, Button startButton,
            Button stopButton, Button sendButton) {
        super();
        // 略
        // コントロール類の初期化(項目設定とか、ボタンのdisable周りとか)
        this.init();
    }

    private void init(){
        nameLabel.setText("");
        // 略
    }
}

@ClientEndpoint( decoders = { ClientDecoder.class }, encoders = { ClientEncoder.class })
public class WSJsonSingleClient {

    private ViewObj viewObj;

    public WSJsonSingleClient(ViewObj viewObj) {
        this.viewObj = viewObj;
    }
    // 略
}

github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE

続き

WebSocketにてJSON形式のデータをJavaFXとやり取りしてみる

Glassfish4とJavaFX間でJSON形式のデータをWebsocketにてやり取りしてみたのでメモ。

何ページかに分かれているけど実際にJSONのハンドリングをしている箇所はこちら

サーバサイドはこの前HTMLとやり取りしたものとほぼ同じ。

大まかな動きは以下。なお、表示はリスト形式ではなくてラベルの中身を置換していく形式(表示以外はやっていることはHTMLの時とほぼ一緒)

  1. HTMLからJSONで入力テキストと現在日時を送信。
  2. GlassfishのWebsocketで受信(&Decode)
  3. 受け取ったデータに情報(とりあえずユーザ情報代わりのセッションID)を付与し繋がっているクライアント(JavaFX&この前作ったHTML)全体にJSON形式で返信
  4. JavaFX(またはHTML)で受け取ったデータを表示

データ形式はこの前と同じ。(なので、クライアントはこの前のHTMLとJavaFXで併用できる。)

(JavaFX->glassfishのデータ形式)
{"message":"メッセージ","messageTime":時間(ミリ秒)}

(glassfish->JavaFXのデータ形式)
{"message":"メッセージ","sessionId":"セッションID","messageTime":"HH:mm:ss"}

斜体は変数

Maven

必要な依存関係を追加。(動作確認はeclipse上のM2E)

    <dependencies>
       <!-- JavaFX 2 -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>javafx</artifactId>
            <version>2.2</version>
            <scope>system</scope>
            <systemPath>${javafx2.home}</systemPath>
        </dependency>
        <!-- Websocket (JSR-356) -->
        <!-- TYRUS-210の対応で1.2にあげている -->
        <dependency>
            <groupId>org.glassfish.tyrus</groupId>
            <artifactId>tyrus-client</artifactId>
            <version>1.2</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.glassfish.tyrus</groupId>
            <artifactId>tyrus-container-grizzly</artifactId>
            <version>1.2</version>
            <scope>compile</scope>
        </dependency>
        <!-- JSON Processing (JSR-353) -->
        <dependency>
            <groupId>javax.json</groupId>
            <artifactId>javax.json-api</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>javax.json</artifactId>
            <version>1.0.2 </version>
        </dependency>
    </dependencies>

画面フロー

起動時

開始ボタンしか押せない状態。裏ではWebSocket用のClientEndPointをインスタンス化している。

initial

 

開始ボタンクリック後(の入力欄に文字入力した状態)

入力欄や送信ボタン・終了ボタンが使えるようになる。

bef_send

送信ボタンクリック後

入力メッセージと時間がサーバに送信される。そしてデータを受信したサーバにて加工(といってもセッションID文字列を付加しただけ)されたデータがクライアントに送られ、そのデータがラベルに表示される。

aft_send

 

終了ボタンクリック後

開始ボタンしか押せない状態になる。

closed

 

github:https://github.com/epea/test01/tree/WF_JSON_FX_SINGLE

続き。

TYRUS-210の対応

昨日書いたDecoder.TextのwillDecodeが2度呼ばれる問題の対応(下参照)、試したら動いた。

Tyrus 1.2は既にGlassFishに統合・組み込まれています。
Nightly Buildのダウンロードもしくは手作業で新しいTyrusにアップグレードすることができます
(手作業の場合、全てのTyrusのjarファイルを置き換えて下さい)。
  1. Nightly Buildから最新のバージョンをダウンロード(8/11時点で glassfish-4.0.1-b02-08_10_2013.zip
  2. ダウンロードしたzipのglassfish4\glassfish\modulesに入っているtruis*.jarをインストールパスの同じところに上書き(自分の環境ではC:\develop\middles\glassfish4\glassfish\modules)

以上でeclipseから動かしてみるとDecoder.TextのwillDecodeが2度呼ばれることは無くなっていた。Eclipse上でのMaven指定等は特に修正する必要はなかった。

 

他にもいくつかバグが修正されているのでそれも直っているはず。

 

WebSocketのエンコード・デコード時のエラー

先日書いたWebsocketでJSONデータをやり取りしたブログだけど、エラー出力をきちんとしていなくてはまった。

Decoder.Text<T>のdecodeメソッド内でExceptionが発生した場合に、明示的にログ・スタックトレースの出力をしておかないと特にスタックトレース等が出力されないでdecodeメソッド(とそれから呼ばれる@OnMessage)が終了してしまう。(コンソールにエラーがつらつら出るようなことは無い。)

そのため、下みたいな感じで明示的にエラーあったことを出力してやらないとわかりにくい。

    @Override
    public TestData decode(String inputString) throws DecodeException {
        try{
            JsonObject jsonObject = Json.createReader(new StringReader(inputString)).readObject();
            return  new TestData(jsonObject);
        } catch(Exception e){
            DecodeException de = 
                    new DecodeException(inputString,"decode失敗", (Throwable)e);
            Logger.getLogger(TestDecoder.class.getName()).log(
                    Level.SEVERE, null, de);
            throw de;
        }
    }

Encoder.Text<T>のencodeメソッドも似たような話がある。

    @Override
    public String encode(TestData paramData) throws EncodeException {
        try{
            JsonObject model = Json.createObjectBuilder()
                       .add("message", paramData.getMessage())
                       .add("sessionId",paramData.getSessionId())
                       .add("messageTime", formatDate(paramData.getMessageTime()))
                       .build();
            return model.toString();
        } catch( Exception e ){
            EncodeException ee =  
                    new EncodeException(paramData, "encode失敗",  (Throwable)e);
            Logger.getLogger(TestEncoder.class.getName()).log(
                    Level.SEVERE, null, ee);
            throw ee;
        }
    }

一応decodeでエラーを発生させたときのログサンプル

重大: javax.websocket.DecodeException: decode失敗
    at jp.co.epea.first.json.TestDecoder.decode(TestDecoder.java:33)
    at jp.co.epea.first.json.TestDecoder.decode(TestDecoder.java:1)
    at org.glassfish.tyrus.core.EndpointWrapper.decodeCompleteMessage(EndpointWrapper.java:278)
    at org.glassfish.tyrus.core.SessionImpl.notifyMessageHandlers(SessionImpl.java:386)
    at org.glassfish.tyrus.core.EndpointWrapper.onMessage(EndpointWrapper.java:495)
    at org.glassfish.tyrus.server.TyrusEndpoint.onMessage(TyrusEndpoint.java:174)
    at org.glassfish.tyrus.websockets.DefaultWebSocket.onMessage(DefaultWebSocket.java:156)
    at org.glassfish.tyrus.websockets.frametypes.TextFrameType.respond(TextFrameType.java:66)
    at org.glassfish.tyrus.websockets.DataFrame.respond(DataFrame.java:102)
    at org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler.onDataAvailable(TyrusHttpUpgradeHandler.java:113)
    at org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.processDataAvailable(InputBuffer.java:488)
    at org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.onDataAvailable(InputBuffer.java:453)
    at org.glassfish.grizzly.http.io.InputBuffer.append(InputBuffer.java:855)
    at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:222)
    at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114)
    at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838)
    at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544)
    at java.lang.Thread.run(Thread.java:724)
Caused by: java.lang.ClassCastException: org.glassfish.json.JsonStringImpl cannot be cast to javax.json.JsonNumber
    at org.glassfish.json.JsonObjectBuilderImpl$JsonObjectImpl.getJsonNumber(JsonObjectBuilderImpl.java:171)
    at jp.co.epea.first.json.TestData.<init>(TestData.java:21)
    at jp.co.epea.first.json.TestDecoder.decode(TestDecoder.java:30)
    ... 27 more

WebSocketで@ServerEndpointが重複したときのエラー

@ServerEndpoint("/hello/")
public class HelloWorld {

と

@ServerEndpoint("/hello/")
public class HelloJson {

で起動時の

情報: Registering WebSocket filter for url pattern /*
といった次に
at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:131)
… 46 more
Caused by: javax.websocket.DeploymentException: Found Equivalent paths. Added path: ‘/first/hello/’ is equivalent with ‘/first/hello/’.

とわかりやすいメッセージがでる。

一応スタックトレース

情報: Registering WebSocket filter for url pattern /*
重大: WebModule[/first]Exception starting filter WebSocket filter
java.lang.InstantiationException
    at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:135)
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:5297)
    at org.apache.catalina.core.StandardContext.start(StandardContext.java:5909)
    at com.sun.enterprise.web.WebModule.start(WebModule.java:691)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:1041)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:1024)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:747)
    at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:2278)
    at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1924)
    at com.sun.enterprise.web.WebApplication.start(WebApplication.java:139)
    at org.glassfish.internal.data.EngineRef.start(EngineRef.java:122)
    at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:291)
    at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:352)
    at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:497)
    at com.sun.enterprise.v3.server.ApplicationLoaderService.processApplication(ApplicationLoaderService.java:407)
    at com.sun.enterprise.v3.server.ApplicationLoaderService.postConstruct(ApplicationLoaderService.java:243)
    at org.jvnet.hk2.internal.ClazzCreator.postConstructMe(ClazzCreator.java:281)
    at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:328)
    at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:448)
    at org.glassfish.hk2.runlevel.internal.AsyncRunLevelContext.findOrCreate(AsyncRunLevelContext.java:163)
    at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2204)
    at org.jvnet.hk2.internal.ServiceHandleImpl.getService(ServiceHandleImpl.java:93)
    at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$QueueRunner.oneJob(CurrentTaskFuture.java:673)
    at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$QueueRunner.run(CurrentTaskFuture.java:660)
    at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$UpOneJob.run(CurrentTaskFuture.java:490)
    at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$UpAllTheWay.go(CurrentTaskFuture.java:362)
    at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture$UpAllTheWay.access$100(CurrentTaskFuture.java:279)
    at org.glassfish.hk2.runlevel.internal.CurrentTaskFuture.go(CurrentTaskFuture.java:113)
    at org.glassfish.hk2.runlevel.internal.AsyncRunLevelContext.proceedTo(AsyncRunLevelContext.java:296)
    at org.glassfish.hk2.runlevel.internal.RunLevelControllerImpl.proceedTo(RunLevelControllerImpl.java:66)
    at com.sun.enterprise.v3.server.AppServerStartup.proceedTo(AppServerStartup.java:532)
    at com.sun.enterprise.v3.server.AppServerStartup.run(AppServerStartup.java:329)
    at com.sun.enterprise.v3.server.AppServerStartup.doStart(AppServerStartup.java:226)
    at com.sun.enterprise.v3.server.AppServerStartup.start(AppServerStartup.java:217)
    at com.sun.enterprise.glassfish.bootstrap.GlassFishImpl.start(GlassFishImpl.java:79)
    at com.sun.enterprise.glassfish.bootstrap.GlassFishDecorator.start(GlassFishDecorator.java:63)
    at com.sun.enterprise.glassfish.bootstrap.osgi.EmbeddedOSGiGlassFishImpl.start(EmbeddedOSGiGlassFishImpl.java:75)
    at com.sun.enterprise.glassfish.bootstrap.GlassFishDecorator.start(GlassFishDecorator.java:63)
    at com.sun.enterprise.glassfish.bootstrap.osgi.OSGiGlassFishImpl.start(OSGiGlassFishImpl.java:71)
    at com.sun.enterprise.glassfish.bootstrap.GlassFishMain$Launcher.launch(GlassFishMain.java:117)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at com.sun.enterprise.glassfish.bootstrap.GlassFishMain.main(GlassFishMain.java:97)
    at com.sun.enterprise.glassfish.bootstrap.ASMain.main(ASMain.java:54)
Caused by: javax.servlet.ServletException: Web socket server initialization failed.
    at org.glassfish.tyrus.servlet.TyrusServletFilter.init(TyrusServletFilter.java:135)
    at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:275)
    at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:131)
    ... 45 more
Caused by: javax.websocket.DeploymentException: Found Equivalent paths. Added path: '/first/hello/' is equivalent with '/first/hello/'.
    at org.glassfish.tyrus.core.ErrorCollector.composeComprehensiveException(ErrorCollector.java:83)
    at org.glassfish.tyrus.server.TyrusServerContainer.start(TyrusServerContainer.java:144)
    at org.glassfish.tyrus.servlet.TyrusServletFilter.init(TyrusServletFilter.java:133)
    ... 47 more

情報: Loading application [first] at [/first]
情報: Loading application first done in 920 ms
情報: GlassFish Server Open Source Edition  4.0  (89) startup time : Felix (2,834ms), startup services(5,306ms), total(8,140ms)
情報: Initiating Jersey application, version Jersey: 2.0 2013-05-03 14:50:15...
情報: JMXStartupService has started JMXConnector on JMXService URL service:jmx:rmi://epeapc01-PC:8686/jndi/rmi://epeapc01-PC:8686/jmxrmi
情報: Grizzly Framework 2.3.1 started in: 10ms - bound to [/0.0.0.0:7,676]
情報: Registered com.sun.enterprise.glassfish.bootstrap.osgi.EmbeddedOSGiGlassFishImpl@30c2555a as OSGi service registration: org.apache.felix.framework.ServiceRegistrationImpl@19bd20.

WebSocketを使ってみる。その4

続けて@PathParamを試してみる。
APIによると
ServerEndpointに{パラメータ}で指定した値とバインドしてくれるらしい。

変数として使える型はStringかプリミティブOrそのボクシング型とのこと。

ただし、パラメータがデコードできなかったらエラーハンドラーを呼び出すらしい。

Java (zip:paramBindTest)

import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/hello/{hoge}/{fuga}")
public class HelloWorld {

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

    @OnMessage
    public void onMessage(String message,
           @PathParam("hoge") Integer hoge,@PathParam("fuga") String fuga) {
        System.out.println("message[" + message + "]");
        System.out.println("hoge[" + hoge + "]");
        System.out.println("fuga[" + fuga + "]");
        for (Session s : sessions) {
            s.getAsyncRemote().sendText(message);
        }
    }

JS

            $(document).ready(function(){
                var host="ws://localhost:8080/first/hello/12/ああ";
                socket = new WebSocket(host);

                socket.onmessage = function(message){
                    $('#log').append(message.data + "<br/>");
                }

結果

情報: 開始します:8ca15fe1-a79d-420e-afc6-3c5ab18a75ec
情報: message[あsdf]
情報: hoge[12] <- ボクシング型にバインドされている
情報: fuga[ああ] <- 日本語も素直にバインドされる模様

日本語もそのままうまくバインドされた。

ためしにIntegerの部分を少数にしたらNumberFormatExceptionをチェーンしたDecodeExceptionが発生。このケースは切り分け難しくない。

情報: エラーです:9366a9f2-2b24-4be3-8897-40427563d56e
重大: javax.websocket.DecodeException: Decoding failed
    at org.glassfish.tyrus.core.PrimitiveDecoders$IntegerDecoder.decode(PrimitiveDecoders.java:165)
    at org.glassfish.tyrus.core.PrimitiveDecoders$IntegerDecoder.decode(PrimitiveDecoders.java:157)
    at org.glassfish.tyrus.core.AnnotatedEndpoint$2.value(AnnotatedEndpoint.java:362)
    at org.glassfish.tyrus.core.AnnotatedEndpoint.callMethod(AnnotatedEndpoint.java:428)
    at org.glassfish.tyrus.core.AnnotatedEndpoint.access$100(AnnotatedEndpoint.java:83)
    at org.glassfish.tyrus.core.AnnotatedEndpoint$WholeHandler$1.onMessage(AnnotatedEndpoint.java:518)
    at org.glassfish.tyrus.core.SessionImpl.notifyMessageHandlers(SessionImpl.java:389)
    at org.glassfish.tyrus.core.EndpointWrapper.onMessage(EndpointWrapper.java:495)
    at org.glassfish.tyrus.server.TyrusEndpoint.onMessage(TyrusEndpoint.java:174)
    at org.glassfish.tyrus.websockets.DefaultWebSocket.onMessage(DefaultWebSocket.java:156)
    at org.glassfish.tyrus.websockets.frametypes.TextFrameType.respond(TextFrameType.java:66)
    at org.glassfish.tyrus.websockets.DataFrame.respond(DataFrame.java:102)
    at org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler.onDataAvailable(TyrusHttpUpgradeHandler.java:113)
    at org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.processDataAvailable(InputBuffer.java:488)
    at org.apache.catalina.connector.InputBuffer$ReadHandlerImpl.onDataAvailable(InputBuffer.java:453)
    at org.glassfish.grizzly.http.io.InputBuffer.append(InputBuffer.java:855)
    at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:222)
    at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:288)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:206)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:136)
    at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:114)
    at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:838)
    at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:113)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:115)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:55)
    at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:135)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:564)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:544)
    at java.lang.Thread.run(Thread.java:724)
Caused by: java.lang.NumberFormatException: For input string: "1.2"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:492)
    at java.lang.Integer.valueOf(Integer.java:582)
    at org.glassfish.tyrus.core.PrimitiveDecoders$IntegerDecoder.decode(PrimitiveDecoders.java:163)
    ... 30 more

 

nullとした場合も素直にエラー

var host="ws://localhost:8080/first/hello/null/ああ";
->
Caused by: java.lang.NumberFormatException: For input string: "null"

空文字だと、予想外に素直なエラー

var host="ws://localhost:8080/first/hello//ああ";
->
Caused by: java.lang.NumberFormatException: For input string: ""

 

最後の文字列が空文字だと空文字としてバインドされる。

var host="ws://localhost:8080/first/hello/12/";
->
情報: 開始します:75b4bc74-30e9-4a69-8af3-07ed67e6a296
情報: message[あ]
情報: hoge[12]
情報: fuga[]

/{空文字}/{数字}の順でも動いた。(//となるので環境次第では不具合が出る??

@ServerEndpoint("/hello/{fuga}/{hoge}")と最初を文字列にする
->
var host="ws://localhost:8080/first/hello//12";
->
情報: 開始します:028c5db1-a60f-4d82-970f-8933058b2bfb
情報: message[あああ]
情報: hoge[12]
情報: fuga[]

{変数名}と@PathParamで誤記をするとバインドされない(null)。

@ServerEndpoint("/hello/{fuga}/{hoge}")
->
public void onMessage(String message,
  @PathParam("hoge") Integer hoge,
  @PathParam("fuga2") String fuga)
->
情報: 開始します:49d01de4-4a6b-4b81-8c71-d6d48d9f0600
情報: message[あ]
情報: hoge[12]
情報: fuga[null]

 

URLパラーンの違いによっては呼び出されない。

一つ前のnullがバインドされる事象はバインド対象が不整合でも動いていたけど、そっちが中途半端に動いているだけで、呼び出されないのが正しいのかな??

@ServerEndpoint("/hello/{hoge}") <-fuga2はない
->
public void onMessage(String message,
   @PathParam("hoge") Integer hoge,]
   @PathParam("fuga2") String fuga) {

->
23:27:25.244[23ms][total 23ms] Status: 404[Not Found]
GET http://localhost:8080/first/hello/12/nasi Load Flags[LOAD_BYPASS_CACHE  LOAD_BACKGROUND  INHIBIT_CACHING  ] Content Size[-1] Mime Type[application/x-unknown-content-type]
   Request Headers:
      Host[localhost:8080]
      User-Agent[Mozilla/5.0 (Windows NT 6.1; WOW64; rv:22.0) Gecko/20100101 Firefox/22.0]
      Accept[text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8]
      Accept-Language[ja,en-us;q=0.7,en;q=0.3]
      Accept-Encoding[gzip, deflate]
      Sec-WebSocket-Version[13]
      Origin[http://localhost:8080]
      Sec-WebSocket-Key[gq8u2S2nXT2xpIagf7qPaQ==]
      Cookie[JSESSIONID=e1e94c1a637e58c7ccaa61c6f17e]
      Connection[keep-alive, Upgrade]
      Pragma[no-cache]
      Cache-Control[no-cache]
      Upgrade[websocket]
   Response Headers:
      X-Powered-By[Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition  4.0  Java/Oracle Corporation/1.7)]
      Server[GlassFish Server Open Source Edition  4.0]
      Set-Cookie[JSESSIONID=e2121d0c30e627004af713703b3d; Path=/first; HttpOnly]

WebSocket(JSR 356)を使ってみる。その2

先日に続きOracleのサイトをみながらぼちぼち試していきます。

@OnMessageの戻り値

If the return type of the method annotated with @OnMessage is not void, the WebSocket implementation will send the return value to the other peer.

戻り値付のメソッドの場合、その値をpeerに返すというような事が書いてあるので試してみる。

ソース

@OnMessage
public String onMessage(String message) {
System.out.println(“message[” + message + “]”);
 return message;
}

実際にメッセージを送ったブラウザには戻り値が帰ってきたことを確認できた。

戻り値確認

ただし、開いていた他のブラウザには送信されていない。

別ブラウザへの反映状況

 

peerと書いているだけなので複数に返すのでなく単独のものに返すという動作という認識であっているのかな??

疑問は残るけど、複数ブラウザ(peer)に返すサンプルがあるgetAsyncRemotegetBasicRemote()を試してみる。APIによると非同期か同期かの違いらしい。

 @OnMessageのgetAsyncRemote/getBasicRemote

ソースを書き換えてみたけれども、getAsyncRemotegetBasicRemoteの両方とも同時にalert(“Calling”);まで進む。

RemoteEndpoint.Basicをみると下のように書かれているのでのでJSのAlert文で待ちが入るというわけではないらしい。

The point of completion of the send is defined when all the supplied data has been written to the underlying connection

送信失敗に備えたエラー処理をするためのもので、Endpoint間の同期をとる目的でないということでよいのかな。

(java)

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

@OnMessage
public void onMessage(String message) {
System.out.println(“message[” + message + “]”);
for (Session s : sessions) {
s.getAsyncRemote().sendText(message);
}
}

@OnMessage
public void onMessage(String message) throws IOException {
System.out.println(“message[” + message + “]”);
for (Session s : sessions) {
s.getBasicRemote().sendText(message);
}
}

(JS)

socket.onmessage = function(message){
alert(“Calling”);
$(‘#log’).append(message.data + “<br/>”);
}