JSR 303: Bean Validator
既報の通り、ついにエンタープライズJavaの次期バージョンであるJava EE 6の仕様策定がJSR 313としてスタートした。本連載でも時期を見てJSR 313やそこに含まれるコンポーネントJSRの詳細を取り上げていく予定だが、エンタープライズ分野に携わる開発者はjcp.orgなどで公表される情報に目を光らせておくといいだろう。
さて、今回もJava SE 7に導入が予定されているAPIを紹介する。Java SE 7への導入が検討されているAPIにはアノテーションを活用したものも少なくない。本来ならば複雑な設定を必要とするような機能も、アノテーションを利用することで容易に利用できるようになるため、この流れは必然と言えるかもしれない。今回はそんなアノテーションを活用したAPIのひとつである「JSR 303: Bean Validator」を取り上げる。
JSR 303はJava Beanオブジェクトのためのバリデーション機構やメタデータモデルを実現する目的で発足した。このAPIを使用することで、Java Beanのプロパティが取り得る値の範囲や条件を設定し、実行時に統一的な方法でそれを検証できるようになる。これによってWebアプリケーションやGUIアプリケーションの入力値検証や、オブジェクトの永続化の際のデータ検証などが容易に行えるようになる。
JSR 303では一般的なバリデーション定義のためのアノテーションを用意し、これをプロパティに指定するだけで条件の指定ができるようにするとのことである。これには数値プロパティの最大値/最小値の指定や、文字列の長さやフォーマットの指定などが含まれるだろう。それに加えて、XMLバリデーションデスクリプタを利用して独自に定義を拡張/オーバーライドできる機構も提供するという。
このような機能を提供してくれるフレームワークはすでにいくつか実在しており、JSR 303の仕様もそれらの影響を受けたものになるだろう。ここではそんなバリデーションフレームワークのうち、代表的なものを2つ取り上げて紹介する。
バリデーションフレームワークの例 その1 - Hibernate Validator
まず最初に紹介するのが、Hibernateプロジェクトによって開発されている「Hibernate Validator」だ。Hibernate Validatorは、HibernateによるJavaオブジェクトの永続化の際にオブジェクトの状態を検証する目的で開発されたが、Hibernate以外の永続化フレームワークや、スタンドアロン・アプリケーションでも利用することができる。Hibernate Validatorはバリデーション定義をアノテーションによって記述することができ、この点がJSR 303の目指すものとよく似ている。
Hibernate Validatorはこのサイトより入手できる。最新バージョンは3.0.0 GAで、使用するにはHibernate Coも必要なので同時にダウンロードしておこう。
本稿のサンプルは、両配布ファイルに含まれる以下の4つのファイルをクラスパスに追加してコンパイル/実行する。
- hibernate-validator.jar
- hibernate-commons-annotation.jar
- hibernate3.jar
- commons-logging-1.0.4.jar
Hibernate Validatorでは、Beanのプロパティにアノテーションを付加することでバリデーション定義を行う。たとえばリスト1のような感じだ。ここではusernameおよびpasswordに文字数の制限を、birthdayに未来の日付を使用できないようにする制限を、emailにE-mail形式の文字列のみに限定する制限を設けている。
リスト1 UserBean.java
package apisample;
import java.util.Date;
import org.hibernate.validator.Email;
import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;
import org.hibernate.validator.Past;
public class UserBean {
@Length(min=4,max=16,message="ユーザ名は4~16文字で入力してください。")
@NotEmpty
private String username;
@Length(min=4,max=8,message="パスワードは4~8文字で入力してください。")
@NotEmpty
private String password;
@Past(message="未来の日付が入力されました。")
private Date birthday;
@Email(message="メールアドレスは[xxxx@xxxx.xxx]の形式で入力してください。")
private String email;
public UserBean(String username, String password, Date birthday, String email) {
this.username = username;
this.password = password;
this.birthday = birthday;
this.email = email;
}
/** Setter/GetterおよびtoString()は省略 **/
}
実際の検証はリスト2のようにして行う。まずUserBeanクラスを指定してValodatorインスタンスを生成し、getInvalidValuesメソッドに検証したいBeanオブジェクトを渡して検証を実行する。このメソッドは検証結果をInvalidValueの配列で返す。
リスト2 HibernateValidatorSample.java
package apisample;
import java.util.Calendar;
import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;
public class HibernateValidatorSample {
public static void main(String[] args) {
// 正しい値を設定した例
Calendar birthday1 = Calendar.getInstance();
birthday1.set(2006,4,1);
UserBean user1 = new UserBean("mycom","jurnal",birthday1.getTime(),"mycom@example.jp");
executeValidation(user1);
// 誤った値を設定した例
Calendar birthday2 = Calendar.getInstance();
birthday2.set(2008,4,1);
UserBean user2 = new UserBean("mj","mycomjurnal",birthday2.getTime(),"mycomexample.jp");
executeValidation(user2);
}
/**
* Beanのプロパティ値の検証を行う
*/
private static void executeValidation(UserBean user) {
ClassValidator userValidator = new ClassValidator(UserBean.class);
InvalidValue[] validationMessages = userValidator.getInvalidValues(user);
System.out.println(user + "を検証します。");
for (InvalidValue message: validationMessages) {
System.out.println(" ERROR: " + message);
}
}
}
このプログラムを実行するとプロンプト1のようになる。user2は各プロパティに誤った値を設定してあるので、検証結果としてエラーメッセージが出力される。
プロンプト1 HibernateValidatorSample.javaの実行例
> java -cp [PATH_TO_JARFILES] apisample/HibernateValidatorSample
UserBean[mycom,jurnal,Mon May 01 21:50:05 JST 2006, mycom@example.jp]を検証します。
UserBean[mj,mycomjurnal,Thu May 01 21:50:05 JST 2008, mycomexample.jp]を検証します。
ERROR: username ユーザ名は4~16文字で入力してください。
ERROR: password パスワードは4~8文字で入力してください。
ERROR: birthday 未来の日付が入力されました。
ERROR: email メールアドレスは[xxxx@xxxx.xxx]の形式で入力してください。
Hibernate Validatorではここで示した以外にもさまざまなアノテーションが用意されているほか、独自にバリデータを拡張して使用することもできる。
バリデーションフレームワークの例 その2 - Commons Validator
JSR 303ではアノテーションによるバリデーション定義に加えて、XMLバリデーションデスクリプタによる定義の拡張もサポートする予定となっている。そこでこれに似た機能を持つフレームワークとしてCommons Validationを紹介しよう。
Commons ValidatorはApache FoundationのJakarta Commonsプロジェクトにおいて開発されているバリデーションフレームワークであり、XMLによって独自にバリデーション定義を拡張するための機構を有している。
Commons Validatorはこのサイトからダウンロードすることができる。ただし、Commons Validatorを使用するのは以下のツールも必要になるため、合わせて入手しておく必要がある。これら各ツールに含まれるJARファイルをクラスパスに追加して使用する。
今回はリスト3のようなJava Beanを検証するバリデータを定義してみる。
リスト3 UserBean2.java
package apisample;
public class UserBean2 {
private String username;
private String email;
public UserBean2(String username, String email) {
this.username = username;
this.email = email;
}
/** Setter/GetterおよびtoString()は省略 **/
}
実際に検証を行うプログラムはリスト4のように記述する。validateRequiredメソッドは、文字列プロパティがnullや空文字列でなく、かつ文字数が4 - 16文字であるかどうかを検証する。validateEmailメソッドは文字列がE-mailアドレス形式になっているかどうかを検証する。いずれもJava BeanオブジェクトとフィールドFieldオブジェクトを引数に取る。
リスト4 UserBeanValidator.java
package apisample;
import org.apache.commons.validator.Field;
import org.apache.commons.validator.GenericValidator;
import org.apache.commons.validator.util.ValidatorUtils;
public class UserBeanValidator {
/**
* 値がnullや空文字列でなく、かつ4文字以上16文字以内である
*/
public static boolean validateRequired(Object bean, Field field)
{
String value =
ValidatorUtils.getValueAsString(bean, field.getProperty());
boolean isNotBlankOrNull = !GenericValidator.isBlankOrNull(value);
boolean isValidLength = GenericValidator.minLength(value, 4)
&& GenericValidator.maxLength(value, 16);
return isNotBlankOrNull && isValidLength;
}
/**
* 値がE-mailの形式になっている
*/
public static boolean validateEmail(Object bean, Field field)
{
String value =
ValidatorUtils.getValueAsString(bean, field.getProperty());
return GenericValidator.isEmail(value);
}
}
バリデーション定義はXMLでリスト5のように記述する。Commons ValidatorのバリデーションエンジンはこのXMLを参照して、検証に使用するクラス(今回はUserBeanValidatorクラス)を特定する。クラスは
<form>タグでは<validation>タグによるバリデータ定義をJava Beanのプロパティに関連付ける。ここではusernameプロパティを論理名"required"のバリデータに、emailプロパティを論理名"email"のバリデータに関連付けている。
リスト5 validator-definition.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.1//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1.dtd">
<form-validation>
<global>
<validator name="required"
classname="apisample.UserBeanValidator"
method="validateRequired"
methodParams=
"java.lang.Object, org.apache.commons.validator.Field"
msg="required.field"/>
<validator name="email"
classname="apisample.UserBeanValidator"
method="validateEmail"
methodParams=
"java.lang.Object, org.apache.commons.validator.Field"
msg="invalid.email"/>
</global>
<formset>
<form name="userInfo">
<field property="username" depends="required">
<arg0 key="userInfo.username.displayname"/>
</field>
<field property="email" depends="email">
<arg0 key="userInfo.email.displayname"/>
</field>
</form>
</formset>
</form-validation>
実際の検証はリスト6のようにして行う。まずバリデーション定義ファイル"validator-definition.xml"からValidatorResourcesを生成し、フォーム名を指定してそこからValidatorオブジェクトを生成する。検証はvalidateメソッドで実行し、結果はValidatorResultsオブジェクトとして帰ってくる。
リスト6 CommonsValidatorSample.java
package apisample;
import java.io.*;
import java.util.*;
import org.apache.commons.validator.Validator;
import org.apache.commons.validator.ValidatorAction;
import org.apache.commons.validator.ValidatorException;
import org.apache.commons.validator.ValidatorResources;
import org.apache.commons.validator.ValidatorResult;
import org.apache.commons.validator.ValidatorResults;
import org.xml.sax.SAXException;
public class CommonsValidatorSample {
public static void main(String[] args) {
// 正しい値を設定した例
UserBean2 user1 = new UserBean2("mycom", "mycom@example.jp");
executeValidation(user1);
// 誤った値を設定した例
UserBean2 user2 = new UserBean2("mj", "mycomexample.jp");
executeValidation(user2);
}
/**
* Beanのプロパティ値の検証を行う
*/
private static void executeValidation(UserBean2 user) {
System.out.println(user + "を検証します。");
try {
InputStream in = user.getClass().getResourceAsStream("validator-definition.xml");
ValidatorResources resources = new ValidatorResources(in);
Validator validator = new Validator(resources, "userInfo");
validator.setParameter(Validator.BEAN_PARAM, user);
ValidatorResults results = validator.validate();
// 検証結果を表示
Set propertyNames = results.getPropertyNames();
for (Object propertyName: propertyNames) {
ValidatorResult result = results.getValidatorResult((String)propertyName);
Iterator actions = result.getActions();
while (actions.hasNext()) {
String actionName = (String) actions.next();
if (!result.isValid(actionName)) {
ValidatorAction action = resources.getValidatorAction(actionName);
System.out.println(" ERROR: " + action.getMsg());
}
}
}
} catch (SAXException ex) {
ex.printStackTrace();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ValidatorException ex) {
ex.printStackTrace();
};
}
}
このプログラムを実行するとプロンプト2のようになる。
プロンプト2 CommonsValidatorSample.javaの実行例
> java -cp [PATH_TO_JARFILES] apisample/CommonsValidatorSample
UserBean2[mycom,mycom@example.jp]を検証します。
UserBean2[mj,mycomexample.jp]を検証します。
ERROR: required.field
ERROR: invalid.email
まとめ
JSR 303は、JSF(JavaServer Faces)やJPA(Java Persistence API)、Bean Bindingなどといった、Java Beansを利用するさまざまなAPIのコアコンポーネントとなることを目標として開発されている。そういう意味では、このJSRが他のAPIに与える影響は非常に大きい。当初の予定では2006年中にEarly Draft Reviewが行われる予定となっていたのでどうやら標準化作業は遅れ気味のようだが、近いうちに仕様の詳細が公開されることだろう。