10. 値の検証方法

10.1. 検証の基本

XlsMapperの検証は、SpringFrameworkのValidation機構に似た方法をとります。

  • エラー情報は、 SheetBindingErrors クラスに格納します。

    • SheetBindingErrorsのインスタンスは、メソッド XlsMapper#loadDetail(...) のように、XlsMapperのメソッド 'xxxDetail()' から取得できます。

  • セルの値をJavaオブジェクトへ変換の失敗情報は、読み込み時に自動的に作成され、格納されます。

    • 1つのセルの型変換に失敗しても処理を続行するよう、 システム設定 のプロパティ continueTypeBindFailure を 'true' に設定します。

  • 型変換以外の検証は、独自に実装したBeanに対するValidatorにより、値を検証します。

    • 通常は、抽象クラス ObjectValidatorSupport を継承して作成します。

    • @XlsHorizontalRecords のようにネストしたBeanの場合、リストの要素のBeanのValidatorを別途用意します。

  • エラー情報は、SheetBindingErrorsに、エラーオブジェクト ObjectError として格納されます。

    • ObjectErrorには、メッセージコードや引数が保持されているため、SheetErrorFormatter を使用して、エラーオブジェクトを文字列に変換します。

 1XlsMapper xlsMapper = new XlsMapper();
 2
 3// 型変換エラーが起きても処理を続行するよう設定
 4xlsMapper.getConiguration().setContinueTypeBindFailure(true);
 5
 6// エラー情報を含んだ詳細な戻り値を取得します。
 7SheetBindingErrors<Employer> bindingResult = xlsMapper.loadDetail(xlsIn, Employer.class);
 8
 9// マッピングしたオブジェクトを取得します。
10Employer bean = bindingResult.getTarget();
11
12// BeanのValidatorを実行します。
13EmployerValidator validator = new EmployerValidator();
14validator.validate(bean, errors);
15
16// グループを指定する場合(クラスで指定する)
17// validator.validate(bean, errors, Hint.class);
18
19// 値の検証結果を文字列に変換します。
20if(errors.hasErrors()) {
21    SheetErrorFormatter errorFormatter = new SheetErrorFormatter();
22    for(ObjectError error : errors.getAllErrors()) {
23        String message = errorFormatter.format(error);
24    }
25}

10.2. 独自の入力値検証

Validatorは、 ObjectValidatorSupport を継承して作成します。

  • Genericsで検証対象のBeanクラスを指定し、validateメソッド内で検証処理の実装を行います。

  • 検証対象のフィールドにエラーがあるかどうかは、 SheetBindingErrors#hasFieldErrors でチェックできます。

    • 型変換エラーがある場合、Beanのプロパティに値がマッピングされていないため、エラーがあるかどうかをチェックします。

  • フィールドに対するエラーを設定する場合、SheetBindingErrors#createFieldError("フィールド名", "エラーコード") で設定します。

    • ビルダクラス InternalFieldErrorBuilder を使って組み立てます。

    • Map<String, Points>フィールドでセルのアドレスを保持している場合は、InternalFieldErrorBuilder#address(...) でセルのアドレスを指定できます。

    • エラー引数は、インデックス形式の配列型と名前付きのマップ型のどちらでも指定できます。名前付きのマップ型の利用をお勧めします。

  • Validatoinの実行時には、BeanValdiation(JSR-303)のように、ヒントとなるグループ情報を渡すことがき、もし、値が渡されたら、特定の判定を行ったりもできます。

 1public class EmployerValidator extends ObjectValidatorSupport<Employer> {
 2
 3    // ネストしたBeanのValidator
 4    private EmployerHistoryValidator historyValidator;
 5
 6    public EmployerValidator() {
 7        this.historyValidator = new EmployerHistoryValidator();
 8    }
 9
10    @Override
11    public void validate(Employer targetObj SheetBindingErrors errors, Class<?>... groups) {
12
13        // 型変換などのエラーがない場合、文字長のチェックを行う
14        // チェック対象のフィールド名を指定します。
15        if(!errors.hasFieldErrors("name")) {
16            if(targetObj.getName().length() > 10) {
17
18                // 名前付きの引数、セルのアドレスを渡す場合の指定
19                errors.createFieldError("name", "error.maxLength")
20                    .address(targetObj.positions("name"))
21                    .variables("max", 10)
22                    .buildAndAddError();
23            }
24        }
25
26        // レコードの要素の値の検証
27        for(int i=0; i < targetObj.getHistory().size(); i++) {
28            // ネストしたBeanの検証の実行
29            // パスをネストする。リストの場合はインデックスを指定する。
30            errors.pushNestedPath("history", i);
31            historyValidator.validate(targetObj.getHistory().get(i), errors);
32            // 検証後は、パスを戻す
33            errors.popNestedPath();
34
35            // パスのネストと戻しは、invokeNestedValidatorで自動的にもできます。
36            // invokeNestedValidator(historyValidator, targetObj.getHistory().get(i), errors, "history", i);
37        }
38
39    }
40}

