少しずつ、Bean Validationの勉強をしていこうと思いまして。
以前、本当に少しだけやったのですが、その続きな感じですね。
Bean Validationを試してみる
http://d.hatena.ne.jp/Kazuhira/20140405/1396702413
今回のお題は、自分でValidatorを自作することです。
自分でValidatorを作るには、
を作成する必要があるようです。では、作ってみましょう。
今回は、配列で指定されたいずれかの値を取るようなValidatorを作成してみます。
@Select(Array("カツオ", "ワカメ", "波平", "フネ")) var name: String = _
準備
まずは、ビルド定義から。Java SE環境で動かすので、このような定義となりました。
build.sbt
name := "bean-validation-my-constraint" 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.4", "org.glassfish.web" % "javax.el" % "2.2.4", "org.scalatest" %% "scalatest" % "2.2.5" % "test" )
アノテーションとConstraintValidatorの実装の作成
では、Validatorの定義に移ります。
まずはアノテーションですが、このような形になりました。
src/main/java/org/littlewings/javaee7/beanvalidation/Select.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 = SelectValidator.class) public @interface Select { String message() default "{message.Select}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; String[] value(); }
@Constraintアノテーションで、実際にバリデーションを行うクラスを指定します。また、メッセージの定義はmessage()で行います。
メッセージはこのように書くと設定ファイルから参照することになりますが、
String message() default "{message.Select}";
message()に固定で書いてもよいらしいです。
String message() default "NGですよ";
また、今回作成するアノテーション固有のパラメーターとしては、Stringの配列を取るようにしました。
String[] value();
続いて、実際にバリデーションを行うクラス。
src/main/scala/org/littlewings/javaee7/beanvalidation/SelectValidator.scala
package org.littlewings.javaee7.beanvalidation import javax.validation.{ConstraintValidator, ConstraintValidatorContext} class SelectValidator extends ConstraintValidator[Select, String] { private var selectableValues: Array[String] = _ override def initialize(constraintAnnotation: Select): Unit = selectableValues = constraintAnnotation.value() override def isValid(value: String, context: ConstraintValidatorContext): Boolean = value match { case null => true case _ => selectableValues.exists(_ == value) } }
今回はシンプルな実装になりました。ConstraintValidatorインターフェースが型パラメータを取り、アノテーションとバリデーション対象の型を指定します。
class SelectValidator extends ConstraintValidator[Select, String] {
パラメーターを取る場合は、initializeで受け取ればよいみたいですね。
最後に、メッセージを定義します。クラスパス上に、「ValidationMessages.properties」という名前でファイルを作成します。
src/main/resources/ValidationMessages.properties
message.Select={value}のいずれかから選択してください
日本語で書いているように見えますが、実際にはこのファイルはnative2asciiによる変換が必要です…。
使ってみる
それでは、作成したValidatorを使ってみます。
Validatorを適用するクラスを定義。
src/test/scala/org/littlewings/javaee7/beanvalidation/Isono.scala
package org.littlewings.javaee7.beanvalidation import javax.validation.constraints.{NotNull, Size} class Isono { @NotNull @Size(min = 1, max = 3) @Select(Array("カツオ", "ワカメ", "波平", "フネ")) var name: String = _ }
他のバリデーション用のアノテーションも付けてみました。
テストコード。
src/test/scala/org/littlewings/javaee7/beanvalidation/MyConstraintSpec.scala
package org.littlewings.javaee7.beanvalidation import javax.validation.{ConstraintViolation, Validation} import org.scalatest.FunSpec import org.scalatest.Matchers._ class MyConstraintSpec extends FunSpec { describe("MyConstraint Spec") { // ここに、テストを書く! } }
まず、必須的な。
it("not null") { val isono = new Isono val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(isono) .toArray(Array.empty[ConstraintViolation[Any]]) constraintViolations should have size (1) constraintViolations.map(_.getMessage) should contain only ("may not be null") }
@Sizeと自作のValidatorでNGになるようにしてみます。
it("size & select") { val isono = new Isono isono.name = "フグ田サザエ" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(isono) .toArray(Array.empty[ConstraintViolation[Any]]) constraintViolations should have size (2) constraintViolations .map(_.getMessage) should contain only( "[カツオ, ワカメ, 波平, フネ]のいずれかから選択してください", "size must be between 1 and 3" ) }
自作のValidatorのみ。
it("select") { val isono = new Isono isono.name = "サザエ" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(isono) .toArray(Array.empty[ConstraintViolation[Any]]) constraintViolations should have size (1) constraintViolations.map(_.getMessage) should contain only ("[カツオ, ワカメ, 波平, フネ]のいずれかから選択してください") }
バリデーションOKの場合。
it("valid") { val isono = new Isono isono.name = "カツオ" val factory = Validation.buildDefaultValidatorFactory val validator = factory.getValidator val constraintViolations = validator .validate(isono) .toArray(Array.empty[ConstraintViolation[Any]]) constraintViolations should be(empty) }
大丈夫そうですね。
まずは、自作のValidatorということで。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/javaee7-scala-examples/tree/master/bean-validation-my-constraint