サンプルプログラム †
これはなに? †
- JSR 303 http://beanvalidation.org/1.0/spec/
- Bean に、値検証のための Annotation をつけて検証を行う
- Annotation は、プロパティ と getter につけることができる
- 画面からは ISO8601 の文字列で日付を受け取り、Date型で検証したいとき
private String dateString;
@Future
public Date getDate() {
try {
return (new SimpleDateFormat("yyyy-MM-dd")).parse(dateString);
} catch (ParseException ex) {
return null;
}
}
- 項目間の検証をしたいとき → Glassfish BeanValidator 複合項目のValidation
private String password;
private String confirm;
@Equal
public Composit getPasswordAndConfirm() {
return Composit(password, confirm);
}
@Data
@AllArgsConstrunctor
public class Composit {
private Object x;
private Object y;
}
標準の Annotation †
@AssertFalse? | boolean | Falseか検証 |
@AssertTrue? | boolean | Trueか検証 |
@NotNull? | Object | Nullでないか検証。""はNullでないことに注意。@NotEmpty?を作る必要あり |
@Null | Object | Nullか検証。""はNullでないことに注意。@Emptyを作る必要あり |
@DecimalMax?(value = "100") | String, int, BigDecimal? | 最大値 |
@DecimalMin?(value = "0") | String, int, BigDecimal? | 最小値 |
@Digits(integer = 3, fraction = 2) | BigDecimal? | 桁数 |
@Future | Date, Calendar | 現在時刻よりも未来日付 |
@Past | Date, Calendar | 現在時刻よりも過去日付 |
@Max(value = 500) | String, int | 最大値 |
@Min(value = 0) | String, int | 最小値 |
@Size(min = 0, max = 255) | 桁数(String#length())。リストの長さ(Collections#size()) |
@Pattern(regexp = "[0-9]+[.][A-Z]") | String | 正規表現 |
使い方 †
ExamBean?.java †
package com.mycompany.beanvalidatorexam;
import java.io.Serializable;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.validation.constraints.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@ToString
public class ExamBean implements Serializable {
/**
* True かどうか検証する.
* @AssertTrue、@AssertFalse、@Null は、通常単独では使わない。
* group と一緒に使い、特定の場合には true/false/null になることを検証する
*/
@AssertTrue
private boolean agree;
/**
* 数値の大小・桁数検証.
* BigDecimal のほかに int、String の検証も可能.
*/
@DecimalMax(value = "100")
@DecimalMin(value = "0")
@Digits(integer = 3, fraction = 2)
@NotNull
private BigDecimal price;
/**
* 数値の大小検証.
* int のほかに String の検証が可能.
*/
@Max(value = 500)
@Min(value = 0)
@NotNull
private int amount;
/**
* 文字列長の検証.
* String#length() のほかに Collection#size() の検証もできる.
*/
@Size(min = 0, max = 255)
@NotNull
private String item;
/**
* 正規表現の検証.
*/
@Pattern(regexp = "[0-9]+[.][A-Z]")
@NotNull
private String code;
/**
* Annotationは、getterにつけてもよい
*/
private String payDateString;
/**
* 未来日検証.
* Date、Calendar の検証ができる
* @Future のほかに @Past で過去日かどうかを検証することができる。
* どっちも実際には使わないだろーけど
* @return 支払日
*/
@Future
@NotNull
public Date getPayDate() {
try {
return (new SimpleDateFormat("yyyy-MM-dd").parse(this.payDateString));
} catch (ParseException | NullPointerException ex) {
ex.printStackTrace();
return null;
}
}
}
Validation †
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<ExamBean>> violations = validator.validate(bean);
- Validator は ValidatorFacotry? から作成する
- Glassfish 4.0.1 からは ValidatorFactory? を Injection してくれるらしい。Glassfish 4.0 では上記のように自前でインスタンス化するひつようあり
- validator.validate(bean) で検証
- 検証結果は Set<ConstraintViolation?<ExamBean?>> に格納される
JAX-RSから使ってみる †
index.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() {
// Set the default value of payDate.
var tomorrow = new Date(new Date().getTime() + 86400000);
var year = ('000' + tomorrow.getFullYear()).slice(-4);
var month = ('0' + (tomorrow.getMonth() + 1)).slice(-2);
var date = ('0' + (tomorrow.getDate())).slice(-2);
$('#payDate').val(year + '-' + month + '-' + date);
$('#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/order",
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;
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 Bean Validator!</h1>
<form id="frmValidate">
<table>
<tbody>
<tr> <td>Item</td> <td> <input id="item" name="item" type="text" value="ニコニコ証券"/> </td> </tr>
<tr> <td>Code</td> <td> <input id="code" name="code" type="text" value="9876.T"/> </td> </tr>
<tr> <td>Price</td> <td> <input id="price" name="price" type="text" value="123.45"/> </td> </tr>
<tr> <td>Amount</td> <td> <input id="amount" name="amount" type="text" value="450"/> </td> </tr>
<tr> <td>Pay Date</td> <td> <input id="payDate" name="payDateString" type="date"/> </td> </tr>
<tr> <td colspan="2" align="left">
<input id="agree" name="agree" type="checkbox" value="true"/>
<label for="agree">I Agree</label>
</td></tr>
<tr> <td colspan="2" align="right">
<button id="btnOrder">Order</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;
}
}
- ブラウザに、検証結果を返却するために ResponseBean? に、BeanValidator? の検証結果を格納する
- Response Bean は、エラー項目とエラーメッセージを格納しているだけ
ResponseBean?.java †
package com.mycompany.beanvalidatorexam;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Path;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class ResponseBean<T> implements Serializable {
private boolean success;
private String message;
private Map<Path, String> errors;
public void setValidationError(Set<ConstraintViolation<T>> verrors) {
errors = new HashMap<>();
for (ConstraintViolation<T> verror : verrors) {
errors.put(verror.getPropertyPath(), verror.getMessage());
}
}
}
実行結果 †
参考文献 †
- JSR 303: Bean Validation, Red Hat, 2009, http://beanvalidation.org/1.0/spec/
- JavaTM Platform, Enterprise Edition 6
API Specification, Oracle, 2011, http://docs.oracle.com/javaee/6/api/index.html?javax/validation/constraints/package-summary.html
- JSR 303 Bean Validationで遊んでみるよ!, Yamkazu's Blog, 2011, http://yamkazu.hatenablog.com/entry/20110206/1296985545
- 私のBeanValidation?の使い方(Java EE Advent Calendar 2013), 裏紙, 2013, http://backpaper0.github.io/2013/12/03/javaee_advent_calendar_2013.html
- はじめてのBean Validation, しんさんの出張所 はてな編, 2010, http://d.hatena.ne.jp/shin/20100112/p5
- はじめてのBean Validation その2, しんさんの出張所 はてな編, 2010, http://d.hatena.ne.jp/shin/20100113/p1
- JavaEE 6のBean Validationがエンティティ バリデーションのメタデータモデルとAPIを提供, InfoQ, 2010, http://www.infoq.com/jp/news/2010/03/javaee6-validation
Java#Glassfish