10.3. フィールド(プロパティ)の入力値検証

フィールドに対する値の検証は、 CellField クラスを使用することでもできます。

  • コンストラクタに検証対象のプロパティ名を指定します。プロパティ名には、ネストしたもの、配列・リストやマップの要素の指定もできます。

    • ドット(.)で繋げることで、階層指定ができます(例: person.name )。

    • 括弧([数値])を指定することで、配列またはリストの要素が指定できます(例: list[0] )。

    • 括弧([キー名])を指定することで、マップの値が指定できます(例: map[abc] )。

    • 組み合わせることもできます(例: data[0][abc].name )。

  • フィールドに対する検証を CellField#add(...) で追加することで複数の検証を設定できます。

  • 値の件所を行う場合は、 CellField#validate(errors) で実行します。

    • SheetBindingErrorsに対してエラーオブジェクトが自動的に設定されます。

  • フィールドに対してエラーがある場合、 CellField#hasErrors(...)/hasNotErrors(...) で検証できます。

 1public class EmployerHistoryValidator extends ObjectValidatorSupport<EmployerHistory> {
 2
 3    @Override
 4    public void validate(EmployerHistory targetObj, SheetBindingErrors errors, Class<?>... groups) {
 5
 6        // プロパティ historyDate に対するフィールドの組み立てと値の検証
 7        final CellField<Date> historyDateField = new CellField<Date>("historyDate", errors);
 8        historyDateField.setRequired(true)
 9            .add(new MinValidator<Date>(new Date(), "yyyy-MM-dd"))
10            .validate(groups);
11
12
13        // プロパティ comment に対するフィールドの組み立てと値の検証
14        final CellField<String> commentField = new CellField<String>("comment", errors);
15        commentField.setRequired(false)
16            .add(StringValidator.maxLength(5))
17            .validate(groups);
18
19        //
20        if(historyDateField.hasNotErrors() && commentField.hasNotErrors()) {
21            // 項目間のチェックなど
22            if(commentField.isInputEmpty()) {
23                errors.createGlobalError("error.01").buildAndAddError();
24            }
25        }
26
27    }
28}

注釈

アノテーション @XlsLabelledArray や @XlsArrayColumns などを使ってフィールドが配列やリストへにマッピングした値を検証する場合、 ArrayCellField を使用します。 [ver.2.0+]

使用方法は、CellFieldと変わりません。

10.4. メッセージファイルの定義

メッセージファイルは、クラスパスのルートに SheetValidationMessages.properties というプロパティファイルを配置しておくと、自動的に読み込まれます。

  • プロパティファイルは、文字コードをUTF-8に設定し、asciiコードへの変換は不要です。 [ver.2.0+]

  • エラーメッセージは、下記の表「エラーメッセージの一致順」に従い一致したものが用いられます。

    • 型変換エラーは、読み込み時に自動的にチェックされ、エラーコードは、 cellTypeMismatch と決まっています。

  • メッセージ中ではEL式を利用できます。

  • メッセージ中の通常の変数は、{変数名} で定義し、EL式は ${EL式} で定義します。

    • ただし、EL式のライブラリを依存関係に追加しておく必要があります。

 1## メッセージの定義
 2## SheetValidationMessages.properties
 3
 4# 共通変数
 5# {sheetName} : シート名
 6# {cellAddress} : セルのアドレス。'A1'などの形式。
 7# {label} : フィールドの見出し。
 8
 9# フィールドエラー
