Hatena::Groupseasarproject

S2できるかな?

2009-03-16

Seasar Conference 2009 White 行ってきました。

「ITpro」の方には書いてないんだけど、Slim3DB操作であるSlim3JDBCでは、「流れるようなインターフェース*1」を使ったSQL自動組み立てをスッパリ捨てるとか、想像の斜め上でビックリ!!

Cubbyこう言うところが難しいよね…。

public class HogeAction extends Action {

	public @RequestParameter String name;

	public ActionResult input() {
		return new Forward("input.jsp");
	}
	
	public ActionResult process() {
		return new Forward("process.jsp");
	}
}
<!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" lang="ja" xml:lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>入力画面</title>
</head>
<body>
<h1>入力画面</h1>
<t:form actionClass="xxx.xxx.HogeAction" actionMethod="process" 
  method="post" value="${action}">
        あなたの名前:
        <t:input type="text" name="name"/>
        <input type="submit" value="送信"/>
</t:form>
</body>
</html>
<!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" lang="ja" xml:lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>出力画面</title>
</head>
<body>
<h1>出力画面</h1>
<p>こんにちは、${action.name}さん。</p>
</body>
</html>

2分間チュートリアルを参考に、こんなアクション+jspを書いて http://localhost:8080/xxx/hoge/input にアクセス、文字を入力し、「送信」のサブミットボタンを押すと、「こんにちは、***さん。」と表示が出る、ここまでは無問題。 …しかし、その画面でF5キーを押してリロードした時は?

http://xs537.xs.to/xs537/09121/caution651.png

当然、こう言うアラート出ますよね? この回避方法とか、マニュアル*2見ても載ってないですよねー。 こう言うの、全くの初心者?だとハマっちゃうから、誰か解決法を書かないと、Cubbyも広まらないんじゃないかな。

…って、待ってても仕方ないので、次、自分が書きます。

Cubbyこう言うところが難しいよね…。#2 Forward/Redirect解決編

前述の問題*3の、一応の解決策はこう。

public class HogeAction extends Action {

	public @RequestParameter String name;

	public ActionResult input() {
		return new Forward("input.jsp");
	}
	
	@Accept(RequestMethod.POST)
	public ActionResult validate() {
		flash.put("name", name);
		return new Redirect("process");
	}
	
	public ActionResult process() {
		return new Forward("process.jsp");
	}
}
<!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" lang="ja" xml:lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>入力画面</title>
</head>
<body>
<h1>入力画面</h1>
<t:form actionClass="jp.silic.pointsite.action.HogeAction" actionMethod="validate" 
  method="post" value="${action}">
        あなたの名前:
        <t:input type="text" name="name"/>
        <input type="submit" value="送信"/>
</t:form>
</body>
</html>
<!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" lang="ja" xml:lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title>出力画面</title>
</head>
<body>
<h1>出力画面</h1>
<p>こんにちは、${f:out(flash['name'])}さん。</p>
</body>
</html>

HogeAction#validateを新しく作り、そこにポストさせ、その後processメソッドにリダイレクトさせています。 nameはリクエストスコープなので、リダイレクトされた先のprocessメソッドでは有効にならない、ので一旦フラッシュメッセージに退避させ、JSPの中で取り出す、と言う動作になります。

大切なのは、「POSTされたメソッドの中でForwardさせてはダメ、別のメソッドにリダイレクトさせて、その中でForward」ってことです。

さて、このnameに「0文字以上、10文字以下」と言う条件の@Validationを付けようと思うのですが、そうすると、また面倒なことに…。

Cubbyこう言うところが難しいよね…。 #3 @Validation解決編

前述*4の「nameに0文字以上、10文字以下というバリデーションルールを書く」をno titleを参考に、書いてみますか。

	// バリデーションのルール
	public ValidationRules validationRules = new DefaultValidationRules() {
		@Override
		public void initialize() {
			// フィールド "name" は必須入力で最大10文字まで
			add("name", new RequiredValidator(), new MaxLengthValidator(10));
		}
	};

// 中略

	@Accept(RequestMethod.POST)
	@Validation(rules = "validationRules", errorPage = "...") 
	// ↑バリデーションがNGの時フォワードされちゃう!!
	public ActionResult validate() {
		flash.put("name", name);
		return new Redirect("process");
	}

まぁ、コメントに書いた通りです。 NG時にerrorPage="..."にフォーワードされてしまう、本来だったら、HogeAction#inputにリダイレクトしたいのに…。

この解決方法もあるんです。

	/** バリデーションのルール */
	public static final ValidationRules validationRules = new DefaultValidationRules() {
		@Override
		public void initialize() {
			// フィールド "name" は必須入力で最大10文字まで
			add("name", new RequiredValidator(), new MaxLengthValidator(10));
		}
		
		@Override
		public ActionResult fail(String errorPage) {
			return new Redirect(errorPage);
		}
	};

// 中略

	@Accept(RequestMethod.POST)
	@Validation(rules = "validationRules", errorPage = "input") 
	public ActionResult validate() {
		flash.put("name", name);
		return new Redirect("process");
	}

@Validationの中でNGになった時呼ばれるのは、ValidationRules#failなので、それをオーバーライドして、リダイレクトにしてやれば良い、と。

public abstract class RedirectValidationRules extends DefaultValidationRules {
	@Override
	public ActionResult fail(String errorPage) {
		return new Redirect(errorPage);
	}
}
  • RedirectValidationRules.java

大抵共通的に使われるものなので、上記のように抽象クラスを定義しておけばよいかもしれませんね。

…まぁ、inputメソッドに戻ってきた時、リクエストスコープであるnameは消えてしまってるんで、validationRulesの中でセッションに入れる処理、とか諸々実装しなくちゃいけないハードルあるんですけど。

謝辞

上記記事は、

  1. 改めて Cubby のライフサイクルを調べた - イトウ アスカ blog
  2. 「CubbyとMavenを使った開発のまとめスレ」の中のPDF資料
  3. SeasarConのセッション後にd:id:agt氏に質問した内容

を参考にして書きました。 ありがとうございました。

トラックバック - http://seasarproject.g.hatena.ne.jp/halflite/20090316