FrontPage

2008/03/12からのアクセス回数 5370

Spring-MVCのValidationでドメインモデルのString型以外の属性を検証したい

Formコントローラでエラーをチェックする場合、

  • HTTPパラメータの値をcommandClassで指定されたオブジェクトに値を設定する
  • オブジェクトの値のValidationを実施する
  • HTTP要求を実行する

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

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

validation_error1.jpg

GenMVCプラグインで実行したいと考えているプログラミングは、

  • ドメインモデルを定義する
  • ドメインモデルからデータベースのテーブルを生成する
  • CRUDを実行するDaoを生成する
  • 編集、一覧表示のコントローラを生成する
  • ビューのひな形を生成する

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

解決方法

悩んだあげく以下のような方法で対応することにしました。

  • ドメインモデルに対応したコマンドオブジェクトを生成する
  • ドメインモデルとコマンドオブジェクト間の値の変換を行うヘルパー関数を作成する

ドメインモデルに対応したコマンドオブジェクトの生成

idを除くテーブル要素となる属性(テーブルのカラムに対応するもの)の型をすべてString型とし、 それ以外をそのままの型と持ち、それらのアクセスメソッドを生成します。

Cart問題のLineItemを定義しますと、

public class LineItem {
	private Integer	id;
	private Integer	quantity = new Integer(1);
	private Integer	productId;
	private Product	product;
	private Integer	orderId;
	-- アクセスメソッドは省略
}

から以下のようなコマンドオブジェクトが生成されます。

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;
	}
}

ドメインモデルとコマンドオブジェクト間の変換関数

先ほどのLineItemの編集を行うEditLineItemControllerを見てみると

	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の処理

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

CustomDataBinderでは、

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

以下にCustomDataBinderのソースを示します。

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);
	}
}

動作の確認

validation.xmlのmemberCommandのフォームのageのチェックをrequired,integerに変更します。

		<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を起動します。

$ mvn package
$ mvn jetty:run

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

validation_ok.jpg

コメント

この記事は、

選択肢 投票
おもしろかった 0  
そうでもない 0  
わかりずらい 3  

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


(Input image string)


添付ファイル: filevalidation_ok.jpg 405件 [詳細] filevalidation_error1.jpg 405件 [詳細]

トップ   編集 凍結解除 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-10-15 (火) 13:22:27 (1291d)
SmartDoc