Posts

Dockerで始めるVMを利用した開発

結構前からDockerの事を聞いていてそれは仮想化技術だと認識していました。 なんで今まで触ってこなかったのかというと仮想化環境を作るにはそれなりの マシンスペックが必要なのだろうと。つまり貧弱なマシンを使っている僕には関係ない。 自宅のMac Book Proが壊れて新しくなったり、 会社のPCのスペックが上がったりしたのでこれは触りどきかと思って今手をつけてみたわけです。 というわけでまさに触り始めなわけで開発まではたどり着いていません。タイトル嘘という方向で。

最初に何をしたかというと、DockerToolBoxというやつをインストールしました。 Homebrewでも入れられるみたいですがどこかのサイトにHomebrewで入れるとなかなか最新にならないよという 至極まっとうな記載があったのでひとまず最新がいいなあと思い、インストールモジュールをダウンロードしてみました。

これ、インストールすると目に見えて分かるのは3つのソフトがインストールされるということです。

  • Docker Quickstart Terminal
  • Kitematic (Beta)
  • VirtualBox

もしかしたら見えないところに他のソフトがインストールされているのかもしれませんが、 まだよくわかっていません。

最初にDocker Quickstart Terminalを起動します。 これはOSXのターミナルが起動します。ここの中でCUIで操作するようです。 で、ちょっとハマったのは起動したターミナルに別タブを開いてそこでDockerの起動とかしようとしても 仮想マシンに接続できないようで理解するまで時間がかかりました。

Docker Quickstart Terminalを起動すると仮想マシンがdefaultという名前で起動します。 これはVirtualBoxを起動するとわかります。 今のところVirtualBoxを使って何かするということはなさそうだという理解です。 このdefaultの仮想マシンですが、間違ってログアウトしてしまったらログインのIDとパスワードがわからなくて難儀しました。 どうやらCore Linuxというものを使っているらしくそれのデフォルトのIDとパスワードでログインできるようです。 こういうDockerとは直接関係ない機能を試してみたくなるところが僕の悪いところで、 Dockerそのものをまだちゃんと触れていない状態です。

Kitematicはいろいろな人がアップしたDockerイメージが登録されているDockerHubというところへの アクセスをGUI経由でできるソフトのようです。 Dockerイメージとかもうよくわからないので、この本を買ってちゃんと勉強することにしました。



まだ最初の方しか読んでいませんが、 ざっくり言うとMavenみたいな感じですね!多分。

というわけでこれからしばらくはDockerを使ってみたいと思っています。

JdbcRealm with WildFly 9.0.1.Final

以前、「WildFlyでJdbcRealm」 という記事を書きました。 これを現在インストールしている9.0.1.Final上で設定したところ、認証がうまく行われないことがわかりました。 大枠の変更はないのですが、DBに登録するパスワードのハッシュ文字列が当時とは異なる値である必要があったので、 忘れないようにメモしておきます。

差分

WildFly 8.0.0.Finalの時の設定:

<security-domain name="app" cache-type="default">
    <authentication>
        <login-module name="app_auth" code="Database" flag="required">
            <module-option name="dsJndiName" value="java:jboss/datasources/ExampleDS"/>
            <module-option name="principalsQuery" value="SELECT PASSWORD FROM ACCOUNTS WHERE EMAIL = ?"/>
            <module-option name="rolesQuery" value="SELECT r.ROLENAME, 'Roles' FROM ROLES r, ACCOUNTS a WHERE r.ACCOUNTID = a.ACCOUNTID AND a.EMAIL = ?"/>
            <module-option name="hashAlgorithm" value="SHA-256"/>
            <module-option name="hashEncoding" value="HEX"/>
        </login-module>
    </authentication>
</security-domain>

WildFly 9.0.1.Finalの設定:

<security-domain name="app" cache-type="default">
    <authentication>
        <login-module name="app_auth" code="Database" flag="required">
            <module-option name="dsJndiName" value="java:jboss/datasources/ExampleDS"/>
            <module-option name="principalsQuery" value="SELECT PASSWORD FROM ACCOUNTS WHERE EMAIL = ?"/>
            <module-option name="rolesQuery" value="SELECT r.ROLENAME, 'Roles' FROM ROLES r, ACCOUNTS a WHERE r.ACCOUNTID = a.ACCOUNTID AND a.EMAIL = ?"/>
            <module-option name="hashAlgorithm" value="SHA-256"/>
            <module-option name="hashEncoding" value="base64"/>
        </login-module>
    </authentication>
</security-domain>

