Quantcast
Channel: Katsumi Kokuzawa's Blog
Viewing all 18 articles
Browse latest View live

JAX-RSでStreamを扱う

$
0
0

JAX-RSでExcelファイルをダウンロードする際にストリームを直接触る必要があって、 どうすればストリームにアクセスできるかちょっと調べてみました。 Excelファイルの生成にはApache POIを使っています。

Apache POIでExcelファイルを生成する場合、下記のようなコードを書きます。

finalWorkbookwb=newHSSFWorkbook();finalFileOutputStreamfileOut=newFileOutputStream("workbook.xls");wb.write(fileOut);fileOut.close();

生成したファイルをファイルとして保存せずにServletでダウンロードしようとした場合、 そのコードは下記のようにHttpServletResponse#getOutputStream()でアウトプットストリームを取得し、 レスポンスボディに対してストリーミング処理をすることになります。

finalWorkbookwb=newHSSFWorkbook();finalFileOutputStreamfileOut=newFileOutputStream(response.getOutputStream());wb.write(fileOut);fileOut.close();

ここからが本題です。
JAX-RSでファイルをダウンロードするにはどうしたら良いのか。
通常のファイルの場合は下記のようなコードを書くことで実現できます。

finalFilefile=newFile("workbook.xls");returnResponse.ok(file).build();

Servletでのダウンロードのように、 レスポンスボディに対してストリーミング処理をする場合はjavax.ws.rs.core.StreamingOutputクラスを利用します。 そのコードは下記のようになります。

finalWorkbookwb=newHSSFWorkbook();finalStreamingOutputso=out->wb.write(out);returnResponse.ok(stream).build();

WildFly SwarmでJAXRSを試す

$
0
0

WildFly Swarmのサイトにも記載されていますが、WildFly Swarmは自己完結型のJava microservicesを作成するのに役立つプロジェクトとのこと。 この分野だとSpring Bootの方が運用実績もあり、先行しているようですが、 将来的にはEJBも使いたいのでWildFly Swarmの方を使ってみます。 WildFly Swarmは一つのモジュールというわけではなく、JavaEEの仕様毎に複数のモジュールに分かれていて、 自分の必要なモジュールを取り込んで利用する形になるようです。

JAXRSを組み込む

今回は数あるモジュールの中からJAXRSのモジュールを利用してみます。 2015年11月時点での最新バージョンは1.0.0.Alpha5です。 pom.xmlに下記のdependencyを追加します。

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

これを依存グラフで見てみると…依存がすごいです(笑)

JAXRSのモジュールを組み込んだだけではビルドしても実行できないので、 下記のpluginもpom.xmlに追加します。

<plugin><groupId>org.wildfly.swarm</groupId><artifactId>wildfly-swarm-plugin</artifactId><version>1.0.0.Alpha5</version><executions><execution><goals><goal>package</goal></goals></execution></executions></plugin>

JAXRSアプリケーションを作る

WildFly Swarmの設定が一通り終わったので、次はJAXRSアプリケーションを作ります。 特別なことはなく、普通のJAXRSアプリケーションです。

packageorg.katsumi;importjavax.ws.rs.ApplicationPath;importjavax.ws.rs.core.Application;@ApplicationPath("/rest")publicclassMyApplicationextendsApplication{}
packageorg.katsumi;importjavax.ws.rs.GET;importjavax.ws.rs.Path;@Path("/hello")publicclassHelloResource{@GETpublicStringhello(){return"Hello World!";}}

どうやってうごかすの?

JAXRSアプリケーションも作ったけれどどうやって動かすのか?
WildFly Swarmでは通常のJavaアプリケーションのようにmainメソッドから動かします。 そのため、mainメソッドを持つクラスを新たに作成します。

packageorg.katsumi;importorg.jboss.shrinkwrap.api.ShrinkWrap;importorg.wildfly.swarm.container.Container;importorg.wildfly.swarm.jaxrs.JAXRSArchive;publicclassMain{publicstaticvoidmain(String...args)throwsException{// コンテナの生成// Archiveを生成する前にインスタンス化しておかないと実行時にエラーが発生finalContainercontainer=newContainer();// ShrinkWrapで仮想アーカイブを作成finalJAXRSArchivearchive=ShrinkWrap.create(JAXRSArchive.class);archive.addClass(MyApplication.class);archive.addClass(HelloResource.class);archive.addAllDependencies();container.start().deploy(archive);}}

mainメソッド内では、ShrinkWrapを利用して生成した仮想アーカイブを起動したコンテナにデプロイします。 コード中のコメントにも書きましたが、アーカイブを作る前にコンテナをインスタンス化しておかないと、 実行時にエラーになります。これで半日悩んだ..orz

で、ここで作ったMainクラスをswarm-pluginに教える必要があります。 設定を追加したwildfly-swarm-pluginが下記になります。

<plugin><groupId>org.wildfly.swarm</groupId><artifactId>wildfly-swarm-plugin</artifactId><version>1.0.0.Alpha5</version><configuration><mainClass>org.katsumi.Main</mainClass></configuration><executions><execution><goals><goal>package</goal></goals></execution></executions></plugin>

Mavenでビルド後に下記の方法で実行することができます。

IDEの場合

Mainクラスを実行

Mavenを利用する場合

mvn wildfly-swarm:run

Jarファイルを実行する場合

jar -jar target/projectname-swarm.jar

余談

今回のアプリですが、IntelliJ IDEA 15で作っています。 インストールしたままの環境でMavenビルドをしたのですが、下記エラーが発生してビルドができない状態でした。

java.lang.NoClassDefFoundError: org/eclipse/aether/RepositorySystemSession

原因はMavenのバージョンが古いためで、IntellijにデフォルトでバンドルされているMavenのバージョンは3.0.5であり、 このバージョンではエラーが発生するので、別途バージョン3.2.5をインストールしてそれを参照するようにしました。

WildFly SwarmでJSFを試す

$
0
0

昨日はWildFly SwarmでJAXRSを触ったので、今日はJSFを試してみることにします。

JSFを組み込む

仕様毎にモジュールが分かれているので、JAXRSの時と同じく、今回はJSFのモジュールを取り込みます。 あと、ここで特に記載はしませんがwildfly-swarm-pluginももちろん設定する必要があります。

<dependency><groupId>org.wildfly.swarm</groupId><artifactId>wildfly-swarm-jsf</artifactId><version>1.0.0.Alpha5</version></dependency>

JSFアプリケーションを作る

JSFアプリケーションを作ると言ってもJavaのコードを書くわけではなく、 動くことが分かれば良いのでXHTMLファイルだけを作るだけにします。

<?xml version='1.0' encoding='UTF-8' ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://xmlns.jcp.org/jsf/html"><body><h:outputTextvalue="Hello JSF!"/></body></html>

あと、JAXRSの時と違い、web.xmlを作る必要があります。

<?xml version="1.0" encoding="UTF-8"?><web-appxmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaeehttp://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"version="3.1"><context-param><param-name>javax.faces.PROJECT_STAGE</param-name><param-value>Development</param-value></context-param><servlet><servlet-name>Faces Servlet</servlet-name><servlet-class>javax.faces.webapp.FacesServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>Faces Servlet</servlet-name><url-pattern>*.xhtml</url-pattern></servlet-mapping></web-app>

動かす

JAXRSの時と同じく、mainメソッドから実行するため、mainメソッドを持つクラスを作ります。 今回はJAXRSArchiveではなく、WARArchieをデプロイします。

packageorg.katsumi.jsf;importorg.jboss.shrinkwrap.api.ShrinkWrap;importorg.jboss.shrinkwrap.api.asset.ClassLoaderAsset;importorg.wildfly.swarm.container.Container;importorg.wildfly.swarm.undertow.WARArchive;publicclassMain{publicstaticvoidmain(String...args)throwsException{finalContainercontainer=newContainer();finalWARArchivewarArchive=ShrinkWrap.create(WARArchive.class);warArchive.addAsWebResource(newClassLoaderAsset("index.xhtml",Main.class.getClassLoader()),"index.xhtml");warArchive.addAsWebInfResource(newClassLoaderAsset("WEB-INF/web.xml",Main.class.getClassLoader()),"web.xml");warArchive.addAllDependencies();container.start().deploy(warArchive);}}

実行方法は下記の3通りです。

  • IDEでmainメソッドを実行
  • mvn wildfly-swarm:run
  • jar -jar target/projectname-swarm.jar

これで実際に実行しようとすると下記のエラーが発生します。

atorg.wildfly.extension.undertow.deployment.UndertowDeploymentService$1.run(UndertowDeploymentService.java:85)atjava.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)atjava.util.concurrent.FutureTask.run(FutureTask.java:266)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)atjava.lang.Thread.run(Thread.java:745)atorg.jboss.threads.JBossThread.run(JBossThread.java:320)

これはwildfly-swarm-weldを取り込んでいないためなので、 下記の記述をpom.xmlに追加します。

<dependency><groupId>org.wildfly.swarm</groupId><artifactId>wildfly-swarm-weld</artifactId><version></version></dependency>

まとめ

ドキュメントを読んだだけではWeldが必要には見えなくて、 結局サンプルコードと何遍も見比べる必要がありましたが、 動いてしまえば、後は非常に快適です。 次はEJBが使えるのか試してみないと。

WildFly SwarmでEJBを試す

$
0
0

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です。

packageorg.katsumi.ejb;importjavax.ejb.Stateless;@StatelesspublicclassHelloEjb{publicStringsay(){return"Hello!";}}

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

packageorg.katsumi.ejb;importjavax.inject.Inject;importjavax.ws.rs.GET;importjavax.ws.rs.Path;@Path("/hello")publicclassHelloResource{@InjectprivateHelloEjbhelloEjb;@GETpublicStringhello(){returnhelloEjb.say();}}
packageorg.katsumi.ejb;importjavax.ws.rs.ApplicationPath;importjavax.ws.rs.core.Application;@ApplicationPath("/rest")publicclassMyApplicationextendsApplication{}

実行する

今回は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を使うことができれば、システム開発で使う一通りの機能が使えること確認できそうです。

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

$
0
0

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をロードします。

packageorg.katsumi;importjavafx.fxml.FXMLLoader;importjavafx.scene.Node;importjavafx.scene.Parent;importorg.junit.Test;importorg.loadui.testfx.GuiTest;importjava.io.IOException;importjava.util.logging.Level;importjava.util.logging.Logger;importstaticorg.junit.Assert.assertThat;importstaticorg.loadui.testfx.controls.Commons.hasText;publicclassIndexControllerTestextendsGuiTest{@OverrideprotectedParentgetRootNode(){try{returnFXMLLoader.load(getClass().getResource("index.fxml"));}catch(IOExceptione){Logger.getLogger(IndexControllerTest.class.getName()).log(Level.SEVERE,"",e);returnnull;}}@TestpublicvoidtestSay(){finalNodenode=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

Server Sent Events

$
0
0

これは 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)publicEventOutputgetServerSentEvents(){...}

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

finalEventOutputeventOutput=newEventOutput();finalOutboundEvent.Builderbuilder=newOutboundEvent.Builder();builder.name("message-to-client");builder.data(String.class,"Hello world !");eventOutput.write(builder.build());

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

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

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

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

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

privateList<EventOutput>eventOutputs=newArrayList<>();@GET@Path("events")@Produces(SseFeature.SERVER_SENT_EVENTS)publicEventOutputgetServerSentEvents(){finalEventOutputeventOutput=newEventOutput();eventOutputs.add(eventOutput);returneventOutput;}

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

@PUT@Path("put")publicvoidputData()throwsIOException{for(EventOutputeventOutput:eventOutputs){finalOutboundEvent.Builderbuilder=newOutboundEvent.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><htmllang="en"><head><metacharset="UTF-8"><title>SSE Example</title><script>functionstartup(){vareventList=document.getElementById("eventList");vareventSource=newEventSource("http://localhost:8080/sse-example/api/sse/events");eventSource.addEventListener("message-to-client",function(e){varnewElement=document.createElement("li");newElement.innerHTML="message: "+e.data;eventList.appendChild(newElement);});}</script></head><bodyonload="startup()"><h1>イベント表示:</h1><ulid="eventList"></ul></body></html>

まとめ

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

参考にしたサイト

JdbcRealm with WildFly 9.0.1.Final

$
0
0

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

差分

WildFly 8.0.0.Finalの時の設定:

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

WildFly 9.0.1.Finalの設定:

<security-domainname="app"cache-type="default"><authentication><login-modulename="app_auth"code="Database"flag="required"><module-optionname="dsJndiName"value="java:jboss/datasources/ExampleDS"/><module-optionname="principalsQuery"value="SELECT PASSWORD FROM ACCOUNTS WHERE EMAIL = ?"/><module-optionname="rolesQuery"value="SELECT r.ROLENAME, 'Roles' FROM ROLES r, ACCOUNTS a WHERE r.ACCOUNTID = a.ACCOUNTID AND a.EMAIL = ?"/><module-optionname="hashAlgorithm"value="SHA-256"/><module-optionname="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

参考サイト

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

$
0
0

結構前から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を使ってみたいと思っています。


Apache Flex カスタムヘッダでセキュリティエラー

IntelliJ IDEAからDocker上のWildFlyでデバッグする

$
0
0

IntelliJ IDEAからDocker上のWildFlyコンテナにアプリケーションをデプロイし、 デバッグモードで起動することでステップ実行ができる環境を作ることが今回の目的です。 Docker上にコンテナを起動できる環境はできている前提になります。

環境

  • OS: Mac OSX 10.11.3
  • Java: Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
  • メモリ: 16GB
  • Docker version 1.9.1

アプリの準備

どんなアプリでも良いのですが最終的な生成物はwarファイルにします。 今回はMavenプロジェクトで下記のようなフォルダ構成にします。

docker-wildfly-example/
├── Dockerfile
├── container_settings.json
├── docker-wildfly-example.iml
├── pom.xml
└── src
    └── main
        ├── java
        │   └── org
        │       └── katsumi
        │           └── HelloBean.java
        └── webapp
            ├── WEB-INF
            │   └── web.xml
            └── index.xhtml

肝心なDockerfileの内容は下記のようになります。

# WildFlyのイメージを取得
FROM jboss/wildfly:latest

# MAINTAINER
MAINTAINER Katsumi

# アプリケーションのデプロイ
COPY target/docker-wildfly-example.war /opt/jboss/wildfly/standalone/deployments/

# ポートの解放
EXPOSE 9999

# WildFlyの実行
CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0", "--debug", "9999"]

mvn packageすることで生成されるtarget/docker-wildfly-example.warファイルを コンテナ上の/opt/jboss/wildfly/standalone/deployments/に配置することで WildFly起動時に自動的にデプロイさせます。 また、デバッグ用のポートとして9999を使用するためEXPOSEに指定します。

デバッグはリモートデバッグをすることになるので、 WildFly起動オプションに--debugを付与して9999ポートを指定します。

実行環境の構築

IntelliJ IDEAにDocker PluginがインストールされているとRun/Debug ConfigurationsにDocker Deploymentを追加することができます。

Debugポートに9999を指定すると画面下部にワーニングが表示されます。

Warning: Debug port forwarding not found

このワーニングの右側にFixボタンを表示されるのでこれをクリックすると port設定をしたjsonファイルの保存先を聞かれるので任意の場所に保存します。 アプリケーションツリーにあるcontainer_settings.jsonがそれになります。 このファイルはContainerタブのJSON fileの項目に設定されます。

やっていることはContainerタブのport bindingsで9999ポートを追加したのと同じことなのですが、 port bindingsに設定してもワーニングが消えません。 ワーニングは消えなくてもport bindingsの設定は有効になるので ワーニングが気にならないようであればport bindingsに設定しても良いです。 ただし注意点としてjsonファイルとport bindingsの両方を指定するとport bindingsの方の設定が無視されるようです。 9999以外のポートをバインドする場合は注意する必要があります。

これでデバッグ起動すればステップ実行ができるようになります。

java.sql.Timestamp の振る舞い

$
0
0

Java6 と Java8 で振る舞いが変わっていたのでメモ。

Java6 では下記コードがエラーにならず結果が出力されます。

System.out.println(java.sql.Timestamp.valueOf("2016-13-01"));// 2017-01-01

ところが Java8 だと下記のエラーが発生します。

Exceptioninthread"main"java.lang.IllegalArgumentException:Timestampformatmustbeyyyy-mm-ddhh:mm:ss[.fffffffff]atjava.sql.Timestamp.valueOf(Timestamp.java:204)

もちろん存在しない日付、例えば 2016-12-32 などを指定した場合にもエラーとなります。 Java6 から Java7 になる際に java.sql.Timestampに対してかなりの数のバグフィックスが行われたようで、 おそらくその修正のどこかで振る舞いが変わったのだと思います。

FreeMarkerでinterpolation部分をそのまま出力

$
0
0

FreeMarker-2.3.23でinterpolation部分をそのまま出力したい。

ただし、テンプレート文字列部分はユーザが自由に入力ができて、さらに、それがFreeMarkerのテンプレートだとは認識していない場合を想定。 つまり、ユーザが${hello}と入力したら、出力結果は${hello}となって欲しい。 調べてみると、下記のようにinterpolation部分を${r"..."}で括ればそのまま出力されるみたい。

TEMPLATE:

${r"${hello}"}

OUTPUT:

${hello}

ということは、ユーザの入力した文字列からinterpolation部分を抽出して、${r"..."}で括るように置換してあげればよさそうだけど、 ユーザが${helloとしか入力しない場合に置換できないし、interpolation部分だけでなく、 <#if>などの制御タグもそのまま出力しなければならないので、この方法はあまり現実的ではなさそう。 で、FreeMarkerのマニュアルを眺めてみると、noparseという項があってそれをみたら「あ、これだ!」となった。 下記のように書くとそのまま出力される。

TEMPLATE:

<#noparse>
  <#if greet>
    ${hello}
  </#if>
</#noparse>

OUTPUT:

<#if greet>
  ${hello}
</#if>

参考サイト

remoteCommand in Composite

$
0
0

JSFにはcompositeというカスタムコンポーネントを作るための仕組みがあります。 PrimeFaces-6.0にはManagedBeanのメソッドを呼び出すためのremoteCommandというコンポーネントがあります。 この二つを使ってカスタムコンポーネントを作ったところ、ManagedBeanのメソッドが呼ばれないという問題が発生しました。

まず、JSFのcomposite機能を使って下記のような二つのコンポーネントを作りました。

sample1.xhtml:

<composite:implementation><p:tree><p:ajaxevent="select"oncomplate="afterSelected()"/></p:tree><p:remoteCommandname="afterSelected"actionListener="#{bean.method1}"/></composite:implementation>

sample2.xhtml:

<composite:implementation><p:tree><p:ajaxevent="select"oncomplate="afterSelected()"/></p:tree><p:remoteCommandname="afterSelected"actionListener="#{bean.method2}"/></composite:implementation>

そしてこれらを一つのXHTMLに組み込みます。

main.xhtml:

<my:sample1/><my:sample2/>

sample1側のツリーノードを選択した際にbean.method1が呼ばれることを想定していたのですが、 呼ばれることなく画面がリフレッシュされました。それぞれのカスタムコンポーネント内の remoteCommandのnameの値が重複していると、エラーが発生することなくメソッドが呼ばれないという現象が発生します。

当たり前と言えば当たり前なのですが、 似たようなコンポーネントを作るとやらかしてしまいそうなので注意しないと。

HttpURLConnectionで嵌った話

$
0
0

この記事はJava Advent Calendar 2016の15日目です。
昨日はenkさんの「JGiven で 100% Pure Java BDD(導入編)」でした。

HttpURLConnectionにはgetInputStreamgetErrorStreamというサーバからのレスポンスを受け取るためのメソッドが用意されています。 この二つのメソッドのうち、getErrorStreamのJavadocを見ると下記のように記載されています。

接続が失敗したが、それにもかかわらずサーバーから有用なデータを送信されてきた場合に、エラー・ストリームを返します。典型的な例としては、HTTPサーバーが404で応答し、それによって接続内でFileNotFoundExceptionがスローされたが、そのサーバーから対処策を含むHTMLヘルプ・ページが送信されてきた、といった場合です。

これを読むと少なくともステータスコードが404の場合にはエラーストリームが取得できそうな気がするのですが、 実際のところインプットストリームで返却するのかエラーストリームで返却するのか明確に仕様が決まっているわけではないらしく、 接続先のサーバの実装に依存し、取得できたりできなかったりします。

インプットストリームで返却されたのかエラーストリームで返却されたのか、 事前に判定するための方法が用意されているわけでもないため、 実際には下記のようなコードでストリームを取得する必要がありそうです。 インプットストリームが取れない場合はIOExceptionが発生、 エラーストリームが取れない場合はnullが返却されます。

エラーストリームが取れない場合にインプットストリームを取得:

InputStreamstream=connection.getErrorStream();if(null=stream){stream=connection.getInputStream();}

インプットストリームが取れない場合にエラーストリームを取得:

InputStreamstream;try{stream=connection.getInputStream();}catch(IOExceptione){stream=connection.getErrorStream();}

実際の問題

JAX-RSクライアントライブラリのresteasy-client 3.0.10が持つクラス、 org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngineを利用した際、 サーバが4xxのステータスコードを返却するとNullPointerExceptionが発生します。 URLConnectionEngineの該当箇所のコードは下記のようになっています。

@OverrideprotectedInputStreamgetInputStream(){if(stream==null){try{stream=(status<300)?connection.getInputStream():connection.getErrorStream();}catch(IOExceptione){thrownewRuntimeException(e);}}returnstream;}@OverrideprotectedvoidreleaseConnection()throwsIOException{getInputStream().close();connection.disconnect();}

ステータスコードが300未満の場合はインプットストリーム、300以上の場合はエラーストリームを取得し、 その取得したストリームをクローズしようとしたところでNullPointerExceptionが発生する状況です。 このクライアントコードを書いた人は、ステータスコードが300以上の場合はエラーと判断したのだと思います。 ところが実際はステータスコードが4xxが返却されてもエラーストリームはnullになっていました。

まとめ

結局、インプットストリームを返却するのかエラーストリームを返却するのか、 仕様として明確に決まっていないために、サーバの実装とクライアントの実装が一致せずに問題が発生しているのだと思います。 少なくともステータスコードで判断することはできないので、 最初にあげたように泥臭いコードでストリームを取得しなければならないのでしょう。 HttpURLConnectionクラスに判定メソッドが追加されると良いとは思うのですが、 Java8の段階ではそのようなメソッドは見当たらないです。

ちなみにresteasy-clientはというと、3.0.15でこの問題は修正されています。

https://github.com/resteasy/Resteasy/blob/master/resteasy-client/src/main/java/org/jboss/resteasy/client/jaxrs/engines/URLConnectionEngine.java

Dialog of PrimeFaces

$
0
0

この記事はJava EE Advent Calendar 2016の25日目です。
昨日は@kikutaroさんの「実はJava EEに含まれるJavaMailについて」でした。

現在業務でJSFを使っています。 導入当初はRIであるMojarraのみを利用しようと考えていたのですが、 業務アプリで多い、ツリーやグリッドで数多くのアクションを実装しなければならず、 一つ一つをJavaScriptで実装していくには時間が足りないという判断のもとに、 それらを簡易に実現できるPrimeFacesを利用することにしました。 採用を決定した段階での最新バージョンはPrimeFaces-6.0です。 PrimeFacesは充実したコンポーネント群を持っているので、 必要なコンポーネントはほぼ見つけることができるかと思います。

さて、今回はその中でダイアログコンポーネントについて説明します。 PrimeFacesのDemoを見るとわかるのですが、 このダイアログコンポーネントを表示するための方法が二通り用意されています。

一つ目は静的にダイアログを表示する方法です。

XHTML:

<p:dialogwidgetVar="sampleDialog">
  ...
</p:dialog><p:commandButtonvalue="Show"oncomplete="PF('sampleDialog').show()"/>

二つ目の方法は動的にダイアログを表示する方法です。

ManagedBean:

publicvoidonShowDialog(){RequestContext.getCurrentInstance().openDialog("dialog.xhtml");}

XHTML:

<p:commandButtonvalue="Show"actionListener="#{bean.onShowDialog}"/>

二つ目の方法は指定したXHTMLをiframe内に表示して、それをダイアログとして表示してくれます。 一つ目の方法と異なり、 ダイアログ内のコンテンツを別XHTMLに分けることができるのでコードの見通しが良くなるかと思います。 また、表示時にダイアログのオプションを指定することができますが、 何も指定しないと、モーダレス、リサイズ可能、コンテンツが640pxで固定されたダイアログが表示されます。 ダイアログをリサイズしてもコンテンツが640pxで固定されているので、追従して広がることがありません。 もし、リサイズに合わせてコンテンツも追従するようにしたければ、 表示時に下記のようなオプションを付与します。

publicvoidonShowDialog(){finalMap<String,Object>options=newHashMap<>();options.put("width",640);options.put("contentWidth","100%");RequestContext.getCurrentInstance().openDialog("dialog.xhtml",options,null);}

このようにすることで、ダイアログの初期表示の幅は640px、コンテンツの幅は100%となり、 コンテンツがリサイズに追従するようになります。

さらにパラメータを渡すことも可能です。

publicvoidonShowDialog(){finalMap<String,List<String>>params=newHashMap<>();params.put("id",Collections.singletonList("123"));RequestContext.getCurrentInstance().openDialog("dialog.xhtml",null,params);}

パラメータはiframeのsrc属性に付与され、/contextPath/dialog.xhtml?id=123という値になります。

ちょっとしたコツ

ここまではドキュメントを読めば大体の内容は記載されているのですが、 以下は実体験に伴う内容です。

ダイアログは簡単に表示できますが、 コンポーネントの構成次第で最初の一回は表示され、2回目以降は表示されないという問題が発生します。 具体的には下記のような構成とした場合です。

<p:panelrendered=”#{bean.showPanel}”><p:commandButtonvalue="Show"actionListener="#{bean.onShowDialog}"/></p:panel>

親コンポーネントにrendered属性が付与されている場合、 2回目以降のダイアログの表示が行われません。 バグの可能性もありますが、現状での回避策は下記のように、 rendered属性が付与された親コンポーネントの外に隠しボタンを用意し、 実際のボタンがクリックされた際に隠しボタンをクリックするようにします。

<p:commandButtonid="hiddenButton"actionListener=”#{bean.onShowDialog}”style="visibility: hidden;"/><p:panelrendered="#{bean.showPanel}"><p:commandButtonvalue="Show"oncomplate="$('#hiddenButton').click()"/></p:panel>

コンポーネントの組み合わせによっては落とし穴もあるよ、ということでした。

参考


IntelliJ IDEAでAngular2アプリを動かす

$
0
0

Angular2のアプリをIntelliJ IDEAで動かすまでの試行錯誤の記録です。

環境

  • macOS Sierra
  • IntelliJ IDEA 2016.3

Angular2のインストール

Angular2を動かすのにはNode.jsが必要なので、最初にNode.jsをインストールします。 macOSの場合、homebrewでnodebrewを入れるのが良さそうです。 nodebrewはNode.jsのバージョン管理システムだそうです。

$brew install nodebrew

nodebrewインストール直後だと、カレントのNode.jsが選択されていないため、 Node.jsのコマンドを打ってもそんなコマンドはないと怒られてしまいます。 そこで、カレントのNode.jsを選択します。 選択方法ですが、まずインストールされているNode.jsのバージョンを知る必要があるので、 下記コマンドを実行し、バージョンリストを表示します。

$nodebrew list

バージョンが表示されたら、利用したいバージョンを指定した下記コマンドを実行します。 私の環境ではv7.3.0がインストールされていたので、これをカレントにします。

$nodebrew use v7.3.0

これでNode.jsが使えるようになりました。

次に目的のAngular2をインストールするわけですが、 ここではコマンドラインツールであるangular-cliをインストールします。 インストールはnpm(Node Package Manager)を使います。

$npm install -g angular-cli

これでAngular2アプリを作る準備ができました。

Angular2アプリを作る

早速アプリを作りたいのですが、IntelliJ IDEAではAngularプロジェクトを作るメニューはありません。 プラグイン等であるのかもしれませんが、プレーンな状態ではできないので、 Angular-cliを使って雛形のプロジェクトを作成します。 プロジェクトを作りたいフォルダに移動して下記コマンドを実行します。

$ng new angular-start

これで雛形のアプリが作成されました。 angular-startフォルダに移動して下記コマンドを実行すると、http://localhost:4200/で最初の画面が表示されます。

$ng serve

IntelliJ IDEAに読み込む

ここまでの作業でAngular2の動作するアプリはできましたが、 IntelliJ IDEAでの編集はまだできません。 IntelliJ IDEAで編集するために下記手順でプロジェクトを読み込みます。

  1. File -> New -> Project from Existing Sources… を選択してangular-startフォルダを選択する
  2. Import ProjectダイアログでCreate Project from existing sourcesを選択してNextボタンをクリックする
  3. 以降は何も変更せず、Nextボタンをクリックする
  4. Finishボタンが表示されたらそれをクリックする

これでソースの読み込みは完了しました。

IntelliJ IDEAで実行する

Run -> Edit Configurations… を選択してRun/Debug Configurationsダイアログを表示します。 下記イメージではnpmの実行設定を既に作成した状態になります。

イメージ右側のNode Interpreterの部分、選択された状態となっていますが、 これはIntelliJ IDEAにNodeJSプラグインをインストールしていないと選択されません。 ですので、事前にNodeJSプラグインをインストールする必要があります。

package.json, Scripts, Argumentsの部分が空白となっていると思うので、 イメージ内で設定しているように設定します。 これで実行する準備が整いました。 実行するとコマンドラインでng serveとした時と同様のログがコンソールに出力されるのが確認できると思います。 最初の時と同じように、起動後http://localhost:4200にアクセスすることで最初の画面を表示することができます。

まとめ

コマンドラインからプロジェクトを作った後にIntelliJ IDEAに読み込ませるのがちょっと面倒です。 プラグインでAngular2のプロジェクトが作れるようになると良いのですが、 今の所できなさそうです。(どこかにあるのかな?) ただし、一度読み込ませてしまえば、後は使い慣れたIntelliJ IDEA上で編集ができるし、 補完も効くので開発効率が良さそうです。

参考

PrimeNGを組み込む

$
0
0

昨日、 Angular2 アプリを IntelliJ IDEA で動かすところまでやってみました。 今日は本来の目的である PrimeNG を使ってみます。

PrimeNG とは

JSF の実装の一つである PrimeFaces と同じところが作っている Angular2 用の Rich UI Components です。 PrimeFaces とほぼ同じ(まだ PrimeNG の方が少ない)コンポーネント群を Angular2 から利用できるプロダクトです。 現在の最新バージョンは 1.1.2 です。

PrimeNG のインストール

PrimeNG の Setupに書いてありますが、 プロジェクトフォルダで下記コマンドを実行します。

$npm install primeng --save

PrimeNG を組み込む

PrimeNG のインストールはできたので昨日作ったアプリに PrimeNG を組み込んでみます。 まず、PrimeNG 用の css が用意されているのでそれを追加します。 css の定義を index.html に書いてみたのですが、 表示された画面で確認すると css ファイルが見つからない状態となります。 色々試行錯誤した結果、src/style.cssファイルに下記を追加することで期待した css が読み込まれることが分かりました。

@importurl('../node_modules/primeng/resources/themes/omega/theme.css');@importurl('../node_modules/primeng/resources/primeng.min.css');

読み込まれるというは正確ではなく、インポートした css が style.css ファイル内に展開されて、 それが html のヘッダに組み込まれるというのが正しい表現になります。 おそらくビルド時にそういうことをしているのだと思うのですが、 Angular のビルドフローがよくわかっていないのでなんとも言えません。 src/style.cssに何も書いていないと html にも組み込まれないので何か判定があるのかと。

で、実はこれだけだと片手落ちで、Font Awesomeで提供されている css と font 群を取り込まないと、 PrimeNG でのアイコン表示ができない状態となります。 まず、Font Awesome のサイトでフォントアイコンをダウンロードします。 プロジェクトの src フォルダ直下に resources フォルダを作り、 その下にダウンロードしたモジュール内にある、css フォルダと fonts フォルダをコピーします。 独自で取り込んだ css などをどこに配置するのが Angular として正しいのかよくわからないので、 PrimeNG の Showcace のソースコードと同じ構成にしてみました。

src/style.cssにインポートを追加します。

@importurl('/resources/css/font-awesome.min.css');

PrimeNG の Button コンポーネントを配置

事前の準備ができたので、PrimeNG のコンポーネントの中から Button コンポーネントを配置します。 src/app/app.component.htmlに下記のコードを追加します。

<buttonpButtontype="button"icon="fa-check"iconPos="left"></button>

次に src/app/app.module.tsを下記のように変更します。 diff がハイライトされていませんが差分表示です。

  import { BrowserModule } from '@angular/platform-browser';
  import { NgModule } from '@angular/core';
  import { FormsModule } from '@angular/forms';
  import { HttpModule } from '@angular/http';

  import { AppComponent } from './app.component';
+ import {ButtonModule} from 'primeng/primeng';

  @NgModule({
    declarations: [
      AppComponent
    ],
    imports: [
      BrowserModule,
      FormsModule,
-     HttpModule
+     HttpModule,
+     ButtonModule
    ],
    providers: [],
    bootstrap: [AppComponent]
  })
  export class AppModule { }

実行

実行すると下記の画面が表示されます。

まとめ

PrimeNG のドキュメントを見ても module.ts ファイルを編集などは書いてないので、 この辺りのことは Angular を知っていればわかることなのかなぁとなんとなくモヤモヤした感じです。 実際仕組みがわかれば大したことをしていない気がするので、 もう少し色々試してみようと思います。 次はボタンへのイベントハンドリングをしてみようかと考えています。

参考

JSF 2.3 の Websocket を試す

$
0
0

JSF 2.3 では新しい機能として Websocket が追加されます。
JSF 2.3 はまだリリースされていませんが、先日 JSF 2.3-m09 が公開されたので、 これを使って Websocket を試してみようと思います。

環境

  • macOS Sierra
  • Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
  • WildFly 10.1.0.Final
  • Payara Server 4.1.1.164

はじめに

今回やったことは下記に書いてあることそのままです。

http://blog.payara.fish/jsf-2.3-the-websocket-quickstart-under-payara-server

ボタンをクリックすることでサーバサイドから時間を取得してそれを表示するアプリを作ります。 アプリの作り方は上記サイトを見てもらえばわかると思うので、ここでは上記のサイトには書かれていない、 JavaEE8に対応していない現状のAPサーバで JSF 2.3 を有効にする方法について説明します。

WildFly 10.1.0.Final で試す

まず、普段利用している WildFly で動かそうとしてみました。 WildFly で JSF 2.3 を有効にするためには、WildFly が内包する JSF を無効にする必要があります。 無効にする方法は、下記の記述をした WEB-INF/jboss-deployment-structure.xmlを用意します。

<?xml version="1.0"?><jboss-deployment-structurexmlns="urn:jboss:deployment-structure:1.2"><deployment><exclude-subsystems><subsystemname="jsf"/></exclude-subsystems></deployment></jboss-deployment-structure>

JSF 2.3 の jar ファイルは WEB-INF/libフォルダに配置します。 この状態で WildFly にデプロイし、表示した画面でボタンをクリックすると下記のエラーが発生してしまいます。

Caused by: javax.faces.el.EvaluationException: javax.el.PropertyNotFoundException: /index.xhtml @15,73 action="#{pushBean.clockAction}": Target Unreachable, identifier 'pushBean' resolved to null
	at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:94)
	at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102)

ApplicationScoped を付与した PushBean クラスがインスタンス化されていないようです。 スコープを変えたり、色々試してみたのですが動作は変わらないので諦めました。 動かす方法をご存知でしたら教えて頂けると嬉しいです。

Payara Server で試す

参考にしたサイトは Payara を使っているので、次に Payara で試してみることにしました。 Payara も JSF を内包しているので何もせずにデプロイすると下記エラーが発生します。

Caused by: java.lang.reflect.MalformedParameterizedTypeException
	at sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.validateConstructorArguments(ParameterizedTypeImpl.java:58)
	at sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.<init>(ParameterizedTypeImpl.java:51)
	at sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl.make(ParameterizedTypeImpl.java:92)
	at sun.reflect.generics.factory.CoreReflectionFactory.makeParameterizedType(CoreReflectionFactory.java:105)
	at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:140)
	at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
	at sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68)
	at sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138)
	at sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49)
	at sun.reflect.generics.repository.ClassRepository.getSuperclass(ClassRepository.java:90)
	at java.lang.Class.getGenericSuperclass(Class.java:777)
	at javax.enterprise.util.TypeLiteral.getTypeParameter(TypeLiteral.java:103)
	at javax.enterprise.util.TypeLiteral.getType(TypeLiteral.java:66)
	at com.sun.faces.cdi.CdiUtils.<clinit>(CdiUtils.java:76)
	... 65 more

Payara が内包する JSF を無効にする方法がわからなかったのですが、 Payara の場合は視点が違っていて、内包する JSF を無効にするのではなく、 Bundle した JSF を有効にする設定をしなければならないようです。 Bundle した JSF を有効にする方法は下記の記述をした WEB-INF/glassfish-web.xmlを用意します。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE glassfish-web-app PUBLIC
        "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN""http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd"><glassfish-web-app><class-loaderdelegate="false"/><propertyname="useBundledJsf"value="true"/></glassfish-web-app>

まとめ

設定さえわかれば何も難しい話ではなかったんですが、 設定方法がわからなくて時間がかかってしまいました。 特に Payara の方は全然情報が見つからなくて苦戦…。 基本的にはリリースされてJavaEE8対応されたAPサーバで動かすことになるので知らなくても問題ないわけですが、 早期に試そうとすると色々とハードルがあり大変です。まあそれが楽しかったりするんですけどね。

参考

Viewing all 18 articles
Browse latest View live