カテゴリー別アーカイブ: JavaFX

JavaFXの非同期処理中に画面を閉じたら。。。。

バックグラウンドでタスクを動かしている状態でJavaFXの画面を閉じる。

閉じる

すると、裏で走っていたTaskが終わった後もなんか生き残っている。

残骸

 

環境は1.7.0_45に入っているjfxrt.jarでEclipse実行。

ソースは下の感じ。

    @FXML
    private void handleExecAction(ActionEvent event) {
        this.logger.info("start");
        try {
            analizeTask = getAnalizeTask();
            pgBar.progressProperty().unbind();
            pgBar.progressProperty().bind(analizeTask.progressProperty());
            pgLabel.textProperty().bind(analizeTask.messageProperty());
            final ExecutorService exe = Executors.newSingleThreadExecutor();
            exe.submit(analizeTask);

    private Task<String> getAnalizeTask() {
        return new Task<String>() {
            @Override
            protected String call() throws Exception {
                logger.info("start");
                try {

 

とりあえずは、

  1. Applicationクラスにコントローラー持たしておいて
  2. stopメソッドからコントローラーの終了メソッドを呼んで
  3. その中からTaskのcancel呼んで
  4. Taskのキャンセルイベントのハンドラー内で強制終了かけてやる

で回避。

Task内の終了処理をきちんとやろうとするといろいろはまりそうですが。

 

1

    private FXMLController controller;

    @Override
    public void start(Stage stage) throws Exception {

        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Scene.fxml"));
        Parent root = (Parent) fxmlLoader.load();
        controller = (FXMLController) fxmlLoader.getController();

2

    @Override
    public void stop() throws Exception {
        controller.destroy();
    }

3

public class FXMLController implements Initializable {    
    public void destroy() {
        if (analizeTask != null)
            analizeTask.cancel();
    }

4

            analizeTask.addEventHandler(WorkerStateEvent.WORKER_STATE_CANCELLED,
                    new EventHandler<WorkerStateEvent>() {
                        @Override
                        public void handle(WorkerStateEvent t) {
                            exe.shutdown();
                        }
                    });

JavaFXでFXMLを使った際のApplicationからcontrollerへのアクセス

FXMLクラスで”fx:controller”(シーンビルダーだとデフォルトで入っているはず)を指定しておいてアプリケーションクラスでFXMLLoader#getControllerを呼べばコントローラを取得できるのであとはごにょごにょすればOK。

注意点は、FXMLLoader#loadを呼んだ後でないとFXMLLoader#getControllerが取得できない(nullになる)こと。

 

(FXML)

<AnchorPane id="AnchorPane" prefHeight="427.0" prefWidth="423.5" 
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" 
fx:controller="jp.co.epea.FXMLController">
    <children>
        <VBox fx:id="vBox"

 

(Applicationクラス)

public class MainApp extends Application {

  private FXMLController controller;

    @Override
    public void start(Stage stage) throws Exception {

        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Scene.fxml"));
        Parent root = (Parent)fxmlLoader.load();
        controller = (FXMLController) fxmlLoader.getController();

 

 

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とやり取りしてみる

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

続き。

JavaFXとWebsocketを連携してみる(On Glassfish4.0) その4

WebSocketで受け取ったデータをJavaFXのラベルに設定する方法とりあえず動いた。

大まかなフローは、

  1. WebSocket起動
  2. Websocketの@OnMessageでデータ受信
  3. @OnMessageの中でPlatform.runLaterを使ってJavaFXのスレッドを起動(コマンドの依頼??)
  4. Platform.runLaterの中でLabel#setTextを呼び出して値を更新

(ボタンイベントからWebsocketの起動周り)

public class SampleController {

    @FXML
    private Label testLabel;

    @FXML
    private void handleButtonAction(ActionEvent event) {
        kickWebSocket();
    }

    private void kickWebSocket() {
        try {
            CountDownLatch messageLatch = new CountDownLatch(1);
            URI clientURI = new URI("ws://localhost:8080/first/hello/");
            ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager
                    .createClient();
            cliContainer.connectToServer(new WSClient(testLabel), clientURI);
            messageLatch.await(1, TimeUnit.SECONDS);
        } catch (DeploymentException | URISyntaxException
                | InterruptedException ex) {
            Logger.getLogger(SampleController.class.getName()).log(
                    Level.SEVERE, null, ex);
        }
    }
}

(OnMeessageから値の設定)

@ClientEndpoint
public class WSClient {

    private Label testLabel;

    public WSClient(Label testLabel) {
        this.testLabel = testLabel;
    }

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("Connection had opened.");
    }

    @OnMessage
    public void onMessage(String message) {
        if (message.length() == 0) {
            return;
        }
        final String channeled = message;
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                testLabel.setText(channeled);
            };
        });
    }

    @OnClose
    public void closeConnection(Session session) {
        System.out.println("Connection had closed.");
    }

    @OnError
    public void error(Session sess, Throwable t) {
        System.out.println("エラーです:" + sess.getId());
        t.printStackTrace();
    }
}

(一応サーバ側)

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

    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);
        }
    }

    @OnOpen
    public void open(Session sess) {
        System.out.println("開始します:" + sess.getId());
        sessions.add(sess);
    }

    @OnClose
    public void close(Session sess) {
        System.out.println("終了します:" + sess.getId());
        sessions.remove(sess);
    }

    @OnError
    public void error(Session sess,Throwable t) {
        System.out.println("エラーです:" + sess.getId());
        t.printStackTrace();
    }
}

まだ、確認できていないのがこの実装で順序の保障できているかどうか。

(実装的にはWebSocketボタン押した回数立ち上がったりと適当な部分はありますが。)

JavaFXとWebsocketを連携してみる(On Glassfish4.0) その3

時系列のままに書いている間が漂ってきましたが。。。。

最初はボタンイベント(handleButtonAction)からjavafx.concurrent.Serviceをキックしていた。ただGrizzlyの中でWebsocket用のスレッドが立てられているので、「Websoketスレッド起動の処理」をスレッド化しても多分意味がないことに気づいた。少なくとも今問題になっている「Not on FX application thread」というやつの対策にはならなそう。

ボタンイベントから直接Websocket用のスレッド起動を行うように変更。

public class SampleController implements Initializable {

    @FXML
    private TableView<RowData> table;

    @FXML
    private TableColumn<RowData, String> column;

    @FXML
    private void handleButtonAction(ActionEvent event) {
        try {
            CountDownLatch messageLatch = new CountDownLatch(1);
            URI clientURI = new URI("ws://localhost:8080/first/hello/");
            ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager
                    .createClient();
            cliContainer.connectToServer(new WSClient(table), clientURI);
            messageLatch.await(1, TimeUnit.SECONDS);
        } catch (DeploymentException | URISyntaxException
                | InterruptedException ex) {
            Logger.getLogger(SampleController.class.getName()).log(
                    Level.SEVERE, null, ex);
        }
    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        table.setEditable(true);
        column.setResizable(true);
        column.setCellValueFactory(new PropertyValueFactory<RowData, String>(
                "message"));
    }
}

予定通りWebsocket(のスレッド)はキックされている。

a

 

@OnMessageでLabelの値に反映させる方法は調査中。 -> 動いた

それを皮切りに”ObservableList経由で反映しているTableView”以外もやっつけるつもりだったけどそもそもできるか不安になってきた。。。

 

JavaFXとWebsocketを連携してみる(On Glassfish4.0) その2

前回からスレッド周りの制約事項にかかってうまくいっていないけど状況の整理。

やろうとしていることは、前回は表の中にWebsockeで取得したデータを反映しようとしていたものを、Labelに変更して表示しようというもの。

1

起きているエラーはJavaFXのコンポーネントをJavaFX配下以外のスレッドで呼んではいけないというもの。

スタックトレースは以下。

java.lang.IllegalStateException: Not on FX application thread; currentThread = Grizzly-worker(1)
エラーです:e9e8fea3-96b6-46be-ad8e-a393e1499784
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:237)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:397)
    at javafx.scene.Parent$1.onProposedChange(Parent.java:245)
    at com.sun.javafx.collections.VetoableObservableList.setAll(VetoableObservableList.java:90)
    at com.sun.javafx.collections.ObservableListWrapper.setAll(ObservableListWrapper.java:314)
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:602)
    at com.sun.javafx.scene.control.skin.LabeledSkinBase.handleControlPropertyChanged(LabeledSkinBase.java:209)
    at com.sun.javafx.scene.control.skin.SkinBase$3.changed(SkinBase.java:282)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:107)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:196)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
    at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:121)
    at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:128)
    at javafx.beans.property.StringPropertyBase.access$100(StringPropertyBase.java:67)
    at javafx.beans.property.StringPropertyBase$Listener.invalidated(StringPropertyBase.java:236)
    at com.sun.javafx.binding.ExpressionHelper$SingleInvalidation.fireValueChangedEvent(ExpressionHelper.java:155)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:100)
    at javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:121)
    at javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:128)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:161)
    at javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:67)
    at jp.co.epea.firstfxclient.ObjData.setDataValue(ObjData.java:26)
    at jp.co.epea.firstfxclient.WSClient.onMessage(WSClient.java:30)
    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 org.glassfish.tyrus.core.AnnotatedEndpoint.callMethod(AnnotatedEndpoint.java:431)
    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:391)
    at org.glassfish.tyrus.core.EndpointWrapper.onMessage(EndpointWrapper.java:498)
    at org.glassfish.tyrus.container.grizzly.GrizzlyClientSocket.onMessage(GrizzlyClientSocket.java:393)
    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.container.grizzly.WebSocketFilter.handleRead(WebSocketFilter.java:300)
    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:837)
    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:565)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545)
    at java.lang.Thread.run(Thread.java:724)

 