違いは module-option の hashEncoding の値。 8.0.0.Finalの時はHEXであり、9.0.1.Finalではbase64にしています。 これはパスワードのハッシュエンコーディングの形式を指定している部分なのですが、 9.0.1.FinalではHEXを認識していない模様。 なので、DBに登録するパスワードのハッシュ文字列も設定に合わせてHEXからbase64に変更します。

パスワードのハッシュ文字列生成方法

WildFlyにはbase64のハッシュ文字列を生成するモジュールが入っているようです。 以下のコマンドで指定文字列のBase64ハッシュ値を取得することができます。

java -cp $JBOSS_HOME/modules/system/layers/base/org/picketbox/main/picketbox-4.9.2.Final.jar org.jboss.security.Base64Encoder [任意文字列] SHA-256

参考サイト

Server Sent Events

これは JavaEE Advent Calendar 2015 の20日目の記事です。
昨日は@yumix_hさんの「「帰ってきたGlassFish Users Group Japan勉強会」の未発表資料」でした。
明日は@emaggameさんです。

Server Sent Eventsとは

Server Sent Events (SSE) はサーバから送られたイベントという意味の通り、push型のデータ通信を行うことができます。 これはHTML5で追加された新機能です。 同じくpush型のデータ通信を行う方法としてWebsocketがありますが、WebsocketがHTTPとは別のプロトコルで通信をするのに対し、 SSEではHTTPプロトコルを利用します。そのため、既存のHTTPを利用した通信との互換性が高いというメリットがある反面、 Websocketのような双方向の通信を行うことはできません。 HTTPプロトコルでpush通信を実現するため、SSEではサーバからのレスポンスを受けても接続を終了せずに継続させます。 こうすることで、サーバ側からのデータを継続して受信することを実現します。 このようにSSEはHTTPプロトコルで接続を行うのですが、クライアントがSSEだと認識できるデータを送ってもらう必要があります。 そこで、サーバはMIMEタイプにtext/event-streamを設定する必要があります。

JavaEE8にSSEのサポートが入るようですが、一足先にJAX-RSのRIであるJerseyでこの機能を試すことができます。

Server Sent Eventsを試す

今回実行した環境は下記の通りです。

  • OS: Mac OSX 10.11.1
  • Java: Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
  • APサーバ: GlasshFish-4.1.1
  • ブラウザ: Safari-9.0.1

実際のコードはGithubにあるので、 コードを見れば分かる方は以降の実装の説明を読むより、 そちらを見ていただいた方が早いかと思います。

sandbox/sse-example

実装の説明

Mavenを利用しているので、最初に下記のDependencyを追加します。 2015/12/10時点のMaven Centralの最新版は2.22.1のようです。

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-sse</artifactId>
  <version>2.22.1</version>
</dependency>

サーバ側のリソースはMIMEタイプにtext/event-streamを設定する他に、 org.glassfish.jersey.media.sse.EventOutputを返却する必要があります。

@GET
@Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents()
{
    ...
}

EventOutputを返却するだけだと、クライアントとの接続が確立しているだけの状態なので、 実際にクライアントに送信するデータを書き込む必要があります。 書き込みはEventOutput#write(OutboundEvent)で行います。 単純には下記のような実装になります。

final EventOutput eventOutput = new EventOutput();
final OutboundEvent.Builder builder = new OutboundEvent.Builder();
builder.name("message-to-client");
builder.data(String.class, "Hello world !");
eventOutput.write(builder.build());

builder.name(...)で指定している文字列はクライアント側でイベントのマッピングをするために利用します。

今回クライアントはJavascriptにします。 JavascriptでSSEを利用するにはEventSourceクラスを利用します。 EventSourceを利用した実装は下記のようになります。

var eventList = document.getElementById("eventList");
var eventSource = new EventSource("http://localhost:8080/sse-example/api/sse/events");
eventSource.addEventListener("message-to-client", function (e) {
    var newElement = document.createElement("li");
    newElement.innerHTML = "message: " + e.data;
    eventList.appendChild(newElement);
});

EventSourceコンストラクタの引数でAPIエンドポイントを指定します。 addEventListenerでサーバからのイベントをハンドリングします。 この時、リスナーに設定するイベント名として、サーバ側コードで指定したイベント名を指定します。 この例では”message-to-client”です。

さて、実際のコードの説明です。
ユースケースとして複数のユーザがそれぞれブラウザの画面を表示している状態で、 データが登録されると、開いている画面に登録された旨を伝えるメッセージを表示することを考えます。 まず必要なのは接続を確立するためにEventOutputを返却するサービスです。 EventOutputはクライアントごとにインスタンスが必要なので、 接続が確立したEventOutputを格納するためのリストも合わせて定義します。 これらを踏まえて下記のコードを作ります。

