サブアプリケーションスコープについて その3

public void initialize() {
    aaa = "AAA";
    bbb = "BBB";
    ccc = "CCC";
}

前述のfuga.javaに上記を追加した場合以下のような結果に。

初期状態
aaa aaa
bbb bbb
ccc null
ddd ddd
初期状態⇒self
aaa aaa
bbb bbb
ccc null
ddd null
初期状態⇒next
aaa AAA
bbb BBB
ccc CCC
ddd null
初期状態⇒next⇒self
aaa aaa
bbb bbb
ccc ccc
ddd null
初期状態⇒next⇒back
aaa aaa
bbb bbb
ccc ccc
ddd ddd
初期状態⇒next⇒back⇒self
aaa aaa
bbb bbb
ccc ccc
ddd null

hogeからfugaに遷移した直後だけ大文字になりますが、その後は小文字に戻ってしまいます。

サブアプリケーションスコープについて その2

以下のようなhogeとfugaの2つのページが同一サブアプリケーションにあったとします(アクセッサは省略)。
hoge

public class HogePage {

    private String aaa;
    @SubapplicationScope
    private String bbb;
    private String ccc;
    private String ddd;

    public Class doHoge() {
        ccc = "ccc";
        return FugaPage.class;
    }

    public Class doNothing() {
        return null;
    }

    public Class initialize() {
        aaa = "aaa";
        bbb = "bbb";
        ddd = "ddd";
        return null;
    }
}
<?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:te="http://www.seasar.org/teeda/extension" xml:lang="ja"
    lang="ja">
    <head>
        <meta http-equiv="Content-Type"
            content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form id="form">
            <input id="aaa" type="text" />
            <span id="bbb" />
            <span id="ccc" />
            <span id="ddd" />
            <input id="doHoge" type="submit" value="next" />
            <input id="doNothing" type="submit" value="self" />
        </form>
    </body>
</html>

fuga

public class FugaPage {

    private String aaa;
    private String bbb;
    private String ccc;
    private String ddd;

    public Class doFuga() {
        return HogePage.class;
    }

    public Class doNothing() {
        return null;
    }
}
<?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:te="http://www.seasar.org/teeda/extension" xml:lang="ja"
    lang="ja">
    <head>
        <meta http-equiv="Content-Type"
            content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form id="form">
            <span id="aaa" />
            <span id="bbb" />
            <span id="ccc" />
            <span id="ddd" />
            <input id="doFuga" type="submit" value="back" />
            <input id="doNothing" type="submit" value="self" />
        </form>
    </body>
</html>
  • aaaは入力項目なのでサブアプリケーションスコープ
  • bbbは@SubapplicationScopeによってサブアプリケーションスコープ
  • cccはhogeからfugaへ遷移するとサブアプリケーションスコープに(謎)
  • dddは何もないのでサブアプリケーションスコープにはならない
初期状態
aaa aaa
bbb bbb
ccc null
ddd ddd
初期状態⇒self
aaa aaa
bbb bbb
ccc null
ddd null
初期状態⇒next
aaa aaa
bbb bbb
ccc ccc
ddd null
初期状態⇒next⇒self
aaa aaa
bbb bbb
ccc ccc
ddd null
初期状態⇒next⇒back
aaa aaa
bbb bbb
ccc ccc
ddd ddd
初期状態⇒next⇒back⇒self
aaa aaa
bbb bbb
ccc ccc
ddd null

ちょっとcccの動きは想定外で戸惑ってしまいました。

Localeについて

UIViewRootのgetLocale()メソッドがRestore Viewフェーズではブラウザの設定、Render ResponseフェーズではOSの設定を戻すようなのですが、これってこういうものなのでしょうか?Restore ViewフェーズではTeedaStateManagerImplのserializedViewsにブラウザの設定がセットされていてこれがViewRootにセットされるのに対し、Render ResponseフェーズではTViewTagのsetPropertiesメソッドで(最初の4行でやろうとしていることの趣旨はわからないのですが結果的には)Local.getDefault()の値がViewRootにセットされています。

リスナ・メソッドからMessageSenderを利用する

Queue1のメッセージを編集してQueue2に書き込むアプリケーションがあったとします。

名前 設定 処理
sender1 jms-outbound-1.dicon メッセージをQueue1に送信する
sender2 jms-outbound-2.dicon メッセージをQueue2に送信する
listener jms-inbound.dicon Queue1のメッセージを受取り、編集してsender2に渡す

