5. 値の検証方法

5.1. 値の検証用の既存のアノテーション

既存のアノテーションとして、以下のものが用意されています。

表 - 5.1.1 値の検証方法を指定する既存のアノテーション(全てのクラスタイプ)

アノテーション

概要

参照

@CsvRequire

必須チェックを行います。

JavaDoc

@CsvEquals

指定した値と等しいか検証します。

JavaDoc

@CsvUnique

他のレコードの値と異なるか検証します。

JavaDoc

@CsvUniqueHashCode

他のレコードの値と異なるかハッシュコードにより検証します。

JavaDoc

表 - 5.1.2 値の検証方法を指定する既存のアノテーション(String型)

アノテーション

概要

参照

@CsvPattern

正規表現と一致するか検証します。

JavaDoc

@CsvLengthMin

指定した文字長以上か検証します。

JavaDoc

@CsvLengthMax

指定した文字長以内か検証します。

JavaDoc

@CsvLengthBetween

指定した文字長の範囲内か検証します。

JavaDoc

@CsvLengthExact

指定した文字長か検証します。

JavaDoc

@CsvLengthExact

指定した文字長か検証します。

JavaDoc

@CsvWordForbid

指定した語彙を含んでいないか検証します。

JavaDoc

@CsvWordRequire

指定した語彙を含んでいるか検証します。

JavaDoc

表 - 5.1.3 値の検証方法を指定する既存のアノテーション(数値型)

アノテーション

概要

参照

@CsvNumberMin

指定した下限値以上か検証します。

JavaDoc

@CsvNumberMax

指定した上限値以下か検証します。

JavaDoc

@CsvNumberRange

指定した値の範囲内か検証します。

JavaDoc

表 - 5.1.4 値の検証方法を指定する既存のアノテーション(日時型)

アノテーション

概要

参照

@CsvDateTimeMin

指定した値以降(下限値以上)か検証します。

JavaDoc

@CsvDateTimeMax

指定した値以前(上限値以下)か検証します。

JavaDoc

@CsvDateTimeRange

指定した値の期間内か検証します。

JavaDoc

5.1.1. 処理順序の指定

属性 order で処理順序を指定することができます。

  • 値が大きいほど後から実行されます。

  • 値が同じ場合は、アノテーションのFQCN(完全限定クラス名)の昇順で実行されます。

    • 属性orderを省略した場合は、デフォルト値 0 が適用されます。

    • ただし、必須チェック用の @CsvRequire は、初めに実行されるよう、属性orderのデフォルト値にはIntegerの最小値(-2147483648)が設定されています。

  • 読み込み時、書き込み時とも同じ処理順序になります。

  • 属性 order が付与されていないアノテーションは順番が付与されているものよりも後になります。

 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3
 4import com.github.mygreen.supercsv.annotation.constraint.*;
 5
 6@CsvBean
 7public class SampleCsv {
 8
 9    @CsvColumn(number=1)
10    @CsvRequire
11    @CsvUnique(order=2)
12    @CsvNumberMin(value="0", order=3)
13    private Integer value;
14
15    // getter/setterは省略
16}

5.1.2. 処理ケースの指定

属性 cases で、アノテーションを適用するケースとして「読み込み時」「書き込み時」を限定することができます。

  • 列挙型 BuildCase で指定します。

    • BuildCase.Read が読み込み時、 BuildCase.Write が書き込み時を表します。

  • 属性の値が空(配列が空)の場合、または、属性 cases を指定しない場合は、全てのケースに該当します。

  • 既存のアノテーションは、基本的に全て属性値が空が設定され、全てのケースに該当します。

 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.annotation.constraint.*;
 4import com.github.mygreen.supercsv.builder.BuildCase;
 5
 6@CsvBean
 7public class SampleCsv {
 8
 9    // 空白の場合、トリミングして空文字となった場合に入力値なしと判断して、nullに変換します。
10    @CsvColumn(number=1)
11    @CsvLengthMax(value=10, cases={})             // 全てのケースに適用
12    @CsvLengthMin(value=0, cases=BuildCase.Read)  // 読み込み時のみ適用
13    @CsvUnique(cases=BuildCase.Write)             // 書き込み時のみ適用
14    private String comment;
15
16    // getter/setterは省略
17}

5.1.3. グループの指定

属性 groups で、グループ用クラスを指定することで、属性 cases より柔軟に適用するケースをを限定することができます。

  • Bean Validation のgroupと同じような考え方ですが、適用される順序は関係ありません。

    • 本ライブラリでは、順序を指定したいときは、属性 order を指定します。

  • 属性を指定しない(空の)場合は、デフォルトのグループ com.github.mygreen.supercsv.annotation.DefaultGroup が適用されたと同じ意味になります。

    • Bean Validationのデフォルトグループ javax.validation.groups.Default とは異なるため、特にBeanValidationのアノテーションと混在させる場合は注意してください。

  • グループ用クラスは、実装が必要ないため、通常はインタフェースで作成します。

 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.annotation.DefaultGroup;
 4import com.github.mygreen.supercsv.annotation.constraint.*;
 5
 6@CsvBean
 7public class SampleCsv {
 8
 9    @CsvColumn(number=1)
10    @CsvRequire
11    @CsvNumberMin(value="0", groups=AdminGroup.class, order=2)
12    @CsvNumberMax(value="100", groups=NormalGroup.class, order=2)
13    private Integer value;
14
15    // getter/setterは省略
16}
17
18// グループ用クラスの作成
19public static interface AdminGroup {}
20public static interface NormalGroup {}

実行時は、CsvAnnotationBeanReader/CsvAnnotationBeanWriter/BeanMappingFactory の引数で指定します。

 1import com.github.mygreen.supercsv.builder.BeanMapping;
 2import com.github.mygreen.supercsv.builder.BeanMappingFactory;
 3import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
 4import com.github.mygreen.supercsv.io.CsvAnnotationBeanWriter;
 5
 6import java.nio.charset.Charset;
 7import java.nio.file.Files;
 8import java.io.File;
 9import java.util.ArrayList;
10import java.util.List;
11
12import org.supercsv.prefs.CsvPreference;
13
14public class Sample {
15
16    // 読み込み時のグループの指定
17    public void sampleRead() {
18
19        CsvAnnotationBeanReader<SampleCsv> csvReader = new CsvAnnotationBeanReader<>(
20                SampleCsv.class,
21                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
22                CsvPreference.STANDARD_PREFERENCE,
23                DefaultGroup.class, AdminGroup.class); // デフォルトとAdmin用のグループクラスを指定する。
24
25        //... 以下省略
26
27    }
28
29    // 書き込み時のグループの指定
30    public void sampleWrite() {
31
32        CsvAnnotationBeanWriter<SampleCsv> csvWriter = new CsvAnnotationBeanWriter<>(
33                SampleCsv.class,
34                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
35                CsvPreference.STANDARD_PREFERENCE,
36                DefaultGroup.class, NoramlGroup.class); // デフォルトとNoraml用のグループクラスを指定する。
37
38        //... 以下省略
39
40    }
41
42    // BeanMapping作成時の指定
43    public void sampleBeanMapping() {
44
45        // BeanMappingの作成
46        BeanMappingFactory mappingFactory = new BeanMappingFactory();
47        BeanMapping<SampleCsv> beanMapping = mappingFactory.create(SampleCsv.class,
48            DefaultGroup.class, NoramlGroup.class);  // デフォルトとNormal用のグループクラスを指定する。
49
50        CsvAnnotationBeanReader<SampleCsv> csvReader = new CsvAnnotationBeanReader<>(
51                beanMapping,
52                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
53                CsvPreference.STANDARD_PREFERENCE);
54
55        //... 以下省略
56    }
57
58}

5.2. 独自の検証処理の作成方法

独自の検証処理を実装するには、3つのステップを踏む必要があります。

  1. CellProcessorの実装クラスの作成

  2. アノテーションクラスの作成

  3. CellProcessorを作成するためのファクトリクラスの作成

以下、それぞれに対して解説していきます。

5.2.1. CellProcessorの実装クラスの作成

サンプルとして、最後が任意の文字で終わるか検証するCellProcessorを作成します。

  • 抽象クラス ValidationCellProcessor [ JavaDoc ] を継承して作成します。

    • ValidationCellProcessor は、値の検証に特化したCellProcessorの実装です。

    • CellProcessorは、「Chain of Responsibility」パターンであるため、その構造を表現するためのクラスとなります。

  • 今回は、文字列型の値を検証するため、インタフェースとして StringCellProcessor [ JavaDoc ] を実装します。

    • この実装は特に必要ないですが、扱うカラムの値の種類を表現するめのものです。 CellProcessorを直接組み立てる時に、これらのインタフェースでchainとして追加する次のCellProcessorを限定するために使用します。

    • 扱う値が数値型のときは LongCellProcessor [ JavaDoc ]などと、扱う値によって変更してください。

  • コンストラクタとして、chainの次の処理となるCellProcessorを引数に持つものと、持たないものを必ず2つ実装します。

  • メソッド execute(...) 内で処理の実装を行います。

    • nullの場合、次の処理に委譲するようにします。 Super CSVの既存のCellProcessorではメソッドvalidateInputNotNull(...)を呼びnullチェックを行いますが、 本ライブラリではnullに対する処理は他のCellProcessorで行うため、次の処理に渡します。

    • 検証対象のクラスタイプが不正な場合は、例外 SuperCsvCellProcessorException をスローします。 アノテーションを間違った型に付与した場合に発生する場合がありますが、ファクトリクラスの段階で弾いてもかまいません。

    • 正常な値であれば、次の処理に渡します。

    • 問題がある場合、例外 SuperCsvValidationException をスローします。 その際に、メソッド createValidationException(...) を呼び出して、ビルダクラスを利用して例外クラスを組み立てます。

 1import org.supercsv.cellprocessor.ift.CellProcessor;
 2import org.supercsv.cellprocessor.ift.StringCellProcessor;
 3import org.supercsv.util.CsvContext;
 4
 5import com.github.mygreen.supercsv.cellprocessor.ValidationCellProcessor;
 6
 7// 独自の値の検証用のCellProcessor
 8public class CustomConstraint extends ValidationCellProcessor
 9        implements StringCellProcessor {
10
11    private String text;
12
13    public CustomConstraint(final String text) {
14        super();
15        checkPreconditions(text);
16        this.text = text;
17    }
18
19    public CustomConstraint(final String text, final CellProcessor next) {
20        super(next);
21        checkPreconditions(text);
22        this.text = text;
23    }
24
25    // コンストラクタで渡した独自の引数のチェック処理
26    private static void checkPreconditions(final String text) {
27        if(text == null) {
28            throw new NullPointerException("text should not be null.");
29        } else if(text.isEmpty()) {
30            throw new NullPointerException("text should not be empty.");
31        }
32    }
33
34    @Override
35    public <T> T execute(final Object value, final CsvContext context) {
36        if(value == null) {
37            // nullの場合、次の処理に委譲します。
38            return next.execute(value, context);
39        }
40
41        final String result;
42        if(value instanceof String) {
43            result = (String)value;
44
45        } else {
46            // 検証対象のクラスタイプが不正な場合
47            throw new SuperCsvCellProcessorException(String.class, value, context, this);
48        }
49
50        // 最後が指定した値で終了するかどうか
51        if(result.endsWith(text)) {
52            // 正常な値の場合、次の処理に委譲します。
53            return next.execute(value, context);
54        }
55
56        // エラーがある場合、例外クラスを組み立てます。
57        throw createValidationException(context)
58            .messageFormat("Not ends with %s.", text)
59            .messageVariables("suffix", text)
60            .build();
61
62    }
63
64}

5.2.2. 値の検証用のアノテーションクラスの作成

  • @Target として、ElementType.FIELDElementType.ANNOTATION_TYPE の2つを指定します。

    • 通常はFieldのみで問題ないですが、 アノテーションを合成 するときがあるため、 ANNOTATION_TYPE も追加しておきます。

  • @Repeatable として、複数のアノテーションを設定できるようにします。

    • 内部アノテーションとして、 List を定義します。

  • 値の検証用のアノテーションであることを示すためのメタアノテーション @CsvConstraint [ JavaDoc ]を指定します。

    • 属性 value に、ConstraintProcessorFactory [ JavaDoc ]を実装したCellProcessorのファクトリクラスの実装を指定します。

  • 共通の属性として、 casesgroupsorder を定義します。

    • 省略した場合は、それぞれのデフォルト値が適用されます。

  • 必要であれば、固有の属性を定義します。今回は、text を定義します。これはCellProcessorに渡す値となります。

 1import java.lang.annotation.Documented;
 2import java.lang.annotation.ElementType;
 3import java.lang.annotation.Repeatable;
 4import java.lang.annotation.Retention;
 5import java.lang.annotation.RetentionPolicy;
 6import java.lang.annotation.Target;
 7
 8import com.github.mygreen.supercsv.annotation.constraint.CsvConstraint;
 9import com.github.mygreen.supercsv.builder.BuildCase;
10
11
12// 独自の値の検証用のアノテーション
13@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
14@Retention(RetentionPolicy.RUNTIME)
15@Documented
16@Repeatable(CsvCustomConstraint.List.class)
17@CsvConstraint(CustomConstratinFactory.class)  // ファクトリクラスを指定
18public @interface CsvCustomConstraint {
19
20    // 固有の属性 - チェックすることとなる最後の文字を指定します。
21    String text();
22
23    // 共通の属性 - ケース
24    BuildCase[] cases() default {};
25
26    // 共通の属性 - グループ
27    Class<?>[] groups() default {};
28
29    // 共通の属性 - 並び順
30    int order() default 0;
31
32    // 繰り返しのアノテーションの格納用アノテーションの定義
33    @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
34    @Retention(RetentionPolicy.RUNTIME)
35    @Documented
36    @interface List {
37
38        CsvCustomConstraint[] value();
39    }
40}

5.2.3. 値の検証用のファクトリクラスの作成

アノテーションをハンドリングして、CellProcessorを作成するためのファクトリクラスを作成します。

  • インタフェース ConstraintProcessorFactory [ JavaDoc ]を実装します。

  • アノテーションが検証対象のクラスタイプ以外に付与される場合があるため、その際は無視するようにします。

  • 独自のCellProcessorのCustomConstraintのインスタンスを作成します。

  • Chainの次の処理となるCellProcessorの変数「next」は、空であることがあるため、コンストラクタで分けます。

 1import com.github.mygreen.supercsv.builder.BuildType;
 2import com.github.mygreen.supercsv.builder.Configuration;
 3import com.github.mygreen.supercsv.builder.FieldAccessor;
 4import com.github.mygreen.supercsv.cellprocessor.ConstraintProcessorFactory;
 5import com.github.mygreen.supercsv.cellprocessor.format.TextFormatter;
 6
 7public class CustomConstraintFactory implements ConstraintProcessorFactory<CsvCustomConstraint> {
 8
 9    @Override
10    public Optional<CellProcessor> create(CsvCustomConstraint anno, Optional<CellProcessor> next,
11            FieldAccessor field, TextFormatter<?> formatter, Configuration config) {
12
13        if(!String.class.isAssignableFrom(field.getType())) {
14            // 検証対象のクラスタイプと一致しない場合は、弾きます。
15            return next;
16        }
17
18        // CellProcessorのインスタンスを作成します
19        final CustomConstraint processor = next.map(n ->  new CustomConstraint(anno.value(), n))
20                .orElseGet(() -> new CustomConstraint(anno.value()));
21
22        return Optional.of(processor);
23
24    }
25
26}

5.3. Bean単位での値の検証方法

CellProcessorは、フィールド1つに対する単項目チェックです。

項目間に対する相関チェックを行う場合は、CsvValidator で1レコード分のBeanに対するチェック処理を実装します。

5.3.1. CsvValidatorの実装

CsvValidator のメソッド validate(...) を実装します。

  • エラー情報は、CsvBindingErrors に格納し管理されています。

    • フィールドに対してエラーメッセージを追加する場合は、CsvBindingErrors#rejectValue(...) で追加します。

    • 項目間などのグローバルなエラーメッセージを追加する場合は、CsvBindingErrors#reject(...) で追加します。

  • CsvValidatorは、CellProcessorの後に実行され、もしエラーがある場合、そのフィールドの値は空が設定されており、 CsvBindingErrors#getFieldErrors(<フィールド名>) でフィールドに対するエラーがあるかどうか判定する必要があります。

    • 既にエラーがある場合などの判定処理を巻単位するため、CsvField クラスを利用します。

コード - 5.3.1 CsvValidatorの実装例
 1import java.util.Map;
 2
 3import com.github.mygreen.supercsv.validation.CsvBindingErrors;
 4import com.github.mygreen.supercsv.validation.CsvField;
 5import com.github.mygreen.supercsv.validation.CsvFieldValidator;
 6import com.github.mygreen.supercsv.validation.CsvValidator;
 7import com.github.mygreen.supercsv.validation.ValidationContext;
 8
 9// SampleCsvに対するValidator
10public class SampleValidator implements CsvValidator<SampleCsv> {
11
12    @Override
13    public void validate(final SampleCsv record, final CsvBindingErrors bindingErrors,
14            final ValidationContext<SampleCsv> validationContext) {
15
16        // フィールド ageの定義
17        final CsvField<Integer> ageField = new CsvField<>(bindingErrors, validationContext, record, "age");
18
19        // フィールド salaryの定義
20        final CsvField<Integer> salaryField = new CsvField<>(bindingErrors, validationContext, record, "salary");
21        salaryField
22            .add(new CsvFieldValidator<Integer>() {
23
24                @Override
25                public void validate(final CsvBindingErrors bindingErrors, final CsvField<Integer> field) {
26                    if(ageField.isEmpty()) {
27                        return;
28                    }
29
30                    // カラム「age(年齢)」が20以上の場合、カラム「給料(salary)」が設定されているかチェックする。
31                    if(ageField.isNotEmpty() && ageField.getValue() >= 20 && field.isEmpty()) {
32                        // メッセージ中の変数の作成
33                        final Map<String, Object> vars = createMessageVariables(field);
34                        vars.put("maxAge", 20);
35
36                        // ageに関するフィールドエラーの追加
37                        bindingErrors.rejectValue(field.getName(), field.getType(), "age.required", vars);
38                    }
39                }
40            })
41            .add(new MaxValidator(10_000_000))
42            .validate(bindingErrors);
43
44
45    }
46
47    // CsvFieldValidator を別に実装
48    public static class MaxValidator implements CsvFieldValidator<Integer> {
49
50        private final int max;
51
52        public MaxValidator(final int max) {
53            this.max = max;
54        }
55
56        @Override
57        public void validate(CsvBindingErrors bindingErrors, CsvField<Integer> field) {
58            if(field.isEmpty()) {
59                return;
60            }
61
62            if(field.getValue() > max) {
63                // メッセージ変数の組み立て
64                Map<String, Object> vars = createMessageVariables(field);
65                vars.put("max", max);
66
67                bindingErrors.rejectValue(field.getName(), field.getType(), "fieldError.max", vars);
68            }
69        }
70
71    }
72
73}

CsvFieldValidator#createMessageVariables(...) を利用すると、以下の良く使用するCSV情報に関する変数を作ることができます。

表 - 5.3.1 よく使用するな変数

変数名

説明

lineNumber

CSVの実ファイル上の行番号。
カラムの値に改行が含まれている場合を考慮した実際の行番号なります。
1から始まります。

rowNumber

CSVの論理上の行番号です。
1から始まります。

columnNumber

CSVの列番号です。
1から始まります。

label

@CsvColumn(label="<見出し>") 出指定したカラムの見出し名です。
label属性を指定していない場合は、フィールド名になります。

validatedValue

不正となった値です。

printer

各フィールドの TextFormatter のインスタンスです。
${print#print(validatedValue)} でパース済みのオブジェクトをフォーマットするのに利用します。

作成したCsvValidatorは、 @CsvBean(validators=) で指定します。

コード - 5.3.2 Beanの実装例
 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.annotation.constraint.CsvNumberMin;
 4import com.github.mygreen.supercsv.annotation.constraint.CsvRequire;
 5
 6
 7@CsvBean(header=true, validators=SampleValidator.class)
 8public class SampleCsv {
 9
10    @CsvColumn(number=1, label="名前")
11    @CsvRequire(considerBlank=true)
12    private String name;
13
14    @CsvColumn(number=2, label="年齢")
15    private Integer age;
16
17    @CsvColumn(number=3, label="給料")
18    @CsvNumberMin("0")
19    private Integer salary;
20
21    // setter/ getterは省略
22
23}

5.3.2. エラーメッセージの定義

CsvBindingErrors#reject(...)/rejectValue(...) でエラーメッセージを追加する場合、エラーコードを指定します。

  • エラーメッセージは、クラスパスのルートに配置したプロパティファイル SuperCsvMessages.properties が自動的に読み込まれるため、そこに定義します。

  • プロパティファイルは、UTF-8、ASCIIコード変換なしで作成します。 [ver.2.2]

  • メッセージキーは、「エラーコード」「クラス名」「フィールド名」「フィールドのクラスタイプ」を組み合わせて、優先順位の高いものに一致した物が採用されます。

  • メッセージ中では変数が利用可能で、予め利用可能な変数は下記が登録されています。

    • メッセージ変数は、{key} で参照可能です。

  • さらに、${exp} の形式だと、式言語として JEXL が利用可能です。

  • ただし、 Spring Frameworkと連携してエラーメッセージの取得方法を変更 している場合は、定義する箇所は異なります。

表 - 5.3.2 メッセージキーの候補

優先度

形式

1

<エラーコード>.<Beanのクラス名>.<フィールド名>

age.reqired.SampleCsv.age
※「age.required」がエラーコード

2

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

age.reqired.age
※「age.required」がエラーコード

3

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

age.reqired.java.lang.Integer
※「age.required」がエラーコード

4

<エラーコード>.<フィールドのクラスタイプの親のクラスパス>
※数値型と列挙型のみ
age.reqired.java.lang.Number
age.reqired.java.lang.Enum
※「age.required」がエラーコード

5

<エラーコード>

age.reqired
※「age.required」がエラーコード
コード - 5.3.3 SuperCsvMessage.propertiesの定義例
 1###################################################
 2# 独自のエラーメッセージの定義
 3###################################################
 4# 定義したキーは、再帰的に{キー名}で参照可能
 5
 6csvContext=[{rowNumber}行, {columnNumber}列]
 7
 8## エラーコードに対するメッセージの定義
 9age.required={csvContext} : 項目「{label}」は、年齢が{maxAge}歳以上の場合には必須です。
10fieldError.max={csvContext} : 項目「{label}」の値(${printer.print(validatedValue)})は、${printer.print(max)}以内で入力してください。

5.3.3. エラーのハンドリング

CsvValidatorの中でCsvBindingErrorsにエラー情報を追加すると、例外 SuperCsvBindingException としてスローされます。

  • 例外クラスは、 CsvExceptionConverter メッセージに変換します。

  • CsvExceptionConverter は、CsvAnnotationBeanReader/CsvAnnotationBeanWriter に組み込まれているため、 メソッド #getErrorMessages() で取得できます。

 1import java.nio.charset.Charset;
 2import java.nio.file.Files;
 3import java.io.File;
 4import java.util.ArrayList;
 5import java.util.List;
 6
 7import org.supercsv.prefs.CsvPreference;
 8import org.supercsv.exception.SuperCsvException;
 9
10import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
11import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
12
13
14public class Sample {
15
16    public void sampleRead() {
17
18        CsvAnnotationBeanReader<SampleCsv> csvReader;
19        try {
20            csvReader = new CsvAnnotationBeanReader<>(
21                SampleCsv.class,
22                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
23                CsvPreference.STANDARD_PREFERENCE);
24
25            // ファイルの読み込み
26            List<SampleCsv> list = csvReader.readAll();
27
28        } catch(SuperCsvException e) {
29
30            // 変換されたエラーメッセージの取得
31            List<String> messages = csvReader.getErrorMessages();
32
33        } finally {
34            if(csvReader != null) {
35                csvReader.close();
36            }
37        }
38    }
39
40}

5.4. エラー処理の方法

5.4.1. 例外の種類とハンドリング

読み込み時の書式の不正や値の検証時に失敗した場合、例外 org.supercsv.exception.SuperCsvException がスローされます。

エラー内容を画面に表示するようなシステムの場合、例外の内容をメッセージに変換する必要があります。 そのような時には、com.github.mygreen.supercsv.validation.CsvExceptionConverter を使い、エラーメッセージに変換します。

CsvExceptionConverter は、CsvAnnotaionBeanReader/CsvAnnotaionBeanWriterに組み込まれており、 例外発生時に自動的にエラーメッセージに変換されたものがため込まれます。

コード - 5.4.1 Super CSV Annotationの例外体系
java.lang.RuntimeException
  └ org.supercsv.exception.SuperCsvException
      ├ org.supercsv.exception.SuperCsvReflectionException
      ├ org.supercsv.exception.SuperCsvCellProcessorException
      │    ├ org.supercsv.exception.SuperCsvConstraintViolationException
      │    │
      │    │ ※Super Csv Annotationの例外
      │    └ com.github.mygreen.supercsv.exception.SuperCsvValidationException
      │
      │※Super Csv Annotationの例外
      ├ com.github.mygreen.supercsv.exception.SuperCsvInvalidAnnotationException
      ├ com.github.mygreen.supercsv.exception.SuperCsvNoMatchHeaderException
      ├ com.github.mygreen.supercsv.exception.SuperCsvNoMatchColumnSizeException
      ├ com.github.mygreen.supercsv.exception.SuperCsvBindingException
      └ com.github.mygreen.supercsv.exception.SuperCsvRowException
表 - 5.4.1 Super CSV の例外

クラス名

説明

SuperCsvException

Super CSVのルートの例外。
Super CSV及び、Super CSV Annotationの例外は、全てこのクラスを継承しています。

SuperCsvReflectionException

Beanのインスタンスなどの作成やプロパティへの値のマッピング時など、
リフレクションを使ってに失敗したときの例外。

SuperCsvCellProcessorException

CellProcessor内で、処理対象のセルの値のクラスタイプが不正などのときのときにスローされる例外。
例えば、文字列を日時にパースするCellProcessor ParseDate で、パースに失敗した場合。

SuperCsvConstraintViolationException

制約のCellProcessor内で、値が制約違反となり不正のときのにスローされる例外。
例えば、CellProcessor LMinMax で、セルの値が指定した数値の範囲以外のときにスローされる例外。
表 - 5.4.2 Super CSV Annotation の例外

クラス名

説明

SuperCsvValidationException

Super CSV Annotationの CellProcessor ValidationCellProcessor
実装しているCellProcessorの制約違反の例外。
メッセージ変数などの情報が格納されている。

SuperCsvInvalidAnnotationException

アノテーションの値が不正だったりした場合にスローされる例外。

SuperCsvNoMatchHeaderException

ヘッダー行を読み込む際に、@CsvColumn(label="") で定義している
値と異なる場合にスローされる例外。

SuperCsvNoMatchColumnSizeException

ヘッダー行やレコードを読み込む際に、@CsvColumn で定義している
カラムサイズと異なる場合にスローされる例外。

SuperCsvRowException

各カラムのCellProcesor内で発生した SuperCsvCellProcessorException の例外を、
レコードを単位にまとめた例外。

SuperCsvBindingException

最終的にカラムのマッピングに失敗したときにスローされる例外。
CsvValidatorによるBeanの検証時のエラーも格納されている。

レコードの値の読み書きを行う場合に業務例外として扱うものは、SuperCsvNoMatchColumnSizeExceptionSuperCsvBindingException の2つと考えて処理すればよいです。 他の例外は、設定が不正な場合にスローされるため、基本はシステムエラー(ランタイムエラー)として扱うことになります。 ただし、ヘッダー行の読み込み時は、SuperCsvNoMatchHeaderException も考慮する必要があります。

CsvAnnotationBeanReader/CsvAnnotationBeanWriter は AutoCloseable が実装されていますが、 try-with-resources 文を使用する場合は注意が必要です。アノテーションの解析などはコンストラクタ内で行うので、 もし、その中で例外が発生するとCSVファイルに関連するリソースが解放されないくなるため分割して定義します。

また、1レコードずつ処理すると、例外発生時に処理が終わってしまうため、全レコードの値を検証したい場合は、 readAll(...)/readWrite(...) メソッドの使用をお勧めします。

例外がスローされたときの処理をtry-catchではなく、ハンドラ形式で処理したい場合は、 CsvSuccessHandlerCsvErrorHandler の実装を指定します。 関数型インタフェースのため、Lambda式を使うことができます。 [v2.3+]

コード - 5.4.2 読み込み時のエラー処理
  1import java.nio.charset.Charset;
  2import java.nio.file.Files;
  3import java.io.File;
  4import java.io.IOException;
  5import java.io.Reader;
  6import java.util.ArrayList;
  7import java.util.List;
  8
  9import org.supercsv.prefs.CsvPreference;
 10import org.supercsv.exception.SuperCsvException;
 11
 12import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
 13import com.github.mygreen.supercsv.exception.SuperCsvBindingException;
 14import com.github.mygreen.supercsv.exception.SuperCsvNoMatchColumnSizeException;
 15import com.github.mygreen.supercsv.exception.SuperCsvNoMatchHeaderException;
 16
 17
 18public class Sample {
 19
 20    // 読み込み時の場合(1行づつ処理する場合)
 21    public void sampleReadEach() {
 22
 23        try(Reader reader = Files.newBufferedReader(
 24                    new File("sample.csv").toPath(), Charset.forName("Windows-31j"));
 25            CsvAnnotationBeanReader<SampleCsv> csvReader = new CsvAnnotationBeanReader<>(
 26                    SampleCsv.class, reader, CsvPreference.STANDARD_PREFERENCE); ) {
 27
 28            // ヘッダー行の読み込み
 29            String[] headers = csvReader.getHeader(true);
 30
 31            List<SampleCsv> list = new ArrayList<>();
 32
 33            // レコードの読み込み - 1行づつ
 34            SampleCsv record = null;
 35            while((record = csvReader.read()) != null) {
 36                    list.add(record);
 37            }
 38
 39        } catch(SuperCsvNoMatchColumnSizeException
 40                | SuperCsvBindingException
 41                | SuperCsvNoMatchHeaderException e) {
 42            // レコードの値が不正な場合のときのエラー
 43
 44        } catch(SuperCsvException e ) {
 45            // Super CSVの設定などのエラー
 46
 47        } catch(IOException e) {
 48            // ファイルI/Oに関する例外
 49
 50        }
 51    }
 52
 53    // 読み込み時の場合(全件処理する場合)
 54    public void sampleReadAll() {
 55
 56        try(Reader reader = Files.newBufferedReader(
 57                    new File("sample.csv").toPath(), Charset.forName("Windows-31j"));
 58            CsvAnnotationBeanReader<SampleCsv> csvReader = new CsvAnnotationBeanReader<>(
 59                    SampleCsv.class, reader, CsvPreference.STANDARD_PREFERENCE); ) {
 60
 61            // 全件読み込む - SuperCsvBindingExceptionなどの例外発生しても続けて処理する
 62            List<SampleCsv> list = csvReader.readAll(true);
 63
 64            // エラーメッセージの取得
 65            List<String> errorMessages = csvReader.getErrorMessages();
 66
 67        } catch(SuperCsvException e ) {
 68            // Super CSVの設定などのエラー
 69
 70        } catch(IOException e) {
 71            // ファイルI/Oに関する例外
 72
 73        }
 74    }
 75
 76    // 読み込み時の場合(ハンドラで処理する場合)
 77    public void sampleReadWithHandler() {
 78
 79        try(Reader reader = Files.newBufferedReader(
 80                    new File("sample.csv").toPath(), Charset.forName("Windows-31j"));
 81            CsvAnnotationBeanReader<SampleCsv> csvReader = new CsvAnnotationBeanReader<>(
 82                    SampleCsv.class, reader, CsvPreference.STANDARD_PREFERENCE); ) {
 83
 84            // ヘッダー行の読み込み
 85            String[] headers = csvReader.getHeader(true);
 86
 87            List<SampleCsv> list = new ArrayList<>();
 88
 89            // ハンドラによる読み込み
 90            while(csvReader.read(
 91                record -> {
 92                    // 読み込み成功時の処理 - CsvSuccessHandler
 93                    list.add(record);
 94                },
 95                error -> {
 96                    // Super CSVに関するエラー処理 - CsvErrorHandler
 97
 98                }) != CsvReadStatus.EOF) {
 99
100            }
101
102            // エラーメッセージの取得
103            List<String> errorMessages = csvReader.getErrorMessages();
104
105        } catch(IOException e) {
106            // ファイルI/Oに関する例外
107
108        }
109
110    }
111}
コード - 5.4.3 書き込み時のエラー処理
  1import java.nio.charset.Charset;
  2import java.nio.file.Files;
  3import java.io.File;
  4import java.io.IOException;
  5import java.io.Writer;
  6import java.util.ArrayList;
  7import java.util.List;
  8
  9import org.supercsv.prefs.CsvPreference;
 10import org.supercsv.exception.SuperCsvException;
 11
 12import com.github.mygreen.supercsv.io.CsvAnnotationBeanWriter;
 13import com.github.mygreen.supercsv.exception.SuperCsvBindingException;
 14
 15
 16public class Sample {
 17
 18    // 書き込み時の場合(1行づつ処理する場合)
 19    public void sampleWriteEach() {
 20
 21        try(Writer writer = Files.newBufferedWriter(
 22                    new File("sample.csv").toPath(), Charset.forName("Windows-31j"));
 23            CsvAnnotationBeanWriter<SampleCsv> csvWriter = new CsvAnnotationBeanWriter<>(
 24                    SampleCsv.class, reader, CsvPreference.STANDARD_PREFERENCE); ) {
 25
 26            // ヘッダー行の書き込み
 27            csvWriter.writeHeaader();
 28
 29            // レコードの書き込み - 1行づつ
 30            SampleCsv record1 = /* 省略*/;
 31            csvWriter.write(record1);
 32
 33            SampleCsv record2 = /* 省略*/;
 34            csvWriter.write(record2);
 35
 36
 37        } catch (SuperCsvBindingException e) {
 38            // レコードの値が不正な場合のときのエラー
 39
 40        } catch(SuperCsvException e ) {
 41            // Super CSVの設定などのエラー
 42
 43        } catch(IOException e) {
 44            // ファイルI/Oに関する例外
 45
 46        }
 47    }
 48
 49    // 書き込み時の場合(全件処理する場合)
 50    public void sampleWriteAll() {
 51
 52        try(Writer writer = Files.newBufferedWriter(
 53                    new File("sample.csv").toPath(), Charset.forName("Windows-31j"));
 54            CsvAnnotationBeanWriter<SampleCsv> csvWriter = new CsvAnnotationBeanWriter<>(
 55                    SampleCsv.class, writer, CsvPreference.STANDARD_PREFERENCE); ) {
 56
 57            List<SampleCsv> list = /* 省略 */;
 58
 59            // 全件書き込む - SuperCsvBindingExceptionなどの例外発生しても続けて処理する
 60            csvWriter.writeAll(list, true);
 61
 62            // エラーメッセージの取得
 63            List<String> errorMessages = csvWriter.getErrorMessages();
 64
 65        } catch(SuperCsvException e ) {
 66            // Super CSVの設定などのエラー
 67
 68        } catch(IOException e) {
 69            // ファイルI/Oに関する例外
 70
 71        }
 72    }
 73
 74    // 書き込み時の場合(ハンドラで処理する場合)
 75    public void sampleWriteWithHandler() {
 76
 77        try(Writer writer = Files.newBufferedWriter(
 78                    new File("sample.csv").toPath(), Charset.forName("Windows-31j"));
 79            CsvAnnotationBeanWriter<SampleCsv> csvWriter = new CsvAnnotationBeanWriter<>(
 80                    SampleCsv.class, writer, CsvPreference.STANDARD_PREFERENCE); ) {
 81
 82            List<SampleCsv> list = /* 省略 */;
 83
 84            // ヘッダー行の書き込み
 85            csvWriter.writeHeaader();
 86
 87            for(SampleCsv item : list) {
 88                csvWriter.write(item, error -> {
 89                    // Super CSVに関するエラー処理 - CsvErrorHandler
 90                    }
 91                );
 92            }
 93
 94            csvWriter.flush();
 95
 96            // エラーメッセージの取得
 97            List<String> errorMessages = csvWriter.getErrorMessages();
 98
 99        } catch(IOException e) {
100            // ファイルI/Oに関する例外
101
102        }
103    }
104}

5.4.2. 書き込み時の値の検証のスキップ

書き込み時の値をスキップしたい場合は、グループによる指定もできますが、システム設定を変更することで一律にスキップすることができます。

BeanMappingFactory から Configuration を取得し、そのプロパティ skipValidationOnWrite の値を trueに設定します。

 1import com.github.mygreen.supercsv.builder.BeanMapping;
 2import com.github.mygreen.supercsv.builder.BeanMappingFactory;
 3import com.github.mygreen.supercsv.io.CsvAnnotationBeanWriter;
 4
 5import java.nio.charset.Charset;
 6import java.nio.file.Files;
 7import java.io.File;
 8
 9import org.supercsv.prefs.CsvPreference;
10
11public class Sample {
12
13    public void sampleWriteWithSkipValidation() {
14
15        // システム情報の設定変更
16        BeanMappingFactory mappingFactory = new BeanMappingFactory();
17        mappingFactory.getConfiguration().setSkipValidationOnWrite(true);
18
19        // BeanMappingの作成
20        BeanMapping<SampleCsv> beanMapping = mappingFactory.create(SampleCsv.class);
21
22        CsvAnnotationBeanWriter<SampleCsv> csvWriter = new CsvAnnotationBeanWriter<>(
23                beanMapping,
24                Files.newBufferedWriter(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
25                CsvPreference.STANDARD_PREFERENCE);
26
27        //... 以下省略
28    }
29
30}

5.5. 値の検証時のエラーメッセージ

セルの値の検証時など例外がスローされ、 CsvExceptionConverter によりメッセージに変換します。 CsvExceptionConverter は、CsvAnnotationBeanReader/CsvAnnotationBeanWriter に設定します。

メッセージリソースは MessageResolver で管理されてます。

  • デフォルトでは、 ResourceBundleMessageResolver が設定されています。

    • ResourceBundleMessageResolver では、システムのプロパティファイル com/github/mygreen/supercsv/localization/SuperCsvMessages.properties が読み込まれます。

    • 独自のメッセージは、クラスパスのルート配置した SuperCsvMessages.properties が読み込まれます。

  • 実装を切り替えることで、他の形式のファイルからも取得することができます。

表 - 5.5.1 MessageResolverの実装

クラス名

説明

ResourceBundleMessageResolver

java.util.ResourceBundle を経由してメッセージを参照します。
デフォルトでは SuperCsvMessages.properties を読み込みます。

PropertiesMessageResolver

java.util.Properties を経由してメッセージを参照します。

SpringMessageResolver

org.springframework.context.MessageSource を経由してメッセージを参照します。

また、メッセージ中には {var} の形式で変数が可能です。 さらに、${exp} の形式で 式言語の Java Expression Language (JEXL) が利用可能です。

デフォルト設定では、式言語ので呼び出し可能な関数が登録されています。 com.github.mygreen.supercsv.expression.CustomFunction のメソッドが接頭語 f: を付けて呼び出し可能です。 また、独自の関数も登録可能です。

コード - 5.5.1 メッセージと式言語の設定
 1import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
 2import com.github.mygreen.supercsv.localization.MessageInterpolator;
 3import com.github.mygreen.supercsv.localization.ResourceBundleMessageResolver;
 4import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
 5import com.github.mygreen.supercsv.expression.ExpressionLanguageJEXLImpl;
 6
 7import java.nio.charset.Charset;
 8import java.nio.file.Files;
 9import java.io.File;
10import java.util.ResourceBundle;
11
12import org.supercsv.prefs.CsvPreference;
13
14public class Sample
15 {
16
17    public void customMessageAndExpression() {
18
19        // CsvExceptionConverterの作成
20        CsvExceptionConverter exceptionConverter = new CsvExceptionConverter();
21
22        // メッセージソースを既存の物に対して追加する
23        ResourceBundleMessageResolver messageResolver = new ResourceBundleMessageResolver();
24        messageResolver.addResourceBundle(ResourceBundle.getBundle("SampleMessages"));
25        exceptionConverter.setMessageResolver(messageResolver);
26
27        // 式言語に独自の関数を登録
28        // 参照可能な関数は、public static である必要があります。
29        ExpressionLanguageJEXLImpl el = new ExpressionLanguageJEXLImpl();
30        Map<String, Object> funcs = new HashMap<>();
31        funcs.put("my", SampleFunctions.class);
32        el.getJexlEngine().setFunctions(funcs);
33
34        // カスタマイズした式言語の登録
35        exceptionConverter.setMessageInterpolator(new MessageInterpolator(el));
36
37        // CsvExceptionConverterを設定する
38        CsvAnnotationBeanReader<UserCsv> csvReader = new CsvAnnotationBeanReader<>(
39                UserCsv.class,
40                Files.newBufferedReader(new File("user.csv").toPath(), Charset.forName("Windows-31j")),
41                CsvPreference.STANDARD_PREFERENCE);
42
43        csvReader.setExceptionConverter(exceptionConverter);
44
45        //... 以下省略
46    }
47
48}

5.6. Bean Validationとの連携

Bean Validationによるカラムの値の検証を行う方法を説明します。

5.6.1. ライブラリの追加

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

本ライブラリは、Bean Validation1.0/1.1の両方に対応しており、 その参照実装である Hibernate Validator を追加します。

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

  • ただし、TomcatやGlassFishなどのWebコンテナ上で使用するのであれば、EL式のライブラリはそれらに組み込まれているため必要ありません。

  • また、本ライブラリの機能を利用して、JEXL に切り替えるのであれば、式言語の追加は必要ありません。

コード - 5.6.1 pom.xmlの依存関係の追加(Bean Validation1.1を利用する場合)
 1<!-- Bean Validation 1.1 -->
 2<dependency>
 3    <groupId>javax.validation</groupId>
 4    <artifactId>validation-api</artifactId>
 5    <version>1.1.0.Final</version>
 6</dependency>
 7<dependency>
 8<groupId>org.hibernate</groupId>
 9    <artifactId>hibernate-validator</artifactId>
10    <version>5.3.3.Final</version>
11</dependency>
12
13<!-- EL式のライブラリが必要であれば追加します -->
14<dependency>
15    <groupId>org.glassfish</groupId>
16    <artifactId>javax.el</artifactId>
17    <version>3.0.1-b08</version>
18</dependency>

Bean Validation 1.0(JSR-303)を利用する場合は、Hibernate Validator4.x系を利用します。 Bean Validation 1.0では、メッセージ中でEL式は利用できませんが、本ライブラリの機能を使用すれば、JEXL が利用できます。

コード - 5.6.2 pom.xmlの依存関係の追加(Bean Validation1.0を利用する場合)
 1<!-- Bean Validation 1.0 -->
 2<dependency>
 3<groupId>javax.validation</groupId>
 4    <artifactId>validation-api</artifactId>
 5    <version>1.0.0.GA</version>
 6</dependency>
 7<dependency>
 8    <groupId>org.hibernate</groupId>
 9    <artifactId>hibernate-validator</artifactId>
10    <version>4.3.2.Final</version>
11</dependency>

5.6.2. Bean Validationの利用方法

アノテーション @CsvBean(validatosr=CsvBeanValidator.class) を指定します。

CsvBeanValidator は、Bean Validation と、本ライブラリの CsvValidator をブリッジするクラスです。

独自のメッセージソースは、クラスパスのルートに HibernateValidation.properties を配置しておけば自動的に読み込まれます。

 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator;
 4
 5import javax.validation.constraints.AssertTrue;
 6import javax.validation.constraints.DecimalMax;
 7import javax.validation.constraints.Pattern;
 8
 9import org.hibernate.validator.constraints.Length;
10import org.hibernate.validator.constraints.NotEmpty;
11import org.hibernate.validator.constraints.Range;
12
13// Bean Validationの指定方法
14@CsvBean(validators=CsvBeanValidator.class)
15private static class TestCsv {
16
17    @CsvColumn(number=1)
18    @NotEmpty
19    private String id;
20
21    @CsvColumn(number=2)
22    @Length(max=10)
23    @Pattern(regexp="[\\p{Alnum}]+", message="半角英数字で設定してください。")
24    private String name;
25
26    @CsvColumn(number=3)
27    @Range(min=0, max=100)
28    private Integer age;
29
30    @CsvColumn(number=4)
31    boolean used;
32
33    // 相関チェック
34    @AssertTrue(message="{name}が設定されている際には、{age}は必須です。")
35    boolean isValidAgeRequired() {
36        if(name != null && !name.isEmpty()) {
37            return age != null;
38        }
39
40        return false;
41    }
42
43    // setter/gettterは省略
44}

Bean Validation による値の検証でエラーがある場合は、例外 SuperCsvBindingException としてスローされます。

  • 例外クラスは、 CsvExceptionConverter メッセージに変換します。

  • CsvExceptionConverter は、CsvAnnotationBeanReader/CsvAnnotationBeanWriter に組み込まれているため、 メソッド #getErrorMessages() で取得できます。

 1import java.nio.charset.Charset;
 2import java.nio.file.Files;
 3import java.io.File;
 4import java.util.ArrayList;
 5import java.util.List;
 6
 7import org.supercsv.prefs.CsvPreference;
 8import org.supercsv.exception.SuperCsvException;
 9
10import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
11import com.github.mygreen.supercsv.validation.CsvExceptionConverter;
12
13
14public class Sample {
15
16    public void sampleRead() {
17
18        CsvAnnotationBeanReader<SampleCsv> csvReader;
19        try {
20            csvReader = new CsvAnnotationBeanReader<>(
21                SampleCsv.class,
22                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
23                CsvPreference.STANDARD_PREFERENCE);
24
25            // ファイルの読み込み
26            List<SampleCsv> list = csvReader.readAll();
27
28        } catch(SuperCsvException e) {
29
30            // 変換されたエラーメッセージの取得
31            List<String> messages = csvReader.getErrorMessages();
32
33        } finally {
34            if(csvReader != null) {
35                csvReader.close();
36            }
37        }
38    }
39
40}

5.6.3. Bean Validationのカスタマイズ

本ライブラリ用のメッセージソースや JEXL に切り替える場合、 Bean Validationのインスタンスを変更する必要があります。

その場合は、@CsvBean(validators=CsvBeanValidator.class) で指定するのではなく、 メソッド CsvAnnotationBeanReader#addValidators(...)CsvAnnotationBeanWriter#addValidators(...) で直接追加します。

 1import com.github.mygreen.supercsv.builder.BeanMapping;
 2import com.github.mygreen.supercsv.builder.BeanMappingFactory;
 3import com.github.mygreen.supercsv.localization.MessageInterpolator;
 4import com.github.mygreen.supercsv.localization.MessageResolver;
 5import com.github.mygreen.supercsv.localization.ResourceBundleMessageResolver;
 6
 7import java.nio.charset.Charset;
 8import java.nio.file.Files;
 9import java.io.File;
10
11import org.supercsv.prefs.CsvPreference;
12
13public class Sample {
14
15    // Beanの定義(validatorsの指定は行わない)
16    @CsvBean
17    public static class SampleCsv {
18        // 省略
19    }
20
21    public void sampleBeanValidationCustom() {
22
23        // CsvReaderの作成
24        CsvAnnotationBeanReader<SampleCsv> csvReader = new CsvAnnotationBeanReader<>(
25                SampleCsv.class,
26                Files.newBufferedReader(new File("sample.csv").toPath(), Charset.forName("Windows-31j")),
27                CsvPreference.STANDARD_PREFERENCE);
28
29        // BeanValidator用のValidatorの作成
30        final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
31
32        // メッセージ処理クラスを本ライブラリのものに入れ替えてインスタンスを生成する
33        final Validator beanValidator = validatorFactory.usingContext()
34            .messageInterpolator(new MessageInterpolatorAdapter(
35                new ResourceBundleMessageResolver(),
36                new MessageInterpolator()))
37            .getValidator();
38
39        // Validatorの追加
40        csvReader.addValidators(beanValidator);
41
42    }
43
44}

メッセージ中の変数として、既存の変数に加えて、CSV用の次の変数が登録されており利用可能です。

表 - 5.6.1 メッセージ中で利用可能な変数

変数名

説明

lineNumber

CSVの実ファイル上の行番号。
カラムの値に改行が含まれている場合を考慮した実際の行番号なります。
1から始まります。

rowNumber

CSVの論理上の行番号です。
1から始まります。

columnNumber

CSVの列番号です。
1から始まります。

label

@CsvColumn(label="<見出し>") 出指定したカラムの見出し名です。
label属性を指定していない場合は、フィールド名になります。

validatedValue

不正となった値です。

printer

各クラスの TextFormatter のインスタンスです。
${printer.print(validatedValue)} で、オブジェクトをフォーマットするのに利用します。

5.6.4. BeanValidationとSpring Frameworkとの連携

Spring Frameworkと連携することで、コードがシンプルになります。 また、独自のフィールド用のValidator内にSpringBeanをインジェクションすることも可能です。

5.6.4.1. XMLによるコンテナの設定

XMLによる設定方法を説明します。

コンテナの定義の基本は次のようになります。

  • アノテーションによるDIの有効化を行います。

  • コンポーネントスキャン対象のパッケージの指定を行います。

  • com.github.mygreen.supercsv.builder.SpringBeanFactory をSpringBeanとして登録します。

  • Springの MessageSource で、本ライブラリのエラーメッセージ com.github.mygreen.supercsv.localization.SuperCsvMessages を読み込んでおきます。 * 独自のエラーメッセージがあれば、追加で定義します。

    • com.github.mygreen.supercsv.localization.SpringMessageResolver に、MessageSource を渡します。

  • CsvBeanValidator に、Springの LocalValidatorFactoryBean で作成したBeanValidationのValidtorのインスタンスを渡します。

 1<?xml version="1.0" encoding="UTF-8"?>
 2<!-- XMLによるコンテナの定義 -->
 3<beans xmlns="http://www.springframework.org/schema/beans"
 4    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5    xmlns:context="http://www.springframework.org/schema/context"
 6    xsi:schemaLocation="http://www.springframework.org/schema/beans
 7        http://www.springframework.org/schema/beans/spring-beans.xsd
 8        http://www.springframework.org/schema/context
 9        http://www.springframework.org/schema/context/spring-context.xsd
10    ">
11
12    <!-- アノテーションによるDIの有効化の定義 -->
13    <context:annotation-config />
14
15    <!-- コンポーネントスキャン対象のパッケージの指定 -->
16    <context:component-scan base-package="sample.spring" />
17
18    <!-- Springのコンテナを経由するCSV用のBeanFactoryの定義 -->
19    <bean id="springBeanFacatory" class="com.github.mygreen.supercsv.builder.SpringBeanFactory" />
20
21    <!-- Spring標準のメッセージソースの定義 -->
22    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
23        <property name="basenames">
24            <list>
25                <value>com.github.mygreen.supercsv.localization.SuperCsvMessages</value>
26                <value>TestMessages</value>
27            </list>
28        </property>
29    </bean>
30
31    <!-- Super CSV Annotation 用のMessgeResolverの定義 -->
32    <bean id="springMessageResolver" class="com.github.mygreen.supercsv.localization.SpringMessageResolver">
33        <property name="messageSource" ref="messageSource" />
34    </bean>
35
36    <!-- BeanValidation用のCsvValidatorの定義 -->
37    <bean id="csvBeanValidator" class="com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator">
38        <constructor-arg>
39            <bean class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
40                <property name="messageInterpolator">
41                    <bean class="com.github.mygreen.supercsv.validation.beanvalidation.MessageInterpolatorAdapter">
42                        <constructor-arg ref="springMessageResolver" />
43                        <constructor-arg><bean class="com.github.mygreen.supercsv.localization.MessageInterpolator" /></constructor-arg>
44                    </bean>
45                </property>
46            </bean>
47        </constructor-arg>
48    </bean>
49
50</beans>

5.6.4.2. JavaConfigによるコンテナの設定

Spring Framework3.0から追加された、JavaソースによるSpringBean定義の方法を説明します。

JavaConfigによる設定を使用する場合は、Spring Frameworkのバージョンをできるだけ最新のものを使用してください。 特に、機能が豊富なバージョン4.0以上の使用を推奨します。

 1import javax.validation.Validator;
 2
 3import org.springframework.context.MessageSource;
 4import org.springframework.context.annotation.Bean;
 5import org.springframework.context.annotation.ComponentScan;
 6import org.springframework.context.annotation.Configuration;
 7import org.springframework.context.annotation.Description;
 8import org.springframework.context.support.ResourceBundleMessageSource;
 9import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
10
11import com.github.mygreen.supercsv.builder.SpringBeanFactory;
12import com.github.mygreen.supercsv.localization.MessageInterpolator;
13import com.github.mygreen.supercsv.localization.SpringMessageResolver;
14import com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator;
15import com.github.mygreen.supercsv.validation.beanvalidation.MessageInterpolatorAdapter;
16
17
18// Javaによるコンテナの定義
19@Configuration
20@ComponentScan(basePackages="sample.spring")
21public class SuperCsvConfig {
22
23    @Bean
24    @Description("Springのコンテナを経由するCSV用のBeanFactoryの定義")
25    public SpringBeanFactory springBeanFactory() {
26        return new SpringBeanFactory();
27    }
28
29    @Bean
30    @Description("Spring標準のメッセージソースの定義")
31    public MessageSource messageSource() {
32        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
33        messageSource.addBasenames("com.github.mygreen.supercsv.localization.SuperCsvMessages", "TestMessages");
34        return messageSource;
35    }
36
37    @Bean
38    @Description("本ライブラリのSpring用のMessgeResolverの定義")
39    public SpringMessageResolver springMessageResolver() {
40        return new SpringMessageResolver(messageSource());
41    }
42
43    @Bean
44    @Description("Spring用のBeanValidatorのValidatorの定義")
45    public Validator csvLocalValidatorFactoryBean() {
46
47        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
48
49        // メッセージなどをカスタマイズ
50        validator.setMessageInterpolator(new MessageInterpolatorAdapter(
51                springMessageResolver(), new MessageInterpolator()));
52        return validator;
53    }
54
55    @Bean
56    @Description("CSV用のCsvValidaotrの定義")
57    public CsvBeanValidator csvBeanValidator() {
58
59        // ValidarorのインスタンスをSpring経由で作成したものを利用する
60        CsvBeanValidator csvBeanValidator = new CsvBeanValidator(csvLocalValidatorFactoryBean());
61        return csvBeanValidator;
62    }
63
64}

5.6.4.3. 独自のConstraintValidatorの作成

Bean Validationの独自のアノテーションを作成する際には、通常の方法と同じです。

  • メタアノテーション @Constraint を付与します。

    • 属性 validatedBy に、 ConsraintValidator の実装クラスを指定します。

  • 複数指定可能できるように、内部クラス List を定義しておきます。

    • Bean Validation 1.1の段階では、Java8から追加された @Repeatable は対応していませんが、 従来の定義方法と揃えておくことで、@Repeatable を使ってJava8のスタイルで使用することができます。

    • ただし、今後リリース予定のBeanValidator2.0から @Repeatable 対応するため、定義しておいても問題はありません。

 1import java.lang.annotation.Documented;
 2import java.lang.annotation.ElementType;
 3import java.lang.annotation.Repeatable;
 4import java.lang.annotation.Retention;
 5import java.lang.annotation.RetentionPolicy;
 6import java.lang.annotation.Target;
 7
 8import javax.validation.Constraint;
 9import javax.validation.Payload;
10
11//BeanValidationの制約のアノテーション
12@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
13@Retention(RetentionPolicy.RUNTIME)
14@Documented
15@Repeatable(UserMailPattern.List.class) // 対応していないので、定義しなくても良い。
16@Constraint(validatedBy=UserMailPatternValidator.class)
17public @interface UserMailPattern {
18
19    // 共通の属性の定義
20    Class<?>[] groups() default {};
21    String message() default "{sample.spring.UserMailPattern.message}";
22    Class<? extends Payload>[] payload() default {};
23
24    // 複数のアノテーションを指定する場合の定義
25    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
26    @Retention(RetentionPolicy.RUNTIME)
27    @Documented
28    @interface List {
29        UserMailPattern[] value();
30    }
31
32}

ConstraintValidator の実装中で、SpringBeanのインジェクションを行いたい場合は、アノテーション @Resource/@Autowired など使います。

ConstraintValidator 自身は、SpringBeanとして登録する必要はありません。

 1import javax.validation.ConstraintValidator;
 2import javax.validation.ConstraintValidatorContext;
 3
 4import org.springframework.beans.factory.annotation.Autowired;
 5
 6
 7// ConstraintValidatorの実装
 8public class UserMailPatternValidator implements ConstraintValidator<UserMailPattern, String> {
 9
10    // SpringBeanをインジェクションします。
11    @Autowired
12    private UserService userService;
13
14    @Override
15    public void initialize(final UserMailPattern constraintAnnotation) {
16
17    }
18
19    @Override
20    public boolean isValid(final String value, final ConstraintValidatorContext context) {
21
22        // nullの場合は対象外
23        if(value == null) {
24            return true;
25        }
26
27        return userService.isMailPattern(value);
28    }
29
30}

5.6.4.4. CsvBeanの定義

CSVのBeanの定義では、@CsvBean(validator=CsvBeanValidator.class) で、CsvBeanValidatorを指定します。

 1import com.github.mygreen.supercsv.annotation.CsvBean;
 2import com.github.mygreen.supercsv.annotation.CsvColumn;
 3import com.github.mygreen.supercsv.validation.beanvalidation.CsvBeanValidator;
 4
 5
 6@CsvBean(header=true, validator=CsvBeanValidator.class)
 7public class UserCsv {
 8
 9    @CsvColumn(number=1, label="メールアドレス")
10    @UserMailPattern   // 独自のBeanValidator用のアノテーションの指定
11    private String mail;
12
13    // setter/getterは省略
14
15}

5.6.4.5. 値の検証方法

  • BeanMappingFactory#getConfiguration() 取得できる、システム設定に、SpringBeanFactoryを設定します。

  • CsvExceptionConverter#setMessageResolver(...) に、SpringBeanとして定義した SpringMessageResolver を設定します。

    • さらに、 CsvAnnotationBeanReader#setExceptionConverter(...) に、作成した CsvExceptionConverter を渡します。

 1import com.github.mygreen.supercsv.builder.BeanMapping;
 2import com.github.mygreen.supercsv.builder.BeanMappingFactory;
 3import com.github.mygreen.supercsv.io.CsvAnnotationBeanReader;
 4
 5import java.nio.charset.Charset;
 6import java.nio.file.Files;
 7import java.io.File;
 8import java.util.ArrayList;
 9import java.util.List;
10
11import org.supercsv.prefs.CsvPreference;
12import org.supercsv.exception.SuperCsvException;
13
14@Service
15public class CsvService {
16
17    @Autowired
18    private SpringBeanFactory beanFactory;
19
20    @Autowired
21    private SpringMessageResolver messageResolver;
22
23    public void sampleSpring() {
24
25        // BeanMappingの作成 - SpringBeanFactoryを設定する
26        BeanMappingFactory beanMappingFactory = new BeanMappingFactory();
27        beanMappingFactory.getConfiguration().setBeanFactory(beanFactory);
28
29        // BeanMappingの作成
30        BeanMapping<UserCsv> beanMapping = mappingFactory.create(UserCsv.class);
31
32        CsvAnnotationBeanReader<UserCsv> csvReader;
33        try {
34            csvReader = new CsvAnnotationBeanReader<>(
35                    beanMapping,
36                    Files.newBufferedReader(new File("user.csv").toPath(), Charset.forName("Windows-31j")),
37                    CsvPreference.STANDARD_PREFERENCE);
38
39            // CsvExceptionConverterの作成 - SpringMessageResolverを設定する
40            CsvExceptionConverter exceptionConverter = new CsvExceptionConverter();
41            exceptionConverter.setMessageResolver(messageResolver);
42
43            // CsvExceptionConverterを設定する
44            svReader.setExceptionConverter(exceptionConverter);
45
46            // ファイルの読み込み
47            List<SampleCsv> list = csvReader.readAll();
48
49        } catch(SuperCsvException e) {
50
51            // 変換されたエラーメッセージの取得
52            List<String> messages = csvReader.getErrorMessages();
53
54        } finally {
55            if(csvReader != null) {
56                csvReader.close();
57            }
58        }
59    }
60
61}