アクションとは特定の URI へのリクエストに対応する処理で、Cubby のユーザーが実装する部分です。
URI に対応するメソッドをアクションメソッド、アクションメソッドが定義されたクラスをアクションクラスといいます。
アクションクラスに定義されたメソッドのうち、アクセス修飾子が public で戻り値が ActionResult のメソッドをアクションメソッドとして認識します。
このメソッドは AOP によってエンハンスされ、メソッド実行時に以下の処理が実行されるようになります。
これらは AOP によってメソッド自体がエンハンスされるため、アクションメソッドの中から他のアクションメソッドを呼び出した場合も上記の処理が実行されます。
クライアントから送信されたリクエストのパラメータはフォームオブジェクトへバインドされます。
フォームオブジェクトはアクションメソッドごとに指定することが可能で、 アクション自身かアクションのプロパティから取得するオブジェクトのいずれかを使用できます。 デフォルトではアクション自身をフォームオブジェクトとして使用しますが、 @Form でアクションのプロパティ名を指定することによって、 そのプロパティから取得できるオブジェクトをフォームオブジェクトとして使用します。
クライアントから送信されたリクエストパラメータは、まず Map<String, Object[]> に変換します。
サーブレット API 上ではリクエストパラメータの値は String だけですが、 ファイルアップロード等に対応するため、この Map には String 以外の型も格納されます。
ファイルアップロードで使用する multipart のリクエストを受けた場合には、アップロードされたファイルを表す FileItem のインスタンスが格納されます。
そして、上記の Map をフォームオブジェクトへバインドします。
デフォルトではアクションのプロパティのうち、 @RequestParameter がついたプロパティに同名のリクエストパラメータをバインドします。
@Form#bindingType でバインディングの方法を指定することで、以下の挙動から選択することができます。
また、バインドされるプロパティの型によってはコンバータによる型変換を行います。
コンバータとは、リクエストパラメータをフォームオブジェクトへバインドする際に型変換を行う 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 を実装するクラスで、アクションメソッドの戻り値として使用できます。
| クラス名 | 説明 |
|---|---|
| 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のドキュメントも合わせてご覧ください。
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);
}
}