Ruletの開発


お約束のHello World

Ruletとして必要なことは、以下の3つです。

それでは、さっそくexamples.org.seasar.nazuna.HelloRuletを作成しましょう。
ソースファイルはsrc/examples/org/seasar/nazuna/HelloRulet.javaになります。

examples.org.seasar.nazuna.HelloRulet

package examples.org.seasar.nazuna;

import org.seasar.nazuna.Rulet;

public class HelloRulet extends Rulet {

    public void doExecute() {
        System.out.println("Hello World");
    }
}

Ruletを実行するには、Nazuna.executeRulet(String className)を呼び出します。
最初の引数は、Ruletのクラス名です。
HelloRuletを呼び出すクライアントは次のようになります。

examples.org.seasar.nazuna.HelloRuletClient

import org.seasar.nazuna.Nazuna;
import org.seasar.util.SeasarException;

public class HelloRuletClient {

    private static final String RULET_CLASS_NAME =
        "examples.org.seasar.nazuna.HelloRulet";
		
    public static void main(String[] args) {
        try {
            Nazuna.executeRulet(RULET_CLASS_NAME);
        } catch (SeasarException ex) {
            ex.printStackTrace();
        }
    }
}

実行結果は、

Hello World
になります。

引数と戻り値

doExecuteメソッドの引数や戻り値は、自由に決めることができます。
プリミティブ型も使うことができます。
例をあげると次のようになります。

doExecuteメソッドは、Nazuna.executeRuletメソッド経由で呼び出されます。
executeRuletメソッドの最初の引数は、Ruletのクラス名で、2番目以降の引数が
doExecuteメソッドに渡されることになります。
doExecuteメソッドの引数の数とexecuteRuletメソッドの関係は次のとおりです。

引数の数executeRulet
0executeRulet(String className)
1executeRulet(String className, Object arg0)
2〜5executeRulet(String className, Object arg0, Object arg1, ...)
6〜executeRulet(String className, Object[] args)

doExecuteメソッドの引数がプリミティブ型の場合、executeRuletメソッドの引数には、
プリミティブ型のラッパークラスのオブジェクトを使います。
例えば、intの場合、java.lang.Integerを使うことになります。
doExecuteメソッドの戻り値は、executeRuletメソッドの戻り値として返されます。
doExecuteメソッドの戻り値がプリミティブ型の場合、executeRuletメソッドの戻り値は、
ラッパークラスのオブジェクトになります。
足し算をするRuletは次のようになります。

examples.org.seasar.nazuna.AddRulet

package examples.org.seasar.nazuna;

import org.seasar.nazuna.Rulet;

public class AddRulet extends Rulet {

    public int doExecute(int arg0, int arg1) {
        return arg0 + arg1;
    }
}
AddRuletを呼び出すクライアントは次のようになります。

examples.org.seasar.nazuna.AddRuletClient

package examples.org.seasar.nazuna;

import org.seasar.nazuna.Nazuna;
import org.seasar.util.SeasarException;

public class AddRuletClient {

    private static final String RULET_CLASS_NAME =
        "examples.org.seasar.nazuna.AddRulet";

    public static void main(String[] args) {
        try {
            Integer ret = (Integer) Nazuna.executeRulet(
                RULET_CLASS_NAME, new Integer(1), new Integer(2));
            System.out.println(ret);
        } catch (SeasarException ex) {
            ex.printStackTrace();
        }
    }
}

実行結果は、

3
になります。

引数のデフォルト値

ルールによっては、引数が指定されなかった場合に、デフォルトの値が
適用されて欲しい場合があります。
Ruletにpublic static finalな定数を定義することで、
引数にデフォルト値を適用させることができます。
定数名(フィールド名)は、「DEFAULT_ARG + 引数のインデックス」になります。
引数のインデックスは0からはじまります。
例えば、2番目の引数が指定されなかった場合のデフォルト値を設定したい場合は、
public static final int DEFAULT_ARG1 = 10;
と定義することになります。
デフォルト値の型に制限はありません。プリミティブ型も使えます。
次のRuletは、引数が指定されなかった場合、Hello Worldと出力されます。

package examples.org.seasar.nazuna;

import org.seasar.nazuna.Rulet;

public class ArgDefaultValueRulet extends Rulet {

    public static final String DEFAULT_ARG0 = "World";
    
    public void doExecute(String arg0) {
        System.out.println("Hello " + arg0);
    }
}
package examples.org.seasar.nazuna;

import org.seasar.nazuna.Nazuna;

public class ArgDefaultValueRuletClient {

    private static final String RULET_CLASS_NAME =
        "examples.org.seasar.nazuna.ArgDefaultValueRulet";

    public static void main(String[] args) {
        try {
            Nazuna.executeRulet(RULET_CLASS_NAME);
            Nazuna.executeRulet(RULET_CLASS_NAME, "Nazuna");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
実行結果は、
Hello World
Hello Nazuna
になります。

事前条件

Ruletを呼び出すときに、Ruletが期待している引数の条件を事前条件といいます。
例をあげると次のようなものがあります。

Ruletを呼び出す側は、事前条件を満たす責任があります。
Ruletは、事前条件が満たされていれば、正しい結果を返す義務(事後条件)があります。
このような考え方は「契約による設計」といわれ、
責任と義務をはっきりさせることで、堅牢なプログラミングが可能になります。
事前条件は、doValidateメソッドに記述します。引数は、doExecuteメソッドにあわせます
次のRuletは、引数のnameが設定されていることを事前条件としてチェックしています。

package examples.org.seasar.nazuna;

import org.seasar.nazuna.Rulet;
import org.seasar.util.Assertion;

public class PreConditionRulet extends Rulet {

    public void doValidate(String name) {
        Assertion.assertNotNull("name", name);
    }

    public void doExecute(String name) {
        System.out.println("Hello " + name);
    }
}

Assertionクラスは、表明のためのユーティリティです。
下記のコードと同じ意味になります。

if (name == null) {
    throw new SeasarRuntimeException("ESSR0007", "name");
}

package examples.org.seasar.nazuna;

import org.seasar.nazuna.Nazuna;

public class PreConditionRuletClient {

    private static final String RULET_CLASS_NAME =
        "examples.org.seasar.nazuna.PreConditionRulet";

    public static void main(String[] args) {
        try {
            Nazuna.executeRulet(RULET_CLASS_NAME, (String) null);
        } catch (Exception ex) {
            System.out.println(ex);
        }
    }
}

実行結果は、

org.seasar.util.SeasarRuntimeException: [ESSR0007]name should not be null
になります。
事後条件は、JUnitなどのテストツールでチェックすることを推奨します。

ライフサイクル

RuletもServletと同様なライフサイクルがあります。
クライアントから最初にリクエストがあったときに、インスタンスが作成され
init()が呼び出されます。
リクエストを処理する直前に、doValidate()を呼び出して事前条件をチェックし、
OKならdoExecute()を呼び出します。
システムが終了するときには、destroy()が呼び出されます。
ホットデプロイが行われるときには、古いインスタンスのdestroy()が呼び出された後、
新しいインスタンスのinit()が呼び出されます。

自動トランザクション制御

org.seasar.nazunaにあるRequiredTx、RequiresNewTx、MandatoryTxインターフェースを
implementsするだけで、Nazunaにトランザクション制御を任せることもできます
これらのインターフェースは、実装は必要なくマーカーとして使われます。
インターフェースとtransAttribute の関係は、以下のようになります。
インターフェースtransAttribute
なしSupports
RequiredTxRequired
RequiresNewTxRequiresNew
MandatoryTxMandatory