独自の入力チェックを実装する

【前提条件】

[環境]
[参考資料]

Bean Validation1.1仕様書
The Java Community Process(SM) Program - communityprocess - final

[その他]

Validatorの生成はCDIを利用しています。

【概要】

前回まではBean Validationの組み込みアノテーションを使用していましたが、
今回は独自の入力チェックを実装します。

アノテーション

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;
import jp.glory.sample02.validator.MailAddressValidator;
import org.hibernate.validator.constraints.NotEmpty;

@Constraint(validatedBy = MailAddressValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@NotEmpty
public @interface MailAddress {
    String message() default "MailAddress is invalid";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

独自の入力チェックをするためには
javax.validation.Constraintアノテーションを付与します。
ConstraintのvalidatedByに入力チェックを行うクラスを指定します。

独自のアノテーションのプロパティには
message()、groups()、payload()の指定が必要となります。

message()にはデフォルトのエラーメッセージを指定します。
groups()、payload()荷は必ず空の配列を指定します。

サンプルではNotEmptyアノテーションを付与しています。

【入力チェッククラス】

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import jp.glory.sample02.annotation.MailAddress;


public class MailAddressValidator implements ConstraintValidator<MailAddress, String> {

    @Override
    public void initialize(MailAddress constraintAnnotation) {
        System.out.println("Initialized MailAddressValidator");
    }

    @Override
    public boolean isValid(final String value, final ConstraintValidatorContext context) {

        if (value == null || value.isEmpty()) {

            return true;
        }

        if (!value.matches("@")) {
            return false;
        }

        if(value.split("@").length != 2) {
            return false;
        }

        return true;
    }
    
}

入力チェックのクラスはjavax.validation.ConstraintValidatorインターフェイスを実装します。
型パラメータはチェックするアノテーション、チェック対象の値の型を指定します。

initializeメソッドには入力チェッククラスが初期化されたときに実行する処理を記述します。

initializeが読み出されるタイミングは
アノテーションをつけた箇所が始めて実行される時になります。
同じBean内でアノテーションが2つある場合には2回初期化されます。

isValidメソッドにはチェックを行う処理を記述します。
チェックエラーがない場合はtrueを、チェックエラーがある場合はfalseを返却します。

今回のサンプルではvalueの値が未設定の場合はtrueを返却しています。
サンプルではMailAddressアノテーションにNotEmptyアノテーションを付与しているので
null、空文字の場合はtrueを返すようにしています。

falseを返すようにして、空文字が設定された場合、
NotEmptyとMailAddressでチェックエラーとなり、2つのメッセージが表示されます。

【動作確認用ソース】

Beanとリソースクラスです。

public class MailAddressBean {

    @FormParam("name")
    @NotEmpty
    private String name = null;

    @FormParam("mail1")
    @MailAddress
    private String mail1 = null;

    @FormParam("mail2")
    @MailAddress(message =  "メールアドレス2が不正です。")
    private String mail2 = null;

    // アクセサメソッドは省略
}
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Set;
import javax.inject.Inject;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.ws.rs.BeanParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import jp.glory.sample02.bean.MailAddressBean;


@Path("/sample02")
public class MailAddressResource {

    @Inject
    private Validator validator = null;
    
    @POST
    public Response postRequest(@BeanParam final MailAddressBean bean) throws ParseException {

        final Set<ConstraintViolation<MailAddressBean>> result =  validator.validate(bean);

        if (result.isEmpty()) {

            return createSuccessResponse();
        }

        return createErrorResponse(result);
    }

    private Date convertDate(final String dateValue) throws ParseException {
        final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

        if (dateValue.isEmpty()) {
            return null;
        }
        return format.parse(dateValue);
    }

    private Response createErrorResponse(final Set <ConstraintViolation<MailAddressBean>> validateResult) {
        
        final StringBuilder builder = new StringBuilder();
        
        builder.append("<html>");
        builder.append("<head>");
        builder.append("<meta charset=\"utf-8\">");
        builder.append("<title>Validation</title>");
        builder.append("</head>");
        builder.append("<body>");

        for (final ConstraintViolation<MailAddressBean> result : validateResult) {
            
            builder.append("<div>");
            builder.append(result.getPropertyPath().toString());
            builder.append(" : ");
            builder.append(result.getInvalidValue());
            builder.append(" : ");
            builder.append(result.getMessage());
            builder.append("</div>");
        }

        builder.append("</body>");
        builder.append("</html>");
     
        return Response.ok(builder.toString()).build();
    }

    private Response createSuccessResponse() {
        
        final StringBuilder builder = new StringBuilder();
        
        builder.append("<html>");
        builder.append("<head>");
        builder.append("<meta charset=\"utf-8\">");
        builder.append("<title>Validation</title>");
        builder.append("</head>");
        builder.append("<body>");
        builder.append("<div>Input value is valid!</div>");
        builder.append("</body>");
        builder.append("</html>");
     
        return Response.ok(builder.toString()).build();
    }
}