JAX-RSをやってみる (14) - Bean Validation -
公式リファレンス: https://jersey.java.net/documentation/latest/user-guide.html#bean-validation
単純にアノテーションベースで入力検証のルールを定義出来る。別に説明しなくても良いんじゃねって事で
SampleBean.java
package sample.bean;
import java.io.Serializable;
import javax.validation.constraints.NotNull;
import javax.ws.rs.QueryParam;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class SampleBean implements Serializable {
private static final long serialVersionUID = 1L;
@QueryParam("name")
@NotNull
private String name;
public SampleBean() {
}
public SampleBean(String name) {
setName(name);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
んな感じでアノテーションベースで@NotNull等の検証ルール定義を付与する
Home.java
package sample.controller;
import javax.validation.Valid;
import javax.ws.rs.BeanParam;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import sample.bean.SampleBean;
@Path("/sample")
public class Home {
@GET
@Produces(MediaType.APPLICATION_JSON)
public SampleBean index(@Valid @BeanParam SampleBean bean) {
return bean;
}
}
な感じで@Validで指定している型のバリデーションを行い正常で無い場合にはValidationException(正確にはConstraiuntViolationException?)がスローされる(但し、ValidationExceptionMapperがあるのでレスポンスは変換される)
ValidationMessages.properties
Spring MVCと同様にバリデーションを行った際のエラーメッセージはカスタム化出来る
javax.validation.constraints.NotNull.message=may not be null
HomeTest.java
package sample.controller;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;
import org.junit.Test;
import sample.SampleApplication;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.*;
public class HomeTest extends JerseyTest {
@Override
protected Application configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
return new SampleApplication();
}
@Test
public void test_index() {
Response response = target("/sample").request(MediaType.APPLICATION_JSON).get();
// Validationにfailすると400になる
assertThat(response.getStatus(), is(200));
}
}
上記の場合データ(QueryParam)が指定されてないのでテストはコケる。そしてエラーメッセージがJSONで取得できる(正確には要求したMediaTypeにより変わる)
[
{
"message": "may not be null",
"messageTemplate": "{javax.validation.constraints.NotNull.message}",
"path": "Home.index.arg0.name"
}
]
検証アノテーションを自作する場合
@ConstraintでvalidatedByに指定したConstraintValidatorクラスにてバリデーション実装を定義すればいい
Hoge.java
package sample.validator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import static java.lang.annotation.ElementType.*;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = HogeValidator.class)
public @interface Hoge {
// ValidationMessagesでsample.validator.hoge.messageでエラーメッセージを設定出来るように
String message() default "{sample.validator.hoge.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
/* 公式的には載ってないけどBean Validationのリポジトリとかにはこれを持つべきっぽい(現時点で理由は不明)
@Retention(RetentionPolicy.RUNTIME)
@interface List {
Hoge[] value();
}
*/
}
ってな感じでアノテーションを作る。messageを{}で囲う事でMessageResource周りでエラーメッセージ等を定義出来たりすると思われる
HogeValidator.java
package sample.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
// StringじゃなくてTにして型検証を含めるべきなのか
public class HogeValidator implements ConstraintValidator<Hoge, String> {
@Override
public void initialize(Hoge constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value != null && "hoge".equals(value)) {
return true;
}
/* エラーメッセージのConstraintViolationを自処理で構築する場合?
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("err").addConstraintViolation();
*/
return false;
}
}
isValidメソッドで検証を実装するだけ。あとは普通に
package sample.controller;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import sample.validator.Hoge;
@Path("/sample")
public class Home {
@QueryParam("name")
@Hoge
private String name;
@GET
@Produces(MediaType.APPLICATION_JSON)
public String index() {
return name;
}
}
な感じでアノテーションを付与して使うだけ
余談1: エラー情報を取得する必要が無いような場合
package sample;
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.ServerProperties;
@ApplicationPath("/")
public class SampleApplication extends ResourceConfig {
public SampleApplication() {
// falseにするとエラー情報が取得出来ない
property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, false);
packages("sample");
}
}
余談2: バリデーションを自前で動かす方法
package sample.controller;
import java.util.Set;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import sample.bean.SampleBean;
@Path("/sample")
public class Home {
@Inject
private Validator validator;
@GET
@Produces(MediaType.APPLICATION_JSON)
public SampleBean index(@QueryParam("name") String name) {
SampleBean bean = new SampleBean(name);
Set<ConstraintViolation<SampleBean>> results = validator.validate(bean);
if (!results.isEmpty()) {
throw new ConstraintViolationException(results);
}
return bean;
}
}
てな感じで@Injectでjavax.validation.Validatorを使えば出来るっぽい。で上記でも書いたようにValidation ErrorなレスポンスはValidationExceptionMapperによってConstraintViolationExceptionが処理される?はずなのでそれをスローする形で良い。でその際の引数にvalidateをした結果のSet<ConstraintViolation<T>>を指定してやれば良い模様
とりあえずはこんな感じで。まだまだ曖昧な所あるので追記するか新しくネタ書くかも