10cellFieldError.patern==[{sheetName}]:${empty label ? '' : label} - {cellAddress}は'書式に一致しませんでした。
11
12# 型変換エラー
13cellTypeMismatch=[{sheetName}]:${empty label ? '' : label} - {cellAddress}の型変換に失敗しました。
14
15# クラスタイプで指定する場合
16cellTypeMismatch.int=[{sheetName}]:${empty label ? '' : label} - {cellAddress}は数値型で指定してください。
17cellTypeMismatch.java.util.Date=[{sheetName}]:${empty label ? '' : label} - {cellAddress}は日付型で指定してください。
18
19# フィールド名で指定する場合
20cellTypeMismatch.updateTime=[{sheetName}]:${empty label ? '' : label} - {cellAddress}は'yyyy/MM/dd'の書式で指定してください。
表 - 10.4.1 エラーメッセージの一致順

優先順位

エラーコードの形式

サンプル

1

<エラーコード>.<完全オブジェクト名>.<完全パス>.<フィールド名>

cellFieldError.pattern.com.sample.SampleBean.list[1].address

2

<エラーコード>.<完全オブジェクト名>.<パス>.<フィールド名>

cellFieldError.pattern.com.sample.SampleBean.list.address

3

<エラーコード>.<完全オブジェクト名>.<フィールド名>

cellFieldError.pattern.com.sample.SampleBean.address

4

<エラーコード>.<オブジェクト名>.<完全パス>.<フィールド名>

cellFieldError.pattern.SampleBean.list[1].address

5

<エラーコード>.<オブジェクト名>.<パス>.<フィールド名>

cellFieldError.pattern.SampleBean.list.address

5

<エラーコード>.<オブジェクト名>.<フィールド名>

cellFieldError.pattern.SampleBean.address

6

<エラーコード>.<完全パス>.<フィールド名>

cellFieldError.pattern.list[1].address

7

<エラーコード>.<パス>.<フィールド名>

cellFieldError.pattern.list.address

8

<エラーコード>.<フィールド名>

cellFieldError.pattern.address

9

<エラーコード>.<フィールドのクラスタイプ>

cellFieldError.pattern.java.lang.String

10

<エラーコード>

cellFieldError.pattern

注釈

メッセージ中で、セルのアドレス(変数{cellAddress})、ラベル(変数{label})を利用したい場合は、 Beanクラスに位置情報を保持するフィールド Map<String, Point> positions と ラベル情報を保持する Map<String, String> labels を定義しておく必要があります。

10.5. メッセージファイルの読み込み方法の変更

メッセージファイルは、java.util.ResourceBundlejava.util.Properties 、またSpringの org.springframework.context.MessageSource からも取得できます。 設定する場合、SheetErrorFormatter#setMessageResolver(...) で対応するクラスを設定します。

表 - 10.5.1 メッセージファイルのブリッジ用クラス

XlsMapper提供のクラス

メッセージ取得元のクラス

com.gh.mygreen.xlsmapper.validation.ResourceBundleMessageResolver

java.util.ResourceBundle

com.gh.mygreen.xlsmapper.validation.PropertiesMessageResolver

java.util.Prperties

com.gh.mygreen.xlsmapper.validation.SpringMessageResolver

org.springframework.context.MessageSource

// SpringのMessageSourceからメッセージを取得する場合
MessageSource messageSource = /*...*/;

SheetErrorFormatter errorFormatter = new SheetErrorFormatter();
errorFormatter.setMessageResolver(new SpringMessageResolver(messageSource));

