Bean Validationで自作バリデータを作る

## 概要
Bean Validationで自作バリデータを書いて、単体テストを書くまでの手順のメモ。

## やってみる
試しにマイケルジャクソンの楽曲名かどうかを判定するバリデータを作ってみる。

### バリデータクラスの実装
```java
package com.example.customvalidate;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.ArrayList;
import java.util.List;

public class MJValidator implements ConstraintValidator<MJ, String> {

    private final static List<String> songs = new ArrayList<String>() {
        {
            add("Remember The Time"); add("Stranger In Moscow"); add("You Are Not Alone");
        }
    };

    public void initialize(MJ constraintAnnotation) {
    }

    public boolean isValid(String value, ConstraintValidatorContext context) {
        return songs.stream().anyMatch(song -> song.equals(value));
    }
}

```

#### 解説
- `ConstraintValidator`インタフェースを継承させる。ジェネリクスには、あとで作るアノテーションとバリデーション対象の型を指定する。
- `isValid`メソッドで実際のバリデーションの判定を行っている。任意の判定をしてbooleanを返す。
- 今回の例では、「Remember The Time」か「Stranger In Moscow」か「You Are Not Alone」ならバリデーションが通る。

### アノテーションの実装

アノテーションを作る。これで `@MJ`って感じで使えるようになる

```java
package com.example.customvalidate;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = {MJValidator.class})
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MJ {

    String message() default "you must specify one of Michael Jackson's songs.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        MJ[] value();
    }
}
```

#### 解説
- `@Constraint(validatedBy=)`のところには実際にバリデーションを行うクラスを指定する。上で作った`MJValidator`を指定する。
- `@Target`には、このアノテーションを付与できる対象を指定する。今回は引数とフィールドとした。
- `message`には、バリデーションに引っかかった際に例外オブジェクトに設定されるメッセージを指定する。

### テストを書く
```java
package com.example.customvalidate;

import org.junit.Before;
import org.junit.Test;
import javax.validation.*;
import java.util.Set;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

public class MJValidatorTest {

    private Validator validator;

    @Before
    public void setUp() {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    @Test
    public void 正しくない曲名を渡したときエラーとなること() {
        TestBean bean = new TestBean("Remember a Time");
        Set<ConstraintViolation<Object>> violations = validator.validate(bean);
        assertThat(violations.isEmpty(), is(false));
        String expectedMessage = "you must specify one of Michael Jackson's songs.";
        violations.forEach(v -> assertThat(v.getMessage().equals(expectedMessage), is(true)));
    }

    @Test
    public void 正しい曲名を渡したときエラーとならないこと() {
        TestBean bean = new TestBean("Remember The Time");
        Set<ConstraintViolation<Object>> violations = validator.validate(bean);
        assertThat(violations.isEmpty(), is(true));
    }

    private static class TestBean {
        @MJ
        private String song;

        TestBean(String song) {
            this.song = song;
        }
    }
}
```

実行して、テストが通っていればok.

### 解説
- テストクラスの内部クラスとして適当なクラスを作り、フィールドに今回作ったアノテーションを設定している
- そのクラスのインスタンスに対してバリデーションを走らせている