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

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

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

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

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

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

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}

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

  • @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}

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

アノテーションをハンドリングして、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}