10.6. Bean Validationを使用した入力値検証

Bean Validationを利用する際には、ライブリを追加します。 Mavenを利用している場合は、pom.xml にライブラリを追加します。

  • 本ライブラリは、Bean Validation 1.0/1.1/2.0及び、Jakarta Bean Validaiton 3.0/3.1に対応してします。

    • Bean Validation 1.0/1.1/2.0では、 com.gh.mygreen.xlsmapper.validation.beanvalidation.SheetBeanValidator を使用して値の検証します。

    • Jakarta Bean Validation 3.xでは、 com.gh.mygreen.xlsmapper.validation.beanvalidation.JakartaSheetBeanValidator を使用して値の検証します。

  • Bean Validationの実装として、Hibernate Validator が必要になるため、依存関係に追加します。

    • Hibernate Validatorを利用するため、メッセージをカスタマイズしたい場合は、クラスパスのルートに「ValidationMessages.properties」を配置します。

  • 検証する際には、 SheetBeanValidator#validate(...) / JakartaSheetBeanValidator#validate(...) を実行します。

    • Bean Validationの検証結果も、SheetBindingErrors の形式に変換され格納されます。

  • メッセー時を出力する場合は、SheetErrorFormatter を使用します。

Hibernate Validatorは対応するBean Validaitonのバージョンが決まっているため、対応したライブラリのバージョンを追加する必要があります。

表 - 10.6.1 Bean ValidationとHibernate Validatorの対応バージョン

Bean Validation

Hibernate Validator

備考

ver.1.0 (JSR-303)

ver.4.x

Java8以上で利用可能です。

ver.1.1 (JSR-349)

ver.5.x

Java8以上で利用可能です。

ver.2.0 (JSR-380)

ver.6.x

Java8以上で利用可能です。

ver.3.0/3.1

ver.8.x

XlsMapper 2.3+ から対応しています。
Hibernate Validator v8.xから、Java11以上 が必須になります。
Bean Validaitonから、Jakarta Bean Validationに名称が変更されパッケージも変更されています。
 1XlsMapper xlsMapper = new XlsMapper();
 2
 3// 型変換エラーが起きても処理を続行するよう設定
 4xlsMapper.getConiguration().setContinueTypeBindFailure(true);
 5
 6// シートの読み込み
 7SheetBindingErrors<Employer> errors = xlsMapper.loadSheetDetail(new File("./src/test/data/employer.xlsx"), errors);
 8
 9// Bean Validation による検証の実行
10SheetBeanValidator validatorAdaptor = new SheetBeanValidator();
11
12// Jakarta Bean Validation による検証の実行
13//JakartaSheetBeanValidator validatorAdaptor = new JakartaSheetBeanValidator();
14
15validatorAdaptor.validate(beanObj, errors);
16
17// 値の検証結果を文字列に変換します。
18if(errors.hasErrors()) {
19    SheetErrorFormatter errorFormatter = new SheetErrorFormatter();
20    for(ObjectError error : errors.getAllErrors()) {
21        String message = errorFormatter.format(error);
22    }
23}
コード - 10.6.1 Bean Validation 1.1 の依存ライブラリ
 1<!-- ====================== Bean Validationのライブラリ ===============-->
 2<dependency>
 3    <groupId>javax.validation</groupId>
 4    <artifactId>validation-api</artifactId>
 5    <version>1.1.0.Final</version>
 6    <scope>provided</scope>
 7</dependency>
 8<dependency>
 9<groupId>org.hibernate</groupId>
10    <artifactId>hibernate-validator</artifactId>
11    <version>5.3.3.Final</version>
12    <scope>provided</scope>
13</dependency>
14<dependency>
15    <groupId>org.glassfish</groupId>
16    <artifactId>javax.el</artifactId>
17    <version>3.0.1-b10</version>
18    <scope>provided</scope>
19</dependency>
コード - 10.6.2 Bean Validation 2.0 の依存ライブラリ
 1<!-- ====================== Bean Validationのライブラリ ===============-->
 2<dependency>
 3    <groupId>javax.validation</groupId>
 4    <artifactId>validation-api</artifactId>
 5    <version>2.0.1.Final</version>
 6    <scope>provided</scope>
 7</dependency>
 8<dependency>
 9<groupId>org.hibernate</groupId>
10    <artifactId>hibernate-validator</artifactId>
11    <version>6.0.20.Final</version>
12    <scope>provided</scope>
13</dependency>
14<dependency>
15    <groupId>org.glassfish</groupId>
16    <artifactId>javax.el</artifactId>
17    <version>3.0.1-b10</version>
18    <scope>provided</scope>
19</dependency>

Jakarta Bean Validation 3.1を利用する場合は、Hibernate Validator8.x系を利用します。 さらに、メッセージ中にJakarta EEのEL式が利用可能となっているため、その実装であるライブリを追加します。

コード - 10.6.3 Jakarta Bean Validation 3.1 の依存ライブラリ
 1<!-- ====================== Jakarta Bean Validationのライブラリ ===============-->
 2<dependency>
 3    <groupId>jakarta.validation</groupId>
 4    <artifactId>jakarta.validation-api</artifactId>
 5    <version>3.1.0</version>
 6</dependency>
 7<dependency>
 8    <groupId>org.hibernate.validator</groupId>
 9    <artifactId>hibernate-validator</artifactId>
10    <version>8.0.2.Final</version>
11</dependency>
12
13<!-- EL式のライブラリが必要であれば追加します -->
14<dependency>
15    <groupId>org.glassfish.expressly</groupId>
16    <artifactId>expressly</artifactId>
17    <version>5.0.0</version>
18</dependency>

10.6.1. Bean Validationのカスタマイズ

BeanValidationのメッセージファイルを他のファイルやSpringのMessageSourcesから取得することもできます。

XlsMapperのクラス com.gh.mygreen.xlsmapper.validation.beanvalidation.MessageInterpolatorAdapter を利用することで、BeanValidationのメッセージ処理クラスをブリッジできます。

  • Jakarta Bean Validaitonの場合は、com.gh.mygreen.xlsmapper.validation.beanvalidation.JakartaMessageInterpolatorAdapter を使用してください。

上記の「メッセージファイルのブリッジ用クラス」を渡すことができます。

コード - 10.6.4 Bean Validation によるメッセージのカスタマイズ
 1// BeanValidationのValidatorの定義
 2ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure()
 3        .messageInterpolator(new MessageInterpolatorAdapter(
 4                .new ResourceBundleMessageResolver(), new MessageInterpolator()))
 5        .buildValidatorFactory();
 6Validator validator = validatorFactory.usingContext()
 7        .getValidator();
 8
 9// Bean ValidationのValidatorを渡す
10SheetBeanValidator sheetValidator = new SheetBeanValidator(validator);

Bean Validation1.1から式中にEL式が利用できるようになりましたが、その参照実装であるHibernate Validator5.xでは、EL2.x系を利用し、EL3.xの書式は利用できません。 EL式の処理系をXlsMapperのクラス com.gh.mygreen.xlsmapper.validation.MessageInterpolator を利用することでEL式の処理系を変更できます。

XslMapperの ExpressionLanguageELImpl は、EL3.0のライブラリが読み込まれている場合、3.x系の処理に切り替えます。

 1// BeanValidatorの式言語の実装を独自のものにする。
 2ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure()
 3        .messageInterpolator(new MessageInterpolatorAdapter(
 4                // メッセージリソースの取得方法を切り替える
 5                new ResourceBundleMessageResolver(ResourceBundle.getBundle("message.OtherElMessages")),
 6
 7                // EL式の処理を切り替える
 8                new MessageInterpolator(new ExpressionLanguageELImpl())))
 9        .buildValidatorFactory();
10Validator validator = validatorFactory.usingContext()
11        .getValidator();
12
13// BeanValidationのValidatorを渡す
14SheetBeanValidator sheetValidator = new SheetBeanValidator(validator);