アクションとは特定の URI へのリクエストに対応する処理で、Cubby のユーザーが実装する部分です。
URI に対応するメソッドをアクションメソッド、アクションメソッドが定義されたクラスをアクションクラスといいます。
コンテナからアクションクラスを検索して、そこに定義されたアクションメソッドと URI パターンの紐付けを内部に登録します。 クライアントからリクエストが送信されたときには、その登録された URI からリクエストされた URI パターンにマッチするアクションメソッドを検索、実行します。
クライアントからのリクエストからアクションを実行するフローは以下のようになります。
コンテナに登録されたクラスのうち、 Action を継承しているか、 ActionClass によって修飾されたクラスをアクションクラスとして認識します。
アクションクラスに定義されたメソッドのうち、アクセス修飾子が public で戻り値が ActionResult のメソッドをアクションメソッドとして認識します。
クライアントから送信されたリクエストのパラメータはフォームオブジェクトへバインドされます。
フォームオブジェクトはアクションメソッドごとに指定することが可能で、 アクション自身かアクションのプロパティから取得するオブジェクトのいずれかを使用できます。 デフォルトではアクション自身をフォームオブジェクトとして使用しますが、 @Form でアクションのプロパティ名を指定することによって、 そのプロパティから取得できるオブジェクトをフォームオブジェクトとして使用します。
クライアントから送信されたリクエストパラメータは、まず Map<String, Object[]> に変換します。
サーブレット API 上ではリクエストパラメータの値は String だけですが、 ファイルアップロード等に対応するため、この Map には String 以外の型も格納されます。
ファイルアップロードで使用する multipart のリクエストを受けた場合には、アップロードされたファイルを表す FileItem のインスタンスが格納されます。
そして、上記の Map をフォームオブジェクトへバインドします。
デフォルトではアクションのプロパティのうち、 @RequestParameter がついたプロパティに同名のリクエストパラメータをバインドします。
@Form#bindingType でバインディングの方法を指定することで、以下の挙動から選択することができます。
また、バインドされるプロパティの型によってはコンバータによる型変換を行います。
以下のクラスは ActionResult を実装するクラスで、アクションメソッドの戻り値として使用できます。
| クラス名 | 説明 |
|---|---|
| Forward | 指定されたパスにフォワードします。 |
| Redirect | 指定されたパスにリダイレクトします。 |
| Direct | 何も処理を行いません。アクションメソッド中でレスポンスに直接書き込みます。ファイルや画像をブラウザに返す時に使用します。 |
| Json | 指定されたオブジェクトを Json 形式に変換してレスポンスを返します。 |
| SendError | 指定されたエラー系のステータスコードを返します。 |
| PassThrough | FilterChain#doFilter で次のフィルタへチェインします。 |
アクションクラスやアクションメソッドに以下のアノテーションを指定することで、アクションのメタ情報を設定することができます。
| アノテーション名 | 説明 | 省略した場合 |
|---|---|---|
| @Path | アクションメソッドに対応する URI を指定します。 | XxxAction#yyyy() の場合は /xxx/yyyy になります。 |
| @OnSubmit | アクションメソッドへ振り分けるためのリクエストパラメータ名を指定します。 | リクエストパラメータによる振り分けを行いません。 |
| @Accept | アクションメソッドが受け付ける HTTP メソッドを指定します。 | GET と POST に対応するメソッドになります。 |
| @Form | リクエストパラメータをバインドするオブジェクトを指定します。 | アクションクラスのインスタンスにリクエストパラメータをバインドします。 |
| @RequestParameter | リクエストパラメータのバインド対象とするプロパティを指定します。 | リクエストパラメータのバインド対象としません。 |
| @InitializeMethod | アクションメソッド実行前に実行する初期化メソッドを指定します。 | Action#initialize を実行します。 |
| @PreRenderMethod | フォワード直前に実行するメソッドを指定します。 | Action#prerender を実行します。 |
| @PostRenderMethod | フォワード直後に実行するメソッドを指定します。 | Action#postrender を実行します。 |
| @Validation | 入力検証を定義したプロパティを指定します。 | 入力検証を行いません。 |
Action を継承したアクションクラスでは、以下のメソッドをオーバーライドすることでアクションメソッド実行の前後にフック処理を記述することができます。 これらはアクションクラスごとの処理となります。
@ActionClass を指定したアクションクラスの場合や、アクションメソッドごとに異なる処理をしたい場合はアノテーション(@InitializeMethod、@PreRenderMethod、@PostRenderMethod)でメソッドを指定してください。
| メソッド名 | 説明 |
|---|---|
| Action#initialize | アクションメソッドの実行前に呼ばれます。パラメータのバインディング前に呼ばれるので、パラメータを使用したい場合はリクエストから直接取得する必要があります。 |
| Action#prerender | フォーワードの直前で呼ばれます。対象のActionクラスのフォワード先で必ず使用する共通のデータなどを取得する目的で使用します。 |
| Action#postrender | フォワードの直後で呼ばれます。通常はあまり使用することはないでしょう。 |
マルチパートのリクエストでアップロードされたファイルを処理するためには、フォームオブジェクトに org.apache.commons.fileupload.FileItem 型のリクエストパラメータを設定して下さい。
事前に、コンテナから org.apache.commons.fileupload.FileUpload と org.apache.commons.fileupload.RequestContext を取得できるように設定しおいてください。
ファイルをアップロードするための JSP
<t:form method="post" action="${contextPath}/fileupload/upload" enctype="multipart/form-data"
value="${form}">
<h1>ファイルアップロード</h1>
拡張子が「png」「jpg」のファイルのみアップロードできます。<br/>
...
<label for="file">ファイル:</label>
<t:input type="file" name="file"/><br/>
<input type="submit" value="アップロード"/>
</t:form>
アップロードを処理するアクションクラス
import org.apache.commons.fileupload.FileItem;
...
public class FileUploadAction extends Action {
ValidationRules validation = new DefaultValidationRules() {
public void initialize() {
add("file", new FileRegexpValidator(".+\\.(?i)(png|jpg)"));
}
};
@RequestParameter
private FileItem file;
public FileItem getFile() {
reutrn file;
}
@Validation(rules = "validation", errorPage = "fileupload.jsp")
public ActionResult upload() {
return new Forward("done.jsp");
}
}
アップロード結果を表示する JSP
...
<h2>ファイルアップロード結果</h2>
<label for="file">File:</label>
アップロード完了しました。<br/>
ファイル名:${file.name}<br/>
ファイルサイズ:${file.size}<br/>
...
コンバータとは、String 型や FileItem 型のリクエストパラメータをフォームオブジェクトのプロパティの型へ変換するクラスです。 Converter を実装します。
型変換に失敗した場合、フォームオブジェクトのプロパティには null が設定されます。
標準で提供されるコンバータはAPIドキュメントをご覧下さい。
コンバータでの型変換に失敗したことをバリデーション時に検出することができます。
ValidationRules を定義するときに org.seasar.cubby.validator.ConversionValidationRule を追加してください。
public ValidationRules validation = new DefaultValidationRules("todo.") {
@Override
public void initialize(String resourceKeyPrefix) {
add(new ConversionValidationRule(resourceKeyPrefix));
}
};
独自のコンバータを作成することによって、標準ではサポートしていない型へリクエストパラメータをバインドすることができます。
以下の BookConverter.java は ISBN を表す文字列からデータベースを参照して Book に変換するコンバータです。 変換結果は
:
@RequestParameter
public Book book;
:
のような Book 型のプロパティにバインドされます。
BookConverter.java
package org.seasar.cubby.showcase.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();
}
}
作成したコンバータはコンテナから取得されます。 使用するコンテナに登録しておいてください。
通常は自動的にプロパティの型にマッチするコンバータが選択されますが、特定のコンバータを指定することもできます。
以下の例では address というフィールドにトリムされた文字列を設定します。
:
@RequestParameter(converter = TrimConverter.class)
public String address;
:
public class TrimConverter extends AbstractConverter {
public Object convertToObject(Object value, Class<?> objectType, ConversionHelper helper) throws ConversionException {
if (value == null) {
return null;
}
return value.toString().trim();
}
}
Cubby では JUnit 等からアクションをテストするためのサポートクラスを用意しています。
テストケースからアクションの実行をエミュレートするための CubbyRunner と、 その実行結果を検証するための CubbyAssert を用意しています。
FooAction.java
@ActionClass
public class FooAction {
public ActionResult index() {
return new Forward("index.jsp");
}
}
HelloActionTest.java
public class FooActionTest {
@Test
public void index1() throws Exception {
// ServletContext, HttpServletRequest, HttpServletResponse などのモックオブジェクトを作成
MockServletContext servletContext = ...
MockHttpServletRequest request = ...
request.setMethod("GET");
request.setServletPath("/foo/"); // 実行するアクションのパスを設定
MockHttpServletResponse response = ...
// アクションを実行
ActionResult actualResult = CubbyRunner.processAction(servletContext,
request, response);
// アクションの実行結果を検証
CubbyAssert.assertPathEquals(Forward.class, "index.jsp", actualResult);
}
}