これを1つのリソースアダプターでやろうとしたら最後のsender2がコネクションを取得するところでエラーとなりました(XAException)。エラーは「XA 操作が失敗しました。errorCode を参照してください」とのことなのでerrorCodeを調べたらXAER_RMERR(-3)で「トランザクションブランチでリソースマネージャエラーが発生しました。」ということらしいですがよく分かりません。。。
sender2だけ別のリソースアダプタにしてみたら上手くいったのですが、ログでいうと

DEBUG 2007-08-30 19:52:08,318 [pool-1-thread-1] 物理コネクションをオープンしました.物理コネクション=[Physical Session -> nullPhysical Connection com.ibm.mq.jms.MQXAConnection@1836aeaSuper -> com.sun.genericra.outbound.ManagedConnection@e0420b]
DEBUG 2007-08-30 19:52:08,365 [pool-1-thread-1] コネクションがトランザクションに関連づけられました.物理コネクション=[Physical Session -> com.ibm.mq.jms.MQSession@1549cebPhysical Connection com.ibm.mq.jms.MQXAConnection@1836aeaSuper -> com.sun.genericra.outbound.ManagedConnection@e0420b] トランザクション=FormatId=4360, GlobalId=1188471081592/1, BranchId=

の1行目までは同じなので、2行目のコネクションとトランザクションの関連づけで失敗しているっぽいです。
インバウンド通信とアウトバウンド通信では別のリソースアダプタを用意した方が無難なのでしょうかね。

追記:と思ったらそうでもなくて、Queue2だけでなくてQueue3にもsender3使って何かを送信するとするならsender2とsender3も別のリソースアダプタでないとダメみたいです。

サブアプリケーションスコープについて その1

以下のようなhogeページからfugaページへ遷移するアプリケーション(同一サブアプリケーション)があったとします(ラジオボタンのラベル引継ぎ機能を使ってみたかったので1.0.10で試しました)。
HogePage.java(アクセッサは省略)

public class HogePage {

    private String aaa;
    private List bbbItems;
    @Required
    private String bbb;
    private String bbbLabel;

    public Class doHoge() {
        return FugaPage.class;
    }

    public void initialize() {
        bbbItems = new ArrayList();
        Map items1 = new HashMap();
        items1.put("value", "001");
        items1.put("label", "ABC");
        bbbItems.add(items1);
        Map items2 = new HashMap();
        items2.put("value", "002");
        items2.put("label", "abc");
        bbbItems.add(items2);
        this.aaa = "AAA";
    }
}

hoge.html

<?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:te="http://www.seasar.org/teeda/extension" xml:lang="ja"
    lang="ja">
    <head>
        <meta http-equiv="Content-Type"
            content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form id="form">
            <span id="allMessages" />
            <span id="aaa" />
            <span id="bbb">
                <input type="radio" name="bbb" value="0"
                    checked="checked" />
                ゼロ
                <input type="radio" name="bbb" value="1" />
                イチ
            </span>
            <input id="doHoge" type="submit" value="次ページへ" />
        </form>
    </body>
</html>

FugaPage.java(アクセッサは省略)

public class FugaPage {
    private String aaa;
    private String bbbLabel;
}

fuga.html

<?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:te="http://www.seasar.org/teeda/extension" xml:lang="ja"
    lang="ja">
    <head>
        <meta http-equiv="Content-Type"
            content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form id="form">
            <span id="allMessages" />
            <span id="aaa" />
            <span id="bbbLabel" />
        </form>
    </body>
</html>

これを実行すると

ラジオボタンを選択しなかった場合
hogeページの上部にエラーメッセージが表示されるが、ラジオボタンの選択肢が消えてしまう。
ラジオボタンを選択した場合
fugaページに遷移するが何も表示されない

となります。前者の問題を回避するために普通(?)は

<input type="hidden" id="bbbItemsSave"/>

というのを追加します。これで後者の問題も半分解決し、bbbLabelが表示されるようになります。同様にaaaも表示するようにするには普通(?)はhidden項目を追加します。

<input type="hidden" id="aaa-hidden"/>

が、実はhidden項目を追加しなくても

@SubapplicationScope
private String aaa;

とやれば引き継がれるようになります。ここまでは公式(?)な方法ですが

@SubapplicationScope
private List bbbItems;