private List<EventOutput> eventOutputs = new ArrayList<>();

@GET
@Path("events")
@Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput getServerSentEvents()
{
    final EventOutput eventOutput = new EventOutput();
    eventOutputs.add(eventOutput);
    return eventOutput;
}

次に登録をするためのサービスを作ります。が、実際に何かを登録するのは実装が面倒なので、 サービスが呼ばれたら各クライアントにメッセージをpushするだけにします。 こんな感じです。

@PUT
@Path("put")
public void putData() throws IOException
{
    for (EventOutput eventOutput : eventOutputs) {
        final OutboundEvent.Builder builder = new OutboundEvent.Builder();
        builder.name("message-to-client");
        builder.data(String.class, "登録された!");
        eventOutput.write(builder.build());
    }
}

これでサービス側は実装完了です。 Javascriptクライアントを実装する前に正しく動くかcurlコマンドで確認してみます。 接続確立のサービスを下記のように呼び出します。

curl http://localhost:8080/sse-example/api/sse/events

プロンプトが待ち状態になりました。接続されたままになったのでうまくいったようです! 別のプロンプトから次のコマンドを実行して最初のプロンプトに通知されるか確認します。

curl -X PUT http://localhost:8080/sse-example/api/sse/put

最初のプロンプトの方に以下のメッセージが表示されました。こちらもうまくいったようです。

event: message-to-client
data: 登録された!

サービス側が正常に動作することが確認できたので、 次にJavascriptクライアントを作ります。 HTMLを含めた全コードは下記のようになりました。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE Example</title>
    <script>
        function startup() {
            var eventList = document.getElementById("eventList");
            var eventSource = new EventSource("http://localhost:8080/sse-example/api/sse/events");
            eventSource.addEventListener("message-to-client", function (e) {
                var newElement = document.createElement("li");
                newElement.innerHTML = "message: " + e.data;
                eventList.appendChild(newElement);
            });
        }
    </script>
</head>
<body onload="startup()">
    <h1>イベント表示:</h1>
    <ul id="eventList"></ul>
</body>
</html>

まとめ

このようにSSEの実装は比較的簡単に行うことがでます。 ただ最初にも書いたようにSSEは一方向通信なので、push通信だけでなく双方向通信を行いたい場合は Websocketを利用することになります。 利用シーンとしてはWebsocketの方が多くなりそうですが、 既存のアプリにpush通知機能を実装するという観点からであれば、 HTTPプロトコルで動作するSSEを利用した方が良いケースがあるかもしれないですね。

参考にしたサイト

JavaFXのUIをJUnit形式でテストする

Java Advent Calendar 2015JavaFX Advent Calendar 2015の10日目の記事です。

昨日は下記のお二人でした。

明日は下記のお二人です。

TestFXを知る

先月ダウンロードしたJava Magazine vol23に面白い記事が載っていました。 テストについて特集された中の、TestFXによるJavaFXのテストについての記事です。 TestFXはJavaFXのユーザ・インターフェースをJUnitベースでテストするためのAPIということで、 JUnitで書いたロジック通りにユーザ・インターフェースのテストが実施されます。 単純にロジックをなぞるだけではなく、実際にユーザ・インターフェースを操作した結果を判定してくれるようです。 これは、実際にテストを実行した際に、JavaFXのアプリ上でマウスカーソルが自動的に動いてボタンをクリックしたりすることからもわかります。

普段のプロジェクトでは、残念ながらJavaFXではなくFlexを使っているのですが、 ユーザ・インターフェース周りのテストの仕組みはあってもなかなか思ったようなテストができていないのが現実です。 TestFXはJUnitの延長上でテストができそうなので期待できそうです。

内容を説明する前に、実際に実行した際の動画を記録しました。 動画だと自動で動いているのかわからないと思いますが、 テスト起動後には何も操作をしていません。

アプリの説明

テストに使ったアプリは、ラベルとボタンのあるシンプルなものです。 ボタンをクリックすることで、ラベルに「Hello World!」と表示します。

実際のコードは下記にあります。

https://github.com/kokuzawa/javafx-test

TestFXを設定

Mavenプロジェクトでは下記のDependencyを追加します。

<dependency>
    <groupId>org.loadui</groupId>
    <artifactId>testFx</artifactId>
    <version>3.1.2</version>
    <scope>test</scope>
</dependency>

テストを書く

対象のテストクラスは、TestFXを使うためにorg.loadui.testfx.GuiTestクラスを継承します。 org.loadui.testfx.GuiTestクラスはgetRootNode()メソッドを持ち、そのメソッドでテストしたい画面のFXMLをロードします。

