#freeze
[[FrontPage]]

#contents

2008/03/12からのアクセス回数 &counter;

* Spring-MVCのValidationでドメインモデルのString型以外の属性を検証したい [#ld2c61c5]
Formコントローラでエラーをチェックする場合、
- HTTPパラメータの値をcommandClassで指定されたオブジェクトに値を設定する
- オブジェクトの値のValidationを実施する
- HTTP要求を実行する

しかし、ドメインモデルのString型以外の属性に不適当な文字列を指定するとValidationの前の
段階でエラーとなり、Validation機能が有効に働きません。

以下は、MemberクラスにInteger型のageを追加し、入力として数値としてふさわしくない"b"
を入力したときのエラー出力画面です。

#ref(validation_error1.jpg);

GenMVCプラグインで実行したいと考えているプログラミングは、
- ドメインモデルを定義する
- ドメインモデルからデータベースのテーブルを生成する
- CRUDを実行するDaoを生成する
- 編集、一覧表示のコントローラを生成する
- ビューのひな形を生成する

という一連の流れにおいて、ドメインモデルの属性の型にStringしか使えないというのは
致命的な問題です。

** 解決方法 [#meaf5664]
悩んだあげく以下のような方法で対応することにしました。
-- ドメインモデルに対応したコマンドオブジェクトを生成する
-- ドメインモデルとコマンドオブジェクト間の値の変換を行うヘルパー関数を作成する

*** ドメインモデルに対応したコマンドオブジェクトの生成 [#geef7f54]
idを除くテーブル要素となる属性(テーブルのカラムに対応するもの)の型をすべてString型とし、
それ以外をそのままの型と持ち、それらのアクセスメソッドを生成します。

Cart問題のLineItemを定義しますと、
#pre{{
public class LineItem {
	private Integer	id;
	private Integer	quantity = new Integer(1);
	private Integer	productId;
	private Product	product;
	private Integer	orderId;
	-- アクセスメソッドは省略
}
}}

から以下のようなコマンドオブジェクトが生成されます。
#pre{{
package example.cart.command;

public class LineItemCommand {
	private Integer	id;
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	private String	orderId;
	public String	getOrderId () {
		return orderId;
	}
	public void setOrderId (String orderId) {
		this.orderId = orderId;
	}
	-- productId, quantity は、省略
	private example.cart.domain.Product	product;
	public example.cart.domain.Product	getProduct () {
		return product;
	}
	public void setProduct (example.cart.domain.Product product) {
		this.product = product;
	}
}

}}

*** ドメインモデルとコマンドオブジェクト間の変換関数 [#g46a239f]
先ほどのLineItemの編集を行うEditLineItemControllerを見てみると

#pre{{
	protected void bind(Object target, Object source)  throws Exception {
		CustomDataBinder binder = new CustomDataBinder(target, source);
		this.prepareBinder(binder);
		binder.bind();				
	}
	
	protected Object commandToDomain(Object source) throws Exception {
		Object object = new LineItem();
		bind(object, source);
		return (object);
	}

	protected Object domainToCommand(Object source) throws Exception {
		Object object = new LineItemCommand();
		bind(object, source);
		return (object);
	}
}}

-- bindメソッドでsourceからtargetに値をbind(変換してセット)しています
-- commandToDomainでコマンドオブジェクトからドメインモデルに変換します
-- domainToCommandでドメインモデルからコマンドオブジェクトに変換します

*** CustomDataBinderの処理 [#h0673ec5]
このように簡単にコマンドオブジェクトとドメインモデルの属性間の値変換ができるのは、
ServletRequestDataBinderとCustomDataBinderのおかげです。

CustomDataBinderでは、
-- コピー元のオブジェクトの属性からテーブルにマッピングした属性をbindします
-- それ以外の属性はそのまま値をコピーします

以下にCustomDataBinderのソースを示します。
#pre{{
package jp.co.pwv.utils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.co.pwv.utils.DbHelper;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.web.bind.ServletRequestDataBinder;

public class CustomDataBinder extends ServletRequestDataBinder {
	DbHelper	helper;
	Map			valueMap = new HashMap();
	
	private void setUpValueMap(Object source) {
		List propList = helper.getPropList();
		for (int i = 0; i < propList.size(); i++) {
			PropertyDescriptor objProp = (PropertyDescriptor)propList.get(i);
			String	propName = objProp.getName().toUpperCase();
			Class	propType = objProp.getPropertyType();
			Method	getMethod = objProp.getReadMethod();
			Object	propValue = null;
			try {
				propValue = getMethod.invoke(source, null);
			}
			catch (Exception e) {
				propValue = null;
			}
			if (propName.equals("CLASS"))	// オブジェクト自身を除く
				continue;	
			if (propValue != null)
				if (Integer.class.equals(propType) ||
						Double.class.equals(propType) ||
						Boolean.class.equals(propType) ||
						String.class.equals(propType) ||
						Date.class.equals(propType) ||
						java.util.Date.class.equals(propType) ||
						Timestamp.class.equals(propType)) {
					valueMap.put(objProp.getName(), propValue.toString());
				}
				else {
					valueMap.put(objProp.getName(), propValue);					
				}
		}				
	}
	
	public CustomDataBinder(Object target, Object source) {
		super(target);
		helper = new DbHelper(source.getClass());
		setUpValueMap(source);
	}
	
	public void bind() throws Exception {
		MutablePropertyValues mpvs = new MutablePropertyValues();
		mpvs.addPropertyValues(valueMap);
		super.doBind(mpvs);
	}
}
}}

** 動作の確認 [#bf3f71b8]
validation.xmlのmemberCommandのフォームのageのチェックをrequired,integerに変更します。
#pre{{
		<form name="memberCommand">
			<field property="address" depends="required">
				<arg0 key="member.address" />
			</field>
			<field property="age" depends="required,integer">
				<arg0 key="member.age" />
			</field>
			<field property="name" depends="required">
				<arg0 key="member.name" />
			</field>
		</form>
}}

mavenでパッケージ、jettyを起動します。

#pre{{
$ mvn package
$ mvn jetty:run
}}

正常に動作し、"Member age must be an integer"のメッセージが出力しました。

#ref(validation_ok.jpg);

* コメント [#oace2596]
この記事は、

#vote(おもしろかった[0],そうでもない[0],わかりずらい[4])
#vote(おもしろかった[0],そうでもない[0],わかりずらい[5])

皆様のご意見、ご希望をお待ちしております。

#comment_kcaptcha

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
SmartDoc