現在の書き方(ソース)でかかっている場所は、ClientManager#connectToServerでGrizzlyのスレッドがたっていて、

        @Override
        protected Task createTask() {
            Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() throws Exception {

                    try {
                        URI clientURI = new URI("ws://localhost:8080/first/hello/");
                        ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient();
                        cliContainer.connectToServer(new WSClient(data), clientURI);

                    } catch (DeploymentException  ex) {
                        Logger.getLogger(SampleController.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    return null;
                }
            };
            return task;
        }

その中で動いているOnmessegeで値を書き換えようとしていること

    ObjData data;

    @OnMessage
    public void onMessage(String message) {
        if (message.length() == 0) {
            return;
        }
        System.out.println(data.getDataValue());
        data.setDataValue(message);//下の自分で作ったメソッド
        System.out.println(data.getDataValue());
    }

// ObjDataの中
    private SimpleStringProperty dataValue;
    public void setDataValue(String dataValue) {
        dataValueProperty().set(dataValue);
    }

改めて前回サンプルにしたソースを見直すとObservableListの変更をトリガーにした通知機能をうまく使っているみたい。

なお、自前でスレッドを管理している場合のドキュメントはこちら

JavaFXソース、JavaDoc添付(Eclipse 4.3 Kepler)

JavaFXのソースとJavaDocのアタッチのをしていなく何かと不便なので添付する。

そもそもPleiadesに入っていたJava 7にくっついていたもの程度の意識なのでまずはこちらのソースを使ってバージョンを調べる。

javafx.runtime.version: 2.2.25-b15

だそう。

まずJavaDocから入れようとダウンロードサイトを開くとjavafx-2_2_25-apidocs.zipとbuild番号が付いていない。ダウンロードしてみて、レポジトリのチェンジログと比べるとb19の変更が反映されているので時系列に差がある模様。とはいえ、リリースノートが下な感じで、大きな修正は言ってもいないのでそのまま突き進む。

The full version string for this update release is 1.7.0_25-b15 (where "b" means "build") except for Windows on which it is 1.7.0_25-b17. The version number is 7u25.

 

疲れたので、以下作業予定で今日は挫折。 (7/22 再開)

ソースダウンロード(そもそもMercurial入れなければいけないので入れる) -> リンク先の右上にダウンロード用リンクあったので、zipでダウンロード。

ソースとJavaDocを適宜フォルダを作って配備。(C:\dev\attachment\javafx2.2.25)

pomの修正(Localレポジトリを作成しないときれいに添付できないかもしれない。参照先)

プロパティー->Javaのビルドパス->ライブラリー->maven依存関係->jafxt.jarのJavadoc添付、ソース添付から設定することで妥協。

 

JavaFX&Websocket連携(エラーパターン)

対向のサーバが落ちているとき。

素直にハンドシェイクできないという。まぁ、そのまんま。

7 20, 2013 5:43:29 午後 jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1 call
SEVERE: null
javax.websocket.DeploymentException: Handshake response not received.
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:300)
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:172)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:65)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:1)
    at javafx.concurrent.Task$TaskCallable.call(Task.java:1259)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)

 