package org.katsumi;

import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import org.junit.Test;
import org.loadui.testfx.GuiTest;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.junit.Assert.assertThat;
import static org.loadui.testfx.controls.Commons.hasText;

public class IndexControllerTest extends GuiTest
{
    @Override
    protected Parent getRootNode()
    {
        try {
            return FXMLLoader.load(getClass().getResource("index.fxml"));
        }
        catch (IOException e) {
            Logger.getLogger(IndexControllerTest.class.getName()).log(Level.SEVERE, "", e);
            return null;
        }
    }

    @Test
    public void testSay()
    {
        final Node node = find("#button");
        click(node);
        assertThat("#greeting", hasText("Hello World!"));
    }
}

テストメソッドは普通にJUnitの形式です。 内容ですが、まずfind("#button")でfx:idがbuttonのコントロールを見つけます。 見つけたボタンコントロールをclickメソッドを利用して実際にクリックします。 ラベルに「Hello World!」が設定されたことをAssert.assertThatで検証します。

まとめ

TestFXはJUnitベースなので抵抗なくテストを実装することができました。 ただ、連続で何度か実行しているとエラーになることがありました。 原因を調べているのですが、まだちょっとわからない状態です。 とは言っても、エラーになるのは稀で、基本的は正常に動作します。

テストコードの導入はプロジェクトの最初の頃に決めておかないと、プロダクトコードがテストしにくい形で作られてしまうことが多々あります。 特にクライアント側のコードはその傾向が強いと思いますので、もしこれからJavaFXのプロジェクトを始める際のであれば、 TestFXの導入を検討してみてはいかがでしょうか。

今回この記事を書くきっかけになった、Java Magazineは下記からダウンロードすることができます。 http://www.oracle.com/technetwork/jp/articles/java/overview/index.html?elq_mid=33486&sh=1612166126426151606143&cmid=JPFM15040092MPP006C005

WildFly SwarmでEJBを試す

WildFly Swarmを試すのも今日で3回目です。 だんだんと実装方法に慣れてきました。 この辺で当初の目的であったWildFly SwarmでEJBを使ってみたいと思います。 EJBが使えないのならSpring Bootで全然構わないわけで、 EJBが使えるかどうかはとても大事なところです。

EJBを組み込む

EJBのモジュールを組み込みます。 よくよく考えてみると、EJBだけでは動きを確認するのが大変なので、 リクエストの受け口だけはJAXRSで作ります。 なので、JAXRSのモジュールも合わせて組み込みます。

また、JAXRSのリソースから@InjectでEJBをDIするにはWeldも必要です。 そのため、JAXRSのモジュールはwildfly-swarm-jaxrs-weldを利用することにします。

<dependencies>
    <dependency>
        <groupId>org.wildfly.swarm</groupId>
        <artifactId>wildfly-swarm-jaxrs-weld</artifactId>
        <version>1.0.0.Alpha5</version>
    </dependency>
    <dependency>
        <groupId>org.wildfly.swarm</groupId>
        <artifactId>wildfly-swarm-ejb</artifactId>
        <version>1.0.0.Alpha5</version>
    </dependency>
</dependencies>

EJBを使ったアプリケーションを作る

まずはEJBです。

package org.katsumi.ejb;

import javax.ejb.Stateless;

@Stateless
public class HelloEjb
{
    public String say()
    {
        return "Hello!";
    }
}

EJBを呼び出すRESTリソースです。

package org.katsumi.ejb;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/hello")
public class HelloResource
{
    @Inject
    private HelloEjb helloEjb;

    @GET
    public String hello()
    {
        return helloEjb.say();
    }
}
package org.katsumi.ejb;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/rest")
public class MyApplication extends Application
{
}

実行する

今回はMainクラスを作るのではなく、Warファイルを生成し、 それを実行する形にします。 まずpom.xmlのpackagingをwarにします。

<packaging>war</packaging>

さらにwarファイルを生成するので下記のプラグインをpom.xmlに追加します。

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>2.6</version>
        <configuration>
            <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.wildfly.swarm</groupId>
        <artifactId>wildfly-swarm-plugin</artifactId>
        <version>${version.wildfly-swarm}</version>
    </plugin>
</plugins>

準備ができたので下記のコマンドで実行します。

mvn wildfly-swarm:run

まとめ

WildFLy SwarmでEJBも問題なく呼び出せることがわかりました。 あとはJPAを使うことができれば、システム開発で使う一通りの機能が使えること確認できそうです。

subscribe via RSS