アクション

アクションとは特定の URI へのリクエストに対応する処理で、Cubby のユーザーが実装する部分です。
URI に対応するメソッドをアクションメソッド、アクションメソッドが定義されたクラスをアクションクラスといいます。

アクションの定義

アクションクラス

Action を継承し、〜Action という名前のクラスをアクションクラスとして認識します。

アクションメソッド

アクションクラスに定義されたメソッドのうち、アクセス修飾子が public で戻り値が ActionResult のメソッドをアクションメソッドとして認識します。
このメソッドは AOP によってエンハンスされ、メソッド実行時に以下の処理が実行されるようになります。

これらは AOP によってメソッド自体がエンハンスされるため、アクションメソッドの中から他のアクションメソッドを呼び出した場合も上記の処理が実行されます。

リクエストパラメータのバインド

クライアントから送信されたリクエストのパラメータはフォームオブジェクトへバインドされます。

フォームオブジェクトはアクションメソッドごとに指定することが可能で、 アクション自身かアクションのプロパティから取得するオブジェクトのいずれかを使用できます。 デフォルトではアクション自身をフォームオブジェクトとして使用しますが、 @Form でアクションのプロパティ名を指定することによって、 そのプロパティから取得できるオブジェクトをフォームオブジェクトとして使用します。

クライアントから送信されたリクエストパラメータは、まず Map<String, Object[]> に変換します。
サーブレット API 上ではリクエストパラメータの値は String だけですが、 ファイルアップロード等に対応するため、この Map には String 以外の型も格納されます。
ファイルアップロードで使用する multipart のリクエストを受けた場合には、アップロードされたファイルを表す FileItem のインスタンスが格納されます。

そして、上記の Map をフォームオブジェクトへバインドします。
デフォルトではアクションのプロパティのうち、 @RequestParameter がついたプロパティに同名のリクエストパラメータをバインドします。
@Form#bindingType でバインディングの方法を指定することで、以下の挙動から選択することができます。

  • 全プロパティをバインド対象とする (ALL_PROPERTIES))
  • @RequestParameter がついたプロパティをバインド対象とする (ONLY_SPECIFIED_PROPERTIES))
  • バインドしない (NONE))
  • @Form で修飾して bindingType を指定しない場合は ALL_PROPERTIES と同様に全プロパティをバインド対象とする

また、バインドされるプロパティの型によってはコンバータによる型変換を行います。

コンバータ

コンバータとは、リクエストパラメータをフォームオブジェクトへバインドする際に型変換を行う Converter を実装したクラスです。

標準でサポートしているコンバータはAPIドキュメントをご覧下さい。

また、独自のコンバータを実装することによって、標準ではサポートしていない型へリクエストパラメータをバインドすることができます。

以下の BookConverter.java は ISBN を表す文字列からデータベースを参照して Book に変換するコンバータです。 変換結果は

                :
        @RequestParameter
        public Book book;
                :

というプロパティにバインドされます。

BookConverter.java

package org.seasar.cubby.examples.other.web.converter;

import org.seasar.cubby.converter.ConversionHelper;
import org.seasar.cubby.converter.impl.AbstractConverter;

/**
 * ISDN を表す文字列と Book を相互変換するコンバータです。
 */
public class BookConverter extends AbstractConverter {

        public BookDao bookDao;

        /**
         * Book.class を返すことで、このコンバータが任意の値を Book へ変換できることを示します。
         */
        public Class<?> getObjectType() {
                return Book.class;
        }

        /**
         * リクエストパラメータ(ここではISBN)を Book へ変換します。
         */
        public Object convertToObject(Object value, Class<?> objectType,
                        ConversionHelper helper) {
                if (value == null) {
                        return null;
                }
                String isbn13 = value.toString();
                Book book = bookDao.findByIsbn13(isbn13);
                return book;
        }

        /**
         * Book から表示用の文字列(ISBN)へ変換します。
         */
        public String convertToString(Object value, ConversionHelper helper) {
                Book book = Book.class.cast(value);
                return book.getIsbn13();
        }

}

ActionResult

以下のクラスは ActionResult を実装するクラスで、アクションメソッドの戻り値として使用できます。

クラス名 説明
Forward 指定されたパスにフォワードします。
Redirect 指定されたパスにリダイレクトします。
Direct 何も処理を行いません。アクションメソッド中でレスポンスに直接書き込みます。ファイルや画像をブラウザに返す時に使用します。
Json 指定されたオブジェクトを Json 形式に変換してレスポンスを返します。
SendError 指定されたエラー系のステータスコードを返します。

アノテーション

アクションクラスやアクションメソッドに以下のアノテーションを指定することで、アクションのメタ情報を設定することができます。

アノテーション名 説明 省略した場合
@Path アクションメソッドに対応する URI を指定します。 XxxAction#yyyy() の場合は /xxx/yyyy になります。
@OnSubmit アクションメソッドへ振り分けるためのリクエストパラメータ名を指定します。 リクエストパラメータによる振り分けを行いません。
@Accept アクションメソッドが受け付ける HTTP メソッドを指定します。 GET と POST に対応するメソッドになります。
@Form リクエストパラメータをバインドするオブジェクトを指定します。 アクションクラスのインスタンスにリクエストパラメータをバインドします。
@RequestParameter リクエストパラメータのバインド対象とするプロパティを指定します。 リクエストパラメータのバインド対象としません。
@InitializeMethod アクションメソッド実行前に実行する初期化メソッドを指定します。 Action#initialize を実行します。
@PreRenderMethod フォワード直前に実行するメソッドを指定します。 Action#prerender を実行します。
@PostRenderMethod フォワード直後に実行するメソッドを指定します。 Action#postrender を実行します。
@Validation 入力検証を定義したプロパティを指定します。 入力検証を行いません。

