BeanValidator? で、複合項目を検証するための方針 †
- getter にもアノテーションを付けられることを利用する
- そこで、複合項目 Composit を作成して、それを返す getter にアノテーションを付けて検証する
- 検証エラー次の項目名が getter から類推されるプロパティ名になることに注意
- ExamBean3 の getPasswordAndConfirm?() で同値検証をしているので、エラー項目名は passwordAndConfirm? になる
- index3.html で <input id="passwordAndConfirm?" name="confirm" type="text"/> としているのがミソ。同値検証エラー時には $('#' + エラー項目名) のCSSを変更すればよい
使い方 †
ExamBean3.java †
package com.mycompany.beanvalidatorexam;
import javax.validation.constraints.NotNull;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class ExamBean3 {
@NotNull
private String password;
@NotNull
private String confirm;
@CompositEqual
public Composit getPasswordAndConfirm() {
return new Composit(password, confirm);
}
}
Composit.java †
package com.mycompany.beanvalidatorexam;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Composit {
private Object a;
private Object b;
}
CompositEqual?.java †
package com.mycompany.beanvalidatorexam;
import com.mycompany.beanvalidatorexam.CompositEqual.DateValidator;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {DateValidator.class})
public @interface CompositEqual {
Class<?>[] groups() default {};
String message() default "Is not equal";
Class<? extends Payload>[] payload() default {};
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
CompositEqual[] value();
}
class DateValidator implements ConstraintValidator<com.mycompany.beanvalidatorexam.CompositEqual, Composit> {
@Override
public void initialize(CompositEqual constraintAnnotation) {
}
@Override
public boolean isValid(Composit value, ConstraintValidatorContext context) {
return value.getA().equals(value.getB());
}
}
}
JAX-RSから使ってみる †
index3.html †
<!DOCTYPE html>
<html lang="ja">
<head>
<title>Start Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="css/tooltipster.css" />
<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script type="text/javascript" src="js/jquery.tooltipster.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#frmValidate').on('submit', function() {
// form -> [{name:xxx, value:yyy},{name:ppp, value:qqq},...]
var params = $(this).serializeArray();
// -> {xxx:yyy, ppp:qqq, ...}
var args = new Object();
for (var cnt = 0; cnt < params.length; cnt++) {
args[ params[cnt].name ] = params[cnt].value;
}
// turn warings off
$(this).find('input').each(function(index, element) {
$(element).css({background: 'white'});
// tooltipstar throw exception if ui-component is not initialized as tooltipstar component.
try { $(element).tooltipster('destroy'); } catch (e) {}
});
$.ajax({
url: "webresources/exam/password",
type: "POST",
data: JSON.stringify(args),
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data) {
if (data.success) {
alert('Your order is accepted.');
return;
}
var violations = data.errors.entry;
console.log(violations);
for (var cnt = 0; cnt < violations.length; cnt++) {
var violation = violations[cnt];
var $component = $('#' + violation.key);
// turn warings on
if ($component) {
$component.css({background: 'pink'});
$component.tooltipster({postion:'top', content: violation.value}).tooltipster('show');
}
}
},
error: function(data, status) {
console.log(data);
console.log(status);
alert('Sorry, your order is abended.');
}
});
// reject from submit, we want to use ajax.
return false;
});
});
</script>
</head>
<body>
<h1>Hello Composit Validator!</h1>
<form id="frmValidate">
<table>
<tbody>
<tr> <td>New Password</td> <td> <input id="password" name="password" type="text"/> </td> </tr>
<tr> <td>Confirm</td> <td> <input id="passwordAndConfirm" name="confirm" type="text"/> </td> </tr>
<tr> <td colspan="2" align="right">
<button id="btnChange">Change Password</button>
</td></tr>
</tbody>
</table>
</form>
</body>
</html>
BVExamResource?.java †
package com.mycompany.beanvalidatorexam;
import java.util.Set;
import javax.enterprise.context.RequestScoped;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("exam")
@RequestScoped
public class BVExamResource {
@Context
private UriInfo context;
public BVExamResource() {
}
@POST
@Path("order")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public ResponseBean orderStock(ExamBean bean) {
ResponseBean<ExamBean> res = new ResponseBean();
// We can't @Inject Validator on Glassfish 4.0, 4.0.1 may be fix it.
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ExamBean>> violations = validator.validate(bean);
System.out.println(violations.toString());
if (!violations.isEmpty()) {
// There are some validation errors
res.setSuccess(false);
res.setValidationError(violations);
return res;
}
res.setSuccess(true);
return res;
}
@POST
@Path("pay")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public ResponseBean payment(ExamBean2 bean) {
ResponseBean<ExamBean2> res = new ResponseBean();
// We can't @Inject Validator on Glassfish 4.0, 4.0.1 may be fix it.
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ExamBean2>> violations = validator.validate(bean, bean.validateGroups());
System.out.println(violations.toString());
if (!violations.isEmpty()) {
// There are some validation errors
res.setSuccess(false);
res.setValidationError(violations);
return res;
}
res.setSuccess(true);
return res;
}
@POST
@Path("password")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public ResponseBean passwordChange(ExamBean3 bean) {
ResponseBean<ExamBean3> res = new ResponseBean();
// We can't @Inject Validator on Glassfish 4.0, 4.0.1 may be fix it.
final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ExamBean3>> violations = validator.validate(bean);
System.out.println(violations.toString());
if (!violations.isEmpty()) {
// There are some validation errors
res.setSuccess(false);
res.setValidationError(violations);
return res;
}
res.setSuccess(true);
return res;
}
}
実行結果 †
Java#Glassfish