投稿者「wpepea」のアーカイブ

Androidの画像フォルダ(res/drawable)

昔のブログから引っ越してきた、2011/07/27当時の記事です

解像度や向きごとに命名規約に従ったフォルダに、それぞれの解像度や向きごと用の画像を入れておけば表示時に自動で切り替えてくれる。
(参考:日本語
(参考:英語=本家

注意しなくてはいけないのは、読み込まれるリソースはフォルダの一階層目だけ。
つまり、下のように目的に応じたフォルダ分けができない・・・・
res/drawable/mount/mount01.bmp
res/drawable/river/river01.bmp
(参考:android ml)

別フォルダ管理プラグイン作るか。。。

WebSocketにてJSON形式のデータをやり取りしてみる(Glassfish 4)

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

大まかな動きは以下

  1. HTMLからJSONで入力テキストと現在日時を送信。
  2. GlassfishのWebsocketで受信(&Decode)
  3. 受け取ったデータに情報(とりあえずユーザ情報代わりのセッションID)を付与し繋がっているクライアント(HTML)全体にJSON形式で返信
  4. HTMLで受け取ったデータを表示
(html->glassfishのデータ形式)
{"message":"メッセージ","messageTime":時間(ミリ秒)}

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

斜体は変数

Maven

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

    <dependencies>
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-web-api</artifactId>
            <version>7.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- 8/10追記 javaee-web-apiの中に入っていたもので足りた -->
        <!-- Websocket (JSR-356) -->
        <dependency>
            <groupId>javax.net.websocket</groupId>
            <artifactId>javax.net.websocket-api</artifactId>
            <version>1.0-b06</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>org.glassfish.websocket</groupId>
            <artifactId>websocket-api</artifactId>
            <version>0.2</version>
            <scope>provided</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>

画面

  1. new WebSocket(arg)のargで接続先指定をしてWebSocketオブジェクトを取得する
  2. 接続先は@ServerEndpointをつけたサーバサイドのPOJOに指定したもの。
  3. WebSocketのsendで送信、onmessageで受信
  4. Websocketで送られてきたデータはsocket.onmessageで渡される引数(今回はmessage)のdataに入っている。
  5. 取り出してからの扱いは普通のJSON。
 <!DOCTYPE html>
<html>
<head>
<title>JsonのWebSocketテスト</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script
    src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script type="text/javascript">
    var socket;
    $(document).ready(
            function() {
                var host = "ws://localhost:8080/first/hellojson/";
                socket = new WebSocket(host);

                socket.onmessage = function(message) {
                    try {
                        var wsRes = $.parseJSON(message.data);
                        $('#log').append(
                                wsRes.messageTime +
                                "(送信者=" + wsRes.sessionId + "):" + 
                                wsRes.message + "<br/>");
                    } catch (e) {
                        alert(e);
                        return;
                    }
                }

                $('#send').click(function() {
                    var obj = new Object();
                    obj.message = $('#msg').val();
                    obj.messageTime = new Date().getTime();
                    var jsonString = JSON.stringify(obj);
                    socket.send(jsonString);
                    $('#msg').val('');
                })

            });
</script>
</head>
<body>
    <h1>JsonのWebSocketテスト</h1>
    <div id="log"></div>
    <input id="msg" type="text" />
    <button id="send">送信</button>
</body>
</html>

WebSocket用のPOJO(GlassFish側)

  1. @ServerEndpointをつけたPOJOでWebSocket用クラス(サーバエンドポイント)を作る
  2. パスはvalueで指定。自分の環境ではコンテキストがfirstなのでfirst/hellojson/がサーバエンドポイントのアドレス
  3. @onMessageが繋がっている(クライアントサイドの)エンドポイントから呼ばれたときに動く個所
  4. decodersでクラスを指定すると、やってきたメッセージが指定されたクラスでデコードされて渡される。
  5. encodersでクラスを指定すると、指定したクラスでエンコードしたデータを、繋がっているエンドポイントに渡せる。
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import jp.co.epea.first.json.TestData;
import jp.co.epea.first.json.TestDecoder;
import jp.co.epea.first.json.TestEncoder;

@ServerEndpoint(value = "/hellojson/", 
decoders = { TestDecoder.class }, encoders = { TestEncoder.class })
public class HelloJson {

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

    @OnMessage
    public void onMessage(TestData data, Session sess) {
        System.out.println("Call HelloJson:data[" + data + "]");
        data.setSessionId(sess.getId());
        for (Session s : sessions) {
            s.getAsyncRemote().sendObject(data);
        }
    }

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

decodersのクラス

  1. javax.websocket.DecoderのText<T>をimplementsする。(Text形式であるJSONの場合)
  2. <T>に入るのはエンドポイントのOnMessageに渡したい型
  3. willDecodeメソッドがデコードできるかどうか判定。
  4. バリデーションとかはwillDecodeでなくOnMessageでやるべきがどうかは未調査。(未実装)
  5. decodeメソッドで返却するオブジェクトを生成しreturnする。
  6. JSON Processingを使っているが何を使ってObject化してもOK
import java.io.StringReader;

import javax.json.Json;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;

public class TestDecoder implements Decoder.Text<TestData> {

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

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

@Override
public TestData decode(String inputString) throws DecodeException {
JsonObject jsonObject = Json.createReader(new StringReader(inputString)).readObject();
return  new TestData(jsonObject);
}

/*
* Answer whether the given String can be decoded into an object of type
* T.だそう 入力チェックもここでやるのがよい?
*/
@Override
public boolean willDecode(String inputString) {
try {
Json.createReader(new StringReader(inputString)).readObject();
return true;
} catch (JsonException ex) {
ex.printStackTrace();
return false;
}
}
}

encodersのクラス

  1. javax.websocket.EncoderのText<T>をimplementsする。(Text形式であるJSONの場合)
  2. <T>に入るのはエンドポイントのOnMessageで呼ぶsendObjectの引数(返す元ネタ)
  3. encodeメソッドで返却するStringを生成しreturnする。
  4. JSON Processingを使っているが何を使ってString化してもOK
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.json.Json;
import javax.json.JsonObject;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;

public class TestEncoder implements Encoder.Text {

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

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

	@Override
	public String encode(TestData paramData) throws EncodeException {
		JsonObject model = Json.createObjectBuilder()
				   .add("message", paramData.getMessage())
				   .add("sessionId",paramData.getSessionId())
				   .add("messageTime", formatDate(paramData.getMessageTime()))
				   .build();
		return model.toString();
	}

	private String formatDate(long millDate){
		return new SimpleDateFormat("HH:mm:ss").format(new Date(millDate));
	}

}

データクラス

import javax.json.JsonObject;

public class TestData {

    // メッセージ
    private String message;
    // 送信日時
    private long messageTime;
    // sessionId(ユーザ名の変わり)
    private String sessionId;

    public TestData(JsonObject jsonObject) {
        if(jsonObject.containsKey("message")){
            this.message = jsonObject.getString("message");
        }
        if(jsonObject.containsKey("messageTime")){
            this.messageTime = jsonObject.getJsonNumber("messageTime").longValue();
        }
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    String getMessage() {
        return message;
    }

    long getMessageTime() {
        return messageTime;
    }

    String getSessionId() {
        return sessionId;
    }
}

github[https://github.com/epea/test01/tree/WS_JSON]

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.

アクセスログに”%E2%80%8B”

アクセスログに”%E2%80%8B”と”zero width space(見えない空白みたいなもの)”が付いたログが残っていた。

(略) "GET /?%E2%80%8Bp=2%E2%80%8B23 HTTP/1.1" 200 63979 (略)

しかもステータス200を返しているしなんだろうと思い調べてみる。

結論から言うと、ウェブマスター ツールから自分でFetch as GoogleをたたいたときにコピペしたURLに”zero width space”が混ざっていたため、それがgoogleに取り込まれた模様。

WordPressの場合は”zero width space”が無いものと扱われるみたいなのでとりあえず動作はする。(セキュリティ的なとこは見ていない)

リンクを画面からコピペしたときに混ざったと思うけど軽く探した範囲では具体的にどこをコピペしたときかは見つけられなかった。

 

なお、Googleで実際に以下のアドレスをたたいたところ両方がインデックスされている。

https://www.rocher.kyoto.jp/araburu/?%E2%80%8Bp=2%E2%80%8B23
https://www.rocher.kyoto.jp/araburu/?p=223

 

 

 

JSON Processing(JSR-353)に寄り道1

Websocketで複数のデータを送ろうとした際に、JavaEE7でJSON Processingが出てたのを思い出したので触ってみた。

ArunさんのブログにあるようにJSONとObject間のデータバインディングまではやってくれない。

(Note, binding JSON to Java objects and vice versa is not part of the scope of this JSR.)

想定用途(?)を探して見つけたブログによるとStaxでDom操作する感覚で使えるよとのこと。

->JCPにしっかり書いてあった。(2.1)

 

利用設定(Maven)

pomに依存関係を追加

        <!-- 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>

試したソース

import javax.json.Json;
import javax.json.JsonObject;
import javax.json.stream.JsonParser;

public class FirstJson01 {
    public static void main(String[] args) {
        System.out.println("生成");
        JsonObject model = Json.createObjectBuilder()
                   .add("firstName", "Duke")
                   .add("lastName", "Java")
                   .add("age", 18)
                   .add("streetAddress", "100 Internet Dr")
                   .add("city", "JavaTown")
                   .add("state", "JA")
                   .add("postalCode", "12345")
                   .add("phoneNumbers", Json.createArrayBuilder()
                      .add(Json.createObjectBuilder()
                         .add("type", "mobile")
                         .add("number", "111-111-1111"))
                      .add(Json.createObjectBuilder()
                         .add("type", "home")
                         .add("number", "222-222-2222")))
                   .build();

        String text = model.toString();
        System.out.println(text);

        System.out.println("解析");
        StringReader sr = new StringReader(text);
        JsonParser parser = Json.createParser(sr);
        while(parser.hasNext()){
            JsonParser.Event event = parser.next();
            System.out.println(event);
        }
    }
}

Eventの種類はSTART_OBJECT/END_OBJECT、START_ARRAY/END_ARRAY、KEY_NAME、VALUE_STRING、VALUE_NUMBER、VALUE_TRUE、VALUE_FALSE、VALUE_NULL。

 

 

 

M2E(Maven)のproperty外部化

以前JavaFXのMaven設定を行った際にjfxrt.jarへのパスはpomに直接記載していた。別端末(別のパス)で使用しようとしたときに外部化した際の手順。

 

設定を記載するsetting.xmlを作成。(proxyを使う際等に既に作っていることも多い。)

Defaultは下。(自分はEclipseの設定->Maven->ユーザ設定から変更)

    The Maven install: $M2_HOME/conf/settings.xml
    A user's install: ${user.home}/.m2/settings.xml

作成した内容は以下。

profile -> propertiesの下に、キー、値を指定する。

(参考サイト

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository/>
  <interactiveMode/>
  <usePluginRegistry/>
  <offline/>
  <pluginGroups/>
  <servers/>
  <mirrors/>
  <proxies/>
    <profiles>
        <profile>
            <id>javafx2</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <javafx2.home>C:\develop\pleiades43\pleiades\java\7\lib\jfxrt.jar</javafx2.home>
            </properties>
        </profile>
    </profiles>
  <activeProfiles/>
</settings>

pomで直接記載していた個所が変数で指定できるようになっている。

 

     <dependencies>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>javafx</artifactId>
            <version>2.0</version>
            <scope>system</scope>
            <systemPath>${javafx2.home}</systemPath>
        </dependency>

 

今回参考にさせてもらったビルドまで記載されているブログ

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の変更をトリガーにした通知機能をうまく使っているみたい。

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

spam対策

ここ数日198.245.49.39と137.175.15.131から数時間おきにspamコメントが寄せられるうになってきた。

ipは決まっているのでとりあえずiptablesではじくように設定。

iptables -I INPUT -s 198.245.49.39 -j DROP
iptables -I INPUT -s 137.175.15.131 -j DROP
/etc/rc.d/init.d/iptables save
(各IPからのパケットをはじく設定&保存。)

今のところは、他のIPからのスパムはないから出てきたベースで拒否していけばいいけど頻度増えてきたらAkismetとかのプラグインではじいた方がいいのかな。

スタックトレースを頼りに海外から来てくれている人もいるので国でばっさりと絞りたくはないし。

(2013/8/6までにスパムが来たIP)

216.244.85.234
205.164.24.90
58.22.70.108
137.175.15.134
185.25.48.24
198.2.200.9
137.175.105.39
137.175.118.101
137.175.15.133
137.175.68.241
137.175.105.38
114.251.150.133
123.184.133.84
163.125.181.151
137.175.15.132
137.175.118.100
137.175.68.178
71.61.84.216
117.27.138.148
137.175.118.99
198.27.82.114
137.175.15.131
198.245.49.39
198.100.144.195