package com.gh.mygreen.xlsmapper.validation.beanvalidation; import java.awt.Point; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Path; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import javax.validation.metadata.ConstraintDescriptor; import org.hibernate.validator.internal.engine.path.PathImpl; import com.gh.mygreen.xlsmapper.ArgUtils; import com.gh.mygreen.xlsmapper.Utils; import com.gh.mygreen.xlsmapper.validation.FieldError; import com.gh.mygreen.xlsmapper.validation.FieldErrorBuilder; import com.gh.mygreen.xlsmapper.validation.ObjectValidator; import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors; /** * BeanValidaion JSR-303(ver.1.0)/JSR-349(ver.1.1)を利用したValidator. * @author T.TSUCHIE * */ public class SheetBeanValidator implements ObjectValidator<Object> { /** * BeanValidationのアノテーションの属性で、メッセージ中の変数から除外するもの。 * <p>メッセージの再構築を行う際に必要 */ private static final Set<String> EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES; static { Set<String> set = new HashSet<String>(3); set.add("message"); set.add("groups"); set.add("payload"); EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES = Collections.unmodifiableSet(set); } private final Validator targetValidator; public SheetBeanValidator(final Validator targetValidator) { ArgUtils.notNull(targetValidator, "targetValidator"); this.targetValidator = targetValidator; } public SheetBeanValidator() { this.targetValidator = createDefaultValidator(); } /** * Bean Validatorのデフォルトのインスタンスを取得する。 * @return */ protected Validator createDefaultValidator() { final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); final Validator validator = validatorFactory.getValidator(); return validator; } /** * BeanValidationのValidatorを取得する。 * @return */ public Validator getTargetValidator() { return targetValidator; } @Override public void validate(final Object targetObj, final SheetBindingErrors errors) { validate(targetObj, errors, new Class<?>[0]); } /** * グループを指定して検証を実行する。 * @param targetObj 検証対象のオブジェクト。 * @param errors エラーオブジェクト * @param groups BeanValiationのグループのクラス */ public void validate(final Object targetObj, final SheetBindingErrors errors, final Class<?>... groups) { ArgUtils.notNull(targetObj, "targetObj"); ArgUtils.notNull(errors, "errors"); processConstraintViolation(getTargetValidator().validate(targetObj, groups), errors); } /** * BeanValidationの検証結果をSheet用のエラーに変換する * @param violations BeanValidationの検証結果 * @param errors シートのエラー */ protected void processConstraintViolation(final Set<ConstraintViolation<Object>> violations, final SheetBindingErrors errors) { for(ConstraintViolation<Object> violation : violations) { final String fieldName = violation.getPropertyPath().toString(); final FieldError fieldError = errors.getFirstFieldError(fieldName); if(fieldError != null && fieldError.isTypeBindFailure()) { // 型変換エラーが既存のエラーにある場合は、処理をスキップする。 continue; } final ConstraintDescriptor<?> cd = violation.getConstraintDescriptor(); final String errorCode = cd.getAnnotation().annotationType().getSimpleName(); final Map<String, Object> errorVars = createVariableForConstraint(cd); final String nestedPath = errors.buildFieldPath(fieldName); if(Utils.isEmpty(nestedPath)) { // オブジェクトエラーの場合 errors.rejectSheet(errorCode, errorVars, violation.getMessage()); } else { // フィールドエラーの場合 // 親のオブジェクトから、セルの座標を取得する final Object parentObj = violation.getLeafBean(); final Path path = violation.getPropertyPath(); Point cellAddress = null; String label = null; if(path instanceof PathImpl) { final PathImpl pathImpl = (PathImpl) path; cellAddress = Utils.getPosition(parentObj, pathImpl.getLeafNode().getName()); label = Utils.getLabel(parentObj, pathImpl.getLeafNode().getName()); } // 実際の値を取得する final Object fieldValue = violation.getInvalidValue(); if(!errorVars.containsKey("validatedValue")) { errorVars.put("validatedValue", fieldValue); } Class<?> fieldType = fieldValue != null ? fieldValue.getClass() : null; errors.addError(FieldErrorBuilder.create() .objectName(errors.getObjectName()).fieldPath(errors.buildFieldPath(fieldName)) .codes(errors.generateMessageCodes(errorCode, fieldName, fieldType), errorVars) .sheetName(errors.getSheetName()).cellAddress(cellAddress) .label(label) .defaultMessage(violation.getMessage()) .fieldValue(fieldValue) .build()); } } } /** * BeanValidationのアノテーションの値を元に、メッセージ変数を作成する。 * @param descriptor * @return メッセージ変数 */ protected Map<String, Object> createVariableForConstraint(final ConstraintDescriptor<?> descriptor) { final Map<String, Object> vars = new LinkedHashMap<String, Object>(); for(Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) { final String attrName = entry.getKey(); final Object attrValue = entry.getValue(); // メッセージ変数で必要ないものを除外する if(EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES.contains(attrName)) { continue; } vars.put(attrName, attrValue); } return vars; } }