URI clientURI = new URI(“ws://localhost:8080/first/hellohoge/”);

みたいにURLが間違っている場合。

Handshake errorというやつとスタックとレースにレスポンスコードが返ってくる。

この例だと存在しないパスなので404が返っているけど、WebsocketじゃないただのHTMLだとUpdate Protocolの時に失敗しているので(?)200が返ってくる。

なお、パスの指定時は最後の”/(スラッシュ)”も厳密に見る模様。

7 20, 2013 8:46:51 午後 jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1 call
SEVERE: null
javax.websocket.DeploymentException: Handshake error.
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:285)
    at org.glassfish.tyrus.client.ClientManager.connectToServer(ClientManager.java:172)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:65)
    at jp.co.epea.firstfxclient.SampleController$TwitterCheckService$1.call(SampleController.java:1)
    at javafx.concurrent.Task$TaskCallable.call(Task.java:1259)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)
Caused by: org.glassfish.tyrus.websockets.HandshakeException: Response code was not 101: 404
    at org.glassfish.tyrus.websockets.HandShake.validateServerResponse(HandShake.java:314)
    at org.glassfish.tyrus.websockets.draft06.HandShake06.validateServerResponse(HandShake06.java:98)
    at org.glassfish.tyrus.container.grizzly.WebSocketFilter.handleClientHandShake(WebSocketFilter.java:368)
    at org.glassfish.tyrus.container.grizzly.WebSocketFilter.handleHandshake(WebSocketFilter.java:353)
    at org.glassfish.tyrus.container.grizzly.WebSocketFilter.handleRead(WebSocketFilter.java:274)
    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:837)
    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:565)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:545)
    ... 1 more

Connection had closed.

 

ちなみに、エラーログをはいているのはSampleController#createTaskの中にあるキャッチ節。

        @Override
        protected Task createTask() {
            Task<Void> task = new Task<Void>() {
                @Override
                protected Void call() throws Exception {
                    messageLatch = new CountDownLatch(1);
                    try {
                        URI clientURI = new URI("ws://localhost:8080/first/hellohoge/");
                        ClientManager cliContainer = org.glassfish.tyrus.client.ClientManager.createClient();
                        cliContainer.connectToServer(new TwitterClient(table), clientURI);
                        messageLatch.await(1, TimeUnit.SECONDS);
                    } catch (DeploymentException | URISyntaxException | InterruptedException ex) {
                        Logger.getLogger(SampleController.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    return null;
                }
            };
            return task;
        }

下の@OnErrorは通らずに、@OnClose(前のログだと緑字部分)で終わるので注意。

    @OnClose
    public void closeConnection(Session session) {
        System.out.println("Connection had closed.");
    }

    @OnError
    public void onError(Session session,Throwable t) {
        System.out.println("error");
        t.printStackTrace();
    }

javafx.concurrent.Taskの使い方次第でもっとハンドリングできそうな気がする。
とりあえず参考になりそうなサイト