確認する順番が前後しているような気がしますが、Bean Validationで使用するアノテーションを定義する時は一緒にListも定義するのを慣例として覚えておいた方が良いみたいです。
Listって何かというと、例えば@Size.Listみたいなので、バリデーション対象に複数の同じ種類のバリデーションルールを付与するのに使います。これを使うと、(こちらはあまりないかもしれませんが)バリデーション対象に対して同じルールを複数摘要したり、グループを指定することで同じルールでも内容を切り替えることができるみたいです。
参考)
JSR 303 Bean Validationで遊んでみるよ!
http://yamkazu.hatenablog.com/entry/20110206/1296985545
はじめてのBean Validation その4
http://d.hatena.ne.jp/shin/20100115/p4
確認のために、自分でアノテーションを定義しつつ、テストをして確認してみましょう。
準備
ビルド定義は、こちら。
build.sbt
name := "bean-validation-list" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.6" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") updateOptions := updateOptions.value.withCachedResolution(true) libraryDependencies ++= Seq( "org.hibernate" % "hibernate-validator" % "5.1.3.Final", "javax.el" % "javax.el-api" % "2.2.5", "org.glassfish.web" % "javax.el" % "2.2.6", "org.scalatest" %% "scalatest" % "2.2.5" % "test" )
バリデーションを作る
すでに標準のバリデーション用のアノテーションに、Listも一緒に定義されていますが、それをそのまま使ったのでは面白くないので、自分で作ってみます。
今回作ったのは、自作のSize。MySizeとしましょう。
src/main/java/org/littlewings/javaee7/beanvalidation/MySize.java
package org.littlewings.javaee7.beanvalidation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.validation.Constraint; import javax.validation.Payload; @Documented @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = MySizeValidator.class) public @interface MySize { String message() default "{javax.validation.constraints.Size.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int min(); int max(); @Documented @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface List { MySize[] value(); } }
ここでのポイントは、アノテーション内部に定義されたアノテーション。
@Documented @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface List { MySize[] value(); }
外側に定義したアノテーションを、配列で持つように宣言するみたいです。
で、これに体操するValidatorも一応用意。簡単のため、Stringのみを対象にしました。
src/main/scala/org/littlewings/javaee7/beanvalidation/MySizeValidator.scala
package org.littlewings.javaee7.beanvalidation import javax.validation.{ConstraintValidator, ConstraintValidatorContext} import org.jboss.logging.Logger class MySizeValidator extends ConstraintValidator[MySize, String] { var min: Int = _ var max: Int = _ override def initialize(constraintAnnotation: MySize): Unit = { min = constraintAnnotation.min max = constraintAnnotation.max } override def isValid(value: String, context: ConstraintValidatorContext): Boolean = { val logger = Logger.getLogger(getClass) logger.infof("Constraint[%s], property[%s]", classOf[MySize].getSimpleName, value.asInstanceOf[Any]) value != null && value.size >= min && value.size <= max } }
グループを用意
グループを使った確認のために、グループ用のインターフェースを用意しておきます。
src/test/java/org/littlewings/javaee7/beanvalidation/MyGroup.java
package org.littlewings.javaee7.beanvalidation; public interface MyGroup { }
アノテーションを使ったクラスを定義
それでは、ここまで用意したアノテーションおよびグループを使ったクラスを定義します。
src/test/java/org/littlewings/javaee7/beanvalidation/Person.java
package org.littlewings.javaee7.beanvalidation; import javax.validation.constraints.Pattern; public class Person { @MySize.List({ @MySize(min = 3, max = 3), @MySize(min = 4, max = 6, groups = MyGroup.class) }) public String firstName; @Pattern.List({ @Pattern(regexp = "^磯.*"), @Pattern(regexp = ".*野$") }) public String lastName; }
これをScalaで書くと、ちょっとわかりにくいのでJavaにしました…。
ここは、Defaultグループの場合はmin = 3、max = 3となり、MyGroupグループの場合はmin = 4, max = 6という意味になります。
@MySize.List({ @MySize(min = 3, max = 3), @MySize(min = 4, max = 6, groups = MyGroup.class) }) public String firstName;
ここの場合、「磯」で始まり、「野」で終わらなければなりません。対象のグループは、Defaultグループです。
@Pattern.List({ @Pattern(regexp = "^磯.*"), @Pattern(regexp = ".*野$") }) public String lastName;
意味ないですけど!!
テストしてみる
それでは、テストコードで動作確認。
src/test/scala/org/littlewings/javaee7/beanvalidation/AnnotationListSpec.scala
package org.littlewings.javaee7.beanvalidation import javax.validation.{ConstraintViolation, Validation} import org.scalatest.FunSpec import org.scalatest.Matchers._ class AnnotationListSpec extends FunSpec { describe("AnnotationList Spec") { // ここに、テストを書く! } }
まずは、DefaultグループでバリデーションOKのパターン。
it("Default Group, valid") { val person = new Person person.firstName = "カツオ" person.lastName = "磯野" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(person) .toArray(Array.empty[ConstraintViolation[Any]]) .sortWith(_.getPropertyPath.toString < _.getPropertyPath.toString) constraintViolations should be(empty) }
MyGroupに指定したバリデーションは、ここでは動作していませんね。
続いて、DefaultグループでNGとなるパターン、その1。
it("Default Group, invalid case 1") { val person = new Person person.firstName = "カツオ" person.lastName = "磯の" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(person) .toArray(Array.empty[ConstraintViolation[Any]]) .sortWith(_.getPropertyPath.toString < _.getPropertyPath.toString) constraintViolations should have size (1) constraintViolations(0).getPropertyPath.toString should be("lastName") constraintViolations(0).getMessage should be("must match \".*野$\"") }
その2。こちらは、firstNameもNGとなるようにしました。
it("Default Group, invalid case 2") { val person = new Person person.firstName = "カツオ?" person.lastName = "いそ野" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(person) .toArray(Array.empty[ConstraintViolation[Any]]) .sortWith(_.getPropertyPath.toString < _.getPropertyPath.toString) constraintViolations should have size (2) constraintViolations(0).getPropertyPath.toString should be("firstName") constraintViolations(0).getMessage should be("size must be between 3 and 3") constraintViolations(1).getPropertyPath.toString should be("lastName") constraintViolations(1).getMessage should be("must match \"^磯.*\"") }
@Pattern.Listに指定した内容がANDでバリデーションされていること、そしてfirstNameに指定した@MySize.Listもデフォルトのものしか動作していないことがわかります。
続いて、MyGroupグループを指定してバリデーションOKの場合。
it("MyGroup, valid") { val person = new Person person.firstName = "katsuo" person.lastName = "isono" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(person, classOf[MyGroup]) .toArray(Array.empty[ConstraintViolation[Any]]) .sortWith(_.getPropertyPath.toString < _.getPropertyPath.toString) constraintViolations should be(empty) }
ここでは、@Pattern.Listはまったく見られていないことがわかります(両方ともDefaultグループなので)。
MyGroupグループでNGとなるパターン。
it("MyGroup, invalid") { val person = new Person person.firstName = "isono katsuo" person.lastName = "isono" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(person, classOf[MyGroup]) .toArray(Array.empty[ConstraintViolation[Any]]) .sortWith(_.getPropertyPath.toString < _.getPropertyPath.toString) constraintViolations should have size(1) constraintViolations(0).getPropertyPath.toString should be("firstName") constraintViolations(0).getMessage should be("size must be between 4 and 6") }
MyGroupで指定したバリデーションが、ちゃんと動作しているようです。
というわけで
Listを使ったアノテーション定義と、その利用について確認してみました。今度から、Bean Validationを使ったアノテーションを定義する時は、一緒にListも付けることにしましょう…。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/bean-validation-list