はどうなんでしょう?bbbItemsを次ページに引き継ぐ必要はないといえばないんですが、動き的には問題なさそうな感じがしてます。個人的にはHTMLにhidden項目追加するよりこっちの方が好きなんでできればこっち使いたいなぁなんて・・・。

SMART deploy構成における実装の切り替え

DIコンテナの存在を知ったとき、最初に期待したのは設定ファイル地獄から脱却できるのでは?ということでした。
今までだとあるロジックに2通りのバリエーションを持たせたいといった場合には設定ファイルにフラグを設けて、ロジックの中でそのフラグを見て分岐させるというようなことをやっていたので、設定ファイルに設定すべき項目が膨れ上がってしまっていました。DIコンテナを使えばフラグはロジックのパラメータにしたり、ロジック自体を分けたりして、あとは呼び出す側(サービス)でパラメータや呼び出すメソッドが異なる実装を用意して切り替えれば設定ファイル地獄から脱却できそうな気がしたのです。

そこからSeasar2についていろいろ調べ始め、そしてSMART deploy構成にしたがって開発しようと決めた訳ですが、そうした場合に実装を切り替えるにはどうすればいいのでしょう?例えばHogeServiceというインターフェイスがあった場合、通常はルートパッケージ.service.impl.hogeServiceImplがコンテナに登録されますが、これをdiconファイルによってルートパッケージ.service.impl.hogeServiceImpl2がコンテナに登録されるようにしたいということです。

ネットで軽く情報を漁ってみたもののいい情報を拾えなかったので、ソースをみていろいろ試してみました。結論だけ書くとconvention.diconだけ編集すればOKで

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
	"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component class="org.seasar.framework.convention.impl.NamingConventionImpl">
        <initMethod name="addRootPackageName">
            <arg>"hatena"</arg>
        </initMethod>
        <!-- hogeServiceImpl2が登録されるようにする -->
        <initMethod name="addInterfaceToImplementationClassName">
            <arg>"hatena.service.HogeService"</arg>
            <arg>"hatena.service.impl.HogeServiceImpl2"</arg>
        </initMethod>
    </component>
</components>

のようにしてインターフェース名と実装クラス名の関連を追加します。こうするとhogeServiceImpl2(だけ)がコンテナに追加されるようになりました。

本題とはそれますが、「addInterfaceToImplementationClassName」というメソッド名、このネーミングセンスは大好きです。メソッド名がそのメソッドの内容を的確に語っています。今回はこのネーミングセンスに助けられ、比較的早く当たりをつけることができました。たまに長いメソッド名を嫌って短くしてしまう人がいるのですが、社内では長くてもいいから内容を的確に語るメソッド名をつけるよう啓蒙中です。

S2JUnit4を使ってみる

惹かれた機能は

の2つ。なのでS2Unitでもよさそうなのですが、なんとなくS2JUnit4で。

で、使ってみた感想ですが、テスト開始時にテーブルの中身をテスト用データに入れ替えてテスト終了時に元に戻してくれるのは素晴らし過ぎる!
あとは期待値との比較のところなんですが、期待値だけだと片手落ちのような・・・。つまり、期待値っていうのはある条件(パラメータ)の下での期待値なのであって、条件と結果(期待値)はセットで定義したいところ。という訳で期待値用のエクセルファイルに条件も定義してしまう方法を検証してみました。

期待値はgetExpectedでDataSet(ブック)を取得し、そこからgetTableでDataTable(シート)を取得という流れになっているようなので、このDataTableをJavaBeansに変換すればOK。この変換はS2Dxoではできなかったので以下のようにしてまずはMapへと変換してみました。

public List convert(DataTable dataTable) {
    final List result = new ArrayList();
    for (int i = 0; i < dataTable.getRowSize(); i++) {
        final Map row = new HashMap();
        for (int j = 0; j < dataTable.getColumnSize(); j++) {
            row.put(dataTable.getColumnName(j), dataTable.getRow(i)
                    .getValue(j));
        }
        result.add(row);
    }
    return result;
}

一度Mapへ変換してしまえばあとはS2Dxoでお好みのJavaBeansへ変換可能。という訳で条件と結果を同じブックに定義するのは何とかなりそうです。あとはテスト用データも同じブックに定義したいところなんですが、こちらはテーブル名ではないシート名を使うことができないようなので今のところ無理みたいです。「このシートはテーブルのデータではありませんよ」的な印が付けられればいいんですが。でももしかしたら別ブックにした方が作業しやすいかも!?