事前処理・事後処理

アクションクラスにある以下のメソッドをオーバーライドすることで、アクションメソッド実行の前後にフック処理を記述することができます。
これらはアクションクラスごとの処理となるので、アクションメソッドごとに異なる処理をしたい場合はアノテーション(@InitializeMethod、@PreRenderMethod、@PostRenderMethod)でメソッドを指定してください。

メソッド名 説明
Action#initialize アクションメソッドの実行前に呼ばれます。パラメータのバインディング前に呼ばれるので、パラメータを使用したい場合はリクエストから直接取得する必要があります。
Action#prerender フォーワードの直前で呼ばれます。対象のActionクラスのフォワード先で必ず使用する共通のデータなどを取得する目的で使用します。
Action#postrender フォワードの直後で呼ばれます。通常はあまり使用することはないでしょう。

また、アクションメソッド実行の前後に処理を組み込みたい場合は、Seasar の AOP を使用して処理をフックすることもできます。

リクエストからアクション実行までのフロー

ブラウザのリクエストからアクション実行までのフローは以下のようになります。

リクエストからアクション実行までのフロー

リクエストからアクション実行までのフロー

アクションのテスト

アクションの単体テストは CubbyTestCase を使用します。 Seasar の S2Unit の機能を全て使用できるので SeasarのS2Unitのドキュメントも合わせてご覧ください。

  1. CubbyTestCase を継承して、XXXXTest を作成します。
  2. テスト対象のアクションをフィールドに用意します。これはアクション実行後に結果を取得するためです。
  3. setUpメソッドで app.dicon を読み込み、 Seasar2 で管理されるコンポーネント(テスト対象のアクションを含む)を初期化します。
  4. アクションのメソッドごとにテストメソッドを書きます。メソッド名は test から始めます。
  5. リクエストパラメータのセットなど、テストに必要な初期化処理は各テストメソッド用のセットアップメソッドで行います。メソッド名は setup から始めます。
  6. CubbyTestCase#processAction にリクエストのパスを指定して、アクションメソッドを実行します。CoolURI(パラメータ付きのURI)の場合、パスにパラメータを含んで実行します(TodoActionTest#)。
  7. 戻り値をCubbyTestCase#processActionで結果の ActionResult の型とパスをチェックします。パスのチェックは Forward と Redirect の場合のみ行われます。
  8. 必要に応じてアクションクラスのフィールドやリクエストやセッションの値をチェックします。

    HelloActionTest.java

    package org.seasar.cubby.examples.other.web.hello;
    
    import org.seasar.cubby.action.ActionResult;
    import org.seasar.cubby.action.Forward;
    import org.seasar.cubby.unit.CubbyTestCase;
    
    public class HelloActionTest extends CubbyTestCase {
    
            // 対象のアクション
            private HelloAction action;
            
            // 初期化処理
        protected void setUp() throws Exception {
            // diconファイルの読み込み
            include("app.dicon");
        }
        
            public void testIndex() throws Exception {
                    // アクションの実行
                    ActionResult result = processAction("/hello/");
                    // 結果のチェック
                    assertPathEquals(Forward.class, "input.jsp", result);
            }
    
            public void setupMessage() {
                // リクエストパラメータのセット
                    getRequest().addParameter("name", "name1");
            }
            
            public void testMessage() throws Exception {
                    // アクションの実行
                    ActionResult result = processAction("/hello/message");
                    // 結果のチェック
                    assertPathEquals(Forward.class, "result.jsp", result);
                    // 実行後のアクションの状態を確認
                    assertEquals("name1", action.name);
            }       
    }

    TodoActionTest.java

    package org.seasar.cubby.examples.todo.action;
    
    import org.seasar.cubby.action.ActionResult;
    import org.seasar.cubby.action.Forward;
    import org.seasar.cubby.action.Redirect;
    import org.seasar.cubby.examples.RunDdlServletRequestListener;
    import org.seasar.cubby.unit.CubbyTestCase;
    
    public class TodoActionTest extends CubbyTestCase {
    
            private TodoAction action;
            
        protected void setUp() throws Exception {
            include("app.dicon");
            RunDdlServletRequestListener listener = new RunDdlServletRequestListener();
            listener.requestInitialized(null);
        }
    
        @Override
        protected void setUpAfterBindFields() throws Throwable {
            super.setUpAfterBindFields();
                    getRequest().addParameter("userId", "test");
                    getRequest().addParameter("password", "test");
                    // 後続のテストを実行するためにログインアクションを実行
                    assertPathEquals(Redirect.class, "/todo/", processAction("/todo/login/process"));
        }
        
            public void testShow() throws Exception {
            this.readXlsAllReplaceDb("TodoActionTest_PREPARE.xls");
            // CoolURIの場合のテスト
                    ActionResult result = processAction("/todo/1");
                    assertPathEquals(Forward.class, "show.jsp", result);
                    assertEquals(new Integer(1), action.id);
                    assertEquals("todo1", action.text);
                    assertEquals("todo1 memo", action.memo);
                    assertEquals(new Integer(1), action.todoType.getId());
                    assertEquals("type1", action.todoType.getName());
                    assertEquals("2008-01-01", action.limitDate);
            }
    }