JSFのConversation Scopedを改めて試してみた

Java EE 7+JSF 2.2に行きたい所は山々なのだが、現在EE 5やら6から逃れられない全国のエンプラ開発現場の方々お疲れ様です。

さてJSF 2.2からはCDIにFlow Scopedという便利なものが付き、開始・終了が自動になったのであるが、現在EE 6の場合、そんなもの使えないのである。しょうがないので、どうしても類似のことがやりたければ、JSF 2.1でも動作するCDI+Conversation Scopedで誤魔化すしか無いのである。

Conversation Scopedは会話(Conversation)の開始・終了を明示的に行わないといけないので、結構めんどくさいということと、どうやれば綺麗に開始・終了できるのかイマイチ成功例がわからなかったのであるが、やっぱり避けられなくなったので改めて何パターンか試してみた。これが一番綺麗かな? というのが見つかったので、メモ代わりに紹介しておく。これが使いこなせれば、継承とアノテーションを駆使してCDI上でViewScopedと似たことがJSF 2.1でも実現できるかも知れない。いやーさすがにこれは無駄な努力かなぁ・・・

※ちなみに本例ではFaceletをJSF 2.2仕様で書いているが、2.1仕様であればjsfc=”h:inputText”とかで書き換える必要がある。

※JSFの基本的な作り方については、先週発表のスライドにまとめてあるので、そっちば参照してはいよ。
http://www.slideshare.net/iwasakihirofumi/java-ee-7-jsf-22

とりあえず、ポイントは一体どこで会話をスタートさせるのか、だ。JSFの場合、Phase Listenerで制御が一応できるので、Pre Render Viewで引っかけて強引に呼ぶ例をWebで見つけた。他にもGetterメソッドを作ってFaceletの先頭で表示させるついでに#{bean.property}で強引に呼び出す方法もあるが、こっちの方がスマートかも知れないし、タイミングは早いから安全ではある。f:eventを書くのめんどくさいけど。

注意点としては、開始と終了の書き方はほとんど固定で、下記のように書くしかなさそう、という点だ。また、間違って2回conversation.start()が呼ばれると、前に格納されていた値は全消去されてしまうので、あくまで最初に1回だけ呼んでやる必要がある。ここを間違えると「?」な挙動になるので注意するべしである。

遷移画面(3画面)

index.xhtml –> confirm.xhtml –> completed.xhtml

Backing Bean (CDI)

package com.mushagaeshi.jsftest;

import java.io.Serializable;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

@Named
@ConversationScoped
public class ConversationBean implements Serializable {

    private static final long serialVersionUID = 5792114893614101597L;

    // 会話制御のためのオブジェクトを挿入する
    @Inject
    private Conversation conversation;

    private String id;

    private String password;

    // 会話の開始メソッド。1頁目の先頭でf:eventにて強制的に呼び出す。
    public void initConversation() {
        // ポストバックでなくて、且つ、会話が開始されてなかったら開始する。
        if (!FacesContext.getCurrentInstance().isPostback()
                && this.conversation.isTransient()) {
            this.conversation.begin();
        }
    }

    /**
     * Creates a new instance of ConversationBean
     */
    public ConversationBean() {
    }

    // 1画面目でボタンが押された際のメソッド。書かなくても行けるけど念のため。
    public String clickNext() {
        return "confirm?faces-redirect=true";
    }

    // 2画面目でボタンが押された際のメソッド。書かなくても行けるけど念のため。
    public String clickConfirm() {
        return "completed?faces-redirect=true";
    }

    // 3画面目でボタンが押された際のメソッド。会話を完了させて遷移する。これで状態が消える。
    public String clickCompleted() {
        // 会話が開始されてたら終了する。
        if (!this.conversation.isTransient()) {
            this.conversation.end();
        }
        return "index?faces-redirect=true";
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

1画面目 (index.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">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:jsf="http://xmlns.jcp.org/jsf"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <!-- 強制的に会話開始をここで呼び出す -->
    <f:event type="preRenderView" listener="#{conversationBean.initConversation()}"/>
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        Hello from Facelets
        <form jsf:id="form">
            <input type="text" jsf:value="#{conversationBean.id}"/>
            <input type="password" jsf:value="#{conversationBean.password}"/>
            <input type="submit" jsf:action="#{conversationBean.clickNext()}" valuie="submit"/>
        </form>
    </h:body>
</html>

2画面目 (confirm.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">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:jsf="http://xmlns.jcp.org/jsf"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h1>Confirm</h1>
        <p>#{conversationBean.id}</p>
        <p>#{conversationBean.password}</p>
        <form jsf:id="form">
            <input type="submit" jsf:action="#{conversationBean.clickConfirm()}" value="confirm"/>
        </form>
    </h:body>
</html>

3画面目 (completed.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">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:jsf="http://xmlns.jcp.org/jsf"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h1>Completed</h1>
        <p>Thank you, #{conversationBean.id}!</p>
        <form jsf:id="form">
            <input type="submit" jsf:action="#{conversationBean.clickCompleted()}" value="completed"/>
        </form>
    </h:body>
</html>

実行結果

1画面目。入力してみる。
Screen Shot 2013-11-18 at 10.54.04 PM

2画面目。確認画面。ちゃんと入力された値が表示されている。
Screen Shot 2013-11-18 at 10.54.15 PM

3画面目。完了画面。まだ入力された値は生き残ってる。下のボタンを押すと会話が完了し値が消え、1画面目に戻る。
Screen Shot 2013-11-18 at 10.54.26 PM

戻った1画面目。Conversationが再度startされているので、値は消えて再度状態が開始されている(つまり空っぽ)。
Screen Shot 2013-11-18 at 10.54.36 PM

こんな感じですね。これであればBacking BeanJava EE 6でも動くし、EE 7が出てもそのまま持って行ける。ただ、繰り返しになるがHTML friendly style tagsだけは違うので、やっぱりEE 7への移行の際には画面に影響があるのである。返す返すも無念である。

Java SE 8 Lambda式をNetBeansでちょっとだけ試してみた

昨日夜にあったJavaOne報告会福岡できしださんがJavaのLambdaをやたら詳しく説明してくれて頭がぐるぐるになってきた。

で、そういえば最近のJava SE 8のビルドで試してなかったなと言うのと、NetBeansの最新版がようやくSE 8に対応したと聞いていたので、最新のを落として試してみた。

スクリーンショット 2013-11-16 17.46.21

とりあえず、やってみるのはSE 8から拡張されるCollection Frameworkのlambda式対応拡張表現である。

package lambdatest;

import java.util.Arrays;
import java.util.List;

/**
 *
 * @author hirofumi
 */
public class LambdaTest {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        // Target
        List<string> list = Arrays.asList(new String[]{"a", "b", "c", "d", "e"});

        // Normal
        System.out.println("Normal expression:");
        for (String x : list) {
            System.out.println(x);
        }

        //lambda
        System.out.println("Lambda expression:");
        list.forEach(x -> System.out.println(x));

        //lambda with parallel stream
        System.out.println("Lambda expression with parallel stream:");
        list.parallelStream().forEachOrdered(x -> System.out.println(x));

        // parallelStream().forEach()すると順序がバラバラになるので
        // 特にJava EEではforEachOrdered()を使いましょう。約束だよ!!
    }
}

NetBeansの補完機能で、上記の「Normal」の部分に対して提案が出ていて、ポチるとLambda式に変換してくれるようになりました。んーいいんかな・・・

スクリーンショット 2013-11-16 17.58.52

これが

スクリーンショット 2013-11-16 18.00.03

こうなるのだ。下と大体一緒なので、賢いと言えるだろう。

実行結果はこれ。

run:
Normal expression:
a
b
c
d
e
Lambda expression:
a
b
c
d
e
Lambda expression with parallel stream:
a
b
c
d
e
ビルド成功(合計時間: 0秒)

さて、どうしても視点がEEになるのだが、これ実際に使えるかなぁ、というのが本音である。こんな感じで置換される機能がIDE側に付いちゃうと、使わざるを得ないのだろうけど、JavaのLambda式だけ見ると、単なるSyntax Sugarの否定のようにも見えて微妙なんだよなぁと思う次第である。C#のようにdelegate式が簡易に使えるわけでも無し。

Java SE、特にGUI系だと、C#やObjective-Cのように、method delegationが可能な言語だと書きやすいし、間違い無くよく使うのであるが、Java EEで使う所が果たしてあるのだろうか・・・と考えると、何となく微妙である。そもそも、匿名クラス表現をあんまり使わないというのもあるのだ。綺麗に書いた方がメンテナンス性は上だしね。「こんな複雑なコード書けた俺格好いい」系の自己中表現なんかは、エンタープライズ系開発では最も忌諱されるものなのである。そもそも。

だから、パッと思いつくのって、今回の例のように、Collection Frameworkの処理位なのかなと思ったりする次第である。しかも、parallelStream()使ったら、順序がバラバラになっちゃうので、早いけど困る事が多いから、多分使わない 順序守れます※。他にも何かあるのかな。EE 8が出てJPA辺りが完全対応したら、また面白い世界が待っているのだろう。どうなるかな。

※桜庭さんに「順序守れるよ!」って教えてもらったので上のプログラムと一緒に修正しました。(2013/11/16 20:15)

エンタープライズ系開発って何か変? いや、それはいま流行の方向と文化が違うと思って貰った方が良いかなと。これはこれで楽しいよ!

ところで、lambda式の中でアロー演算子(->)が使われてるのは微妙なのである。だってC++とかPHPのmethod callと一緒じゃんと。これはC++11に合わせたんだろうけど、そこだけは良いかなと思う。思うけど、Javaのlambda表現の本家ぽいC++がそもそもそういう表現を取り入れているというのもどうかなぁと思う次第である。C++14で変わる事は無さそうだし、暫くこんな感じなのでしょう。まあいいか。

JavaOne 2013 報告会 at 福岡 技術編

今頃書くな、という話ではあるのですが、JavaOne 2013報告会の2回目を福岡でやってます。丁度いまやってるので、事後報告ではあります(´д`)

JavaOne 2013 報告会 at 福岡 技術編
http://atnd.org/events/45326

スクリーンショット 2013-11-15 19.10.45

数日前に日本Oracle本社で喋らせて頂いたのですが、今年の5月、去年同じ場所で喋らせてもらった時と比較して考えても、現場の熱がだいぶ上がってきたなぁという感じがしています。

いろんな方から伺うのは、いまJava EEが凄いですね、という話です。延々やってきた身としては、ああ、そうなんだ、という感じではあります。が、感じとして似ているのは、J2EE 1.3が出た頃辺りかなと。

要するに、これから盛り上がりますよー、という前兆が見える状況にようやく至ったと考えるわけです。

いつもどっちかというと煽る側に立つ身としては、使えるものじゃないと煽ってもしょうがないよね、というのもあります。が、そもそも使えないものを煽るって酷いよね、という事もあり、J2EE 1.4やJEE 5は放置してきた経緯があります。

で、Java EE 6で超まともに育ったこの仕様の最新版ということで、EE 7のローンチ前よりEE 6っていいよ、と言い続けてきた甲斐があったものというものです。

そんでもって、いろんな方と今回会話させて貰う機会が多くあり、そこで何となく理解したのは、Java EE仕様の部分部分の情報はある程度みなさん共有できているものの、こうやったら上手く作れる、という知見については、やはり日本語書籍がほぼゼロという状況もあり、これからという非常に悲しい状況ではあります。

そのため、この会議では、特に誰もが弄ることが多いJSF 2.2について、NetBeansを使ってウルトラ超簡単な作り方のコツを1ステップずつやってみるものを作ってみました。

資料は先ほどできたばっかりで、これから発表(オラクルの寺田さんが現在発表中)なのですが、後ほどSlide Shareに掲載しようかと思ってますので、興味のある方は後ほどご覧下さいませ。

※追記 21:18
Slide Shareに上げました。

Java EE座談会に出ます

すっかりBlog更新するのを億劫がって放置してしまったのであるが、明日東京のOracleに呼ばれ、Java EE 6の座談会に出ることになった。これで3回目である。福岡より飛行機で行きます。

最新のJavaを3時間で知る!Java解説セミナー (受付終了)
http://www.oracle.com/webapps/events/ns/EventsDetail.jsp?p_eventId=170343&src=7863984&src=7863984&Act=31

この座談会のセミナーは既に受け付け終了してしまっているのだが、講演の様子は後日記事になるらしく、そこで參照していただければと。私も告知を知らなかったので案内出來ませんでしたことよ(×_×)

ちなみに前回・前々回のものは既にWeb記事になっているので、そちらを参照して頂きたい。

「Java EE 6導入を推進するうえでのポイントと導入効果」(前回のもの)
http://builder.japan.zdnet.com/sp_oracle/weblogic_2013/35036674/

なお今回はJavaOne 2013に参加した事や、Java EE 7ネタを入れたので前回と少し違う方向性になるのではと思う。思うだけなのでどうなるかは不明であるのだが、大筋はあまり変わらない予定である。

GlassFishの商用サポートが打ち切られるという衝撃的な発表が先日行われたが、その後に控えている多くのJava EE商用製品のEE 7対応版がいよいよ来年より登場してくると考えている。そろそろ手を付けても罰は当たらない(投資が無駄にならない)頃かなと思っているのだが、どうだろうか。大量なので一朝一夕に追いかけられないため、徐々にキャッチアップしていくのが賢い戦略であろう、と思う次第である。