[Java1.8] bean-validationで相関バリデーションをしてみた

# 概要

[Spring Expression Language][spel]を使って相関バリデーションしてみました。
相関バリデーションのような複雑なバリデーションルールを記述する方法としては、

0. ELを使う方法
1. Entityのなかに、valideメソッドを実装する方法

があるかとおもいますが、実際に書いてみました。

_制約もスキーマ情報であること、制約条件は制約対象のできるだけ近くに記述されていること、宣言的であること、が大切だと思います。_

さらに、groupsの使い方を確認してみました。
一般的にアノテーションのgroupsに設定するラベル的な使い方が紹介される事が多いと思いますが、ラベルとしてではなく実際にインターフェースとして使う方法です。


## 環境
* JDK1.8

## ライブラリ
* javax-validation-api
* spring-mvc
* spring-context
* hibernate-validation
* ほか

## 参考
[[JSR 303] :Bean Validation](http://beanvalidation.org/1.0/spec/)
[Spring framework Reference document][spring]
[oval object validation framework][oval]

# 実装
## コード
```Java:ValidTrue.java
@Target(value={ TYPE,FIELD,METHOD })
@Retention(RUNTIME)
@Constraint(validatedBy={ SpELValidator.class })
public @interface ValidTrue{
    String value() default "true";
    String message() default "error!";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
```
```Java:SpELValidator.java
public class SpELValidator implements ConstraintValidator<ValidTrue, Object>{
	ExpressionParser parser =  new SpelExpressionParser();
	ValidTrue annotation;

	@Override
	public void initialize(ValidTrue annotation) {
		this.annotation = annotation;
	}
	@Override
	public boolean isValid(Object value, ConstraintValidatorContext context) {
		context.buildConstraintViolationWithTemplate(annotation.message());
		
		EvaluationContext evContext = new StandardEvaluationContext(value);
		
		Expression exp = parser.parseExpression(annotation.value());
		return (boolean)exp.getValue(evContext, boolean.class);
	}
}
```
## test

### 1. @ValidTrue type

```Java:Entity.java
@ValidTrue("from < to")
public class Entity{
	public LocalDate from;
	public LocalDate to;
}
```
### 2. @AsserTrue type
```Java:Entity.java
//@ValidTrue("from < to")
public class Entity{
	public LocalDate from;
	public LocalDate to;

    @AssertTrue
    private isValid(){ return from.isBefore(to); }
}
```
```Java:ValidTrueTest.java
public class ValidTrueTest {
	@Test
	public void test() throws Exception{
		ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
       
        Entity e = new Entity();
        e.from = LocalDate.now();
        e.to = LocalDate.MIN;
        validator.validate(e).stream().peek(System.out::println).findFirst().ifPresent( Assert::assertNotNull );
    }
}
```
## 結果
    ConstraintViolationImpl{interpolatedMessage='error!', propertyPath=, rootBeanClass=class com.test.batch.Entity, messageTemplate='error!'}

１の例は、[SpEL][spel]でなくても基本同じだと思いますが、ELの表現力の及ぶかぎりどんなバリデーションルールでもかけます。
booleanを返せばいいだけ。
２の例は標準のアノテーション@AssertTrueを用いるわけですが、メソッドとして定義する（インターフェースを変更してしまう）のが個人的にどうかと思ってしまいます。
なので、privateで宣言してみました。
これだと気になりません。
ところが・・・

# groupsを使ってみる

##test

### 1. @ValidTrue type
```Java:TypeA.java
@ValidTrue("from<to")
public interface TypeA  {	
}
```
```Java:TypeB.java
@ValidTrue("from>to")
public interface TypeB  {	
}
```
```Java:Entity.java
public class Entity implements TypeA,TypeB{
	public LocalDate from;
	public LocalDate to;
```

```Java:ValidTrueTest.java
public class ValidTrueTest {
	@Test
	public void test() throws Exception{
		ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
       
        Entity e = new Entity();
        e.from = LocalDate.now();
        e.to = LocalDate.MIN;
        validator.validate(e,TypeA.class).stream().peek(System.out::println).findFirst().ifPresent( Assert::assertNotNull );
    }
}
```

### 2. @AssertTrue type
```Java:TypeA.java
public interface TypeA  {	
    public LocalDate getFrom();
    public LocalDate getTo();

    @AssertTrue
    public default boolean isValid(){
        return getFrom().isBefore(getTo());
    }
}
```
```Java:TypeB.java
public interface TypeB  {	
    public LocalDate getFrom();
    public LocalDate getTo();

    @AssertTrue
    public default boolean isValid(){
        return getFrom().isAfter(getTo());
    }
}
```
```Java:Entity.java
public class Entity implements TypeA,TypeB{
    @NotNull
	public LocalDate from;
    @NotNull
	public LocalDate to;

    @Override
    public LocalDate getFrom(){ return from; }
    @Override
    public LocalDate getTo(){ return to; }
```

```Java:ValidTrueTest.java
public class ValidTrueTest {
	@Test
	public void test() throws Exception{
		ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
        Validator validator = validatorFactory.getValidator();
       
        Entity e = new Entity();
        e.from = LocalDate.now();
        e.to = LocalDate.MIN;
        validator.validate(e,Entity.class,TypeA.class).stream().peek(System.out::println).findFirst().ifPresent( Assert::assertNotNull );
    }
}
```
##結果

制約条件が多いとき、一つのエンティティにTypeA用の制約とTypeB用の制約を沢山書くとごちゃごちゃするなら、このようにインターフェースに制約を分けて書くのは良い気がします。

仮面を張り替えるようなイメージでしょうか。
getter/setterもきちんと書いておけば、TypeA型として操作できますし。同じBeanだけど内容によって複数の意味に切り替えられます。

例）
* 未実効の申請 / 実行中の申請 / 実行済みの申請　（時間によって遷移する場合）
* 固定金利ローン / 変動金利ローン　（そもそも複数のタイプがある場合）
などなど。

それぞれインターフェースを用意してvalidationを切り換えつつ、それぞれ別々のインターフェースを公開できます。この段階ならこの値がはいってないといけない、などなど。

# 結論
* group便利！
* ValidTrue悪くないと思うんだが・・・AssertTrueで良いといわれると・・・インターフェース自身に定義されていない要素もELだと引っ張ってこれるのは便利ですね。

---

Qiita初めての投稿になります。もし内容、書き方など問題があればご指摘くださいませ。


[oval]:http://oval.sourceforge.net/
[spring]:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/
[spel]:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#expressions
[evernote]:https://evernote.com/intl/jp/