SheetBeanValidator.java

  1. package com.gh.mygreen.xlsmapper.validation.beanvalidation;

  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. import java.util.Collections;
  5. import java.util.HashMap;
  6. import java.util.HashSet;
  7. import java.util.Map;
  8. import java.util.Objects;
  9. import java.util.Optional;
  10. import java.util.Set;

  11. import javax.validation.ConstraintViolation;
  12. import javax.validation.Path;
  13. import javax.validation.Validation;
  14. import javax.validation.Validator;
  15. import javax.validation.ValidatorFactory;
  16. import javax.validation.metadata.ConstraintDescriptor;

  17. import org.hibernate.validator.internal.engine.path.NodeImpl;
  18. import org.hibernate.validator.internal.engine.path.PathImpl;
  19. import org.slf4j.Logger;
  20. import org.slf4j.LoggerFactory;

  21. import com.gh.mygreen.xlsmapper.fieldaccessor.LabelGetterFactory;
  22. import com.gh.mygreen.xlsmapper.fieldaccessor.PositionGetterFactory;
  23. import com.gh.mygreen.xlsmapper.localization.MessageInterpolator;
  24. import com.gh.mygreen.xlsmapper.localization.ResourceBundleMessageResolver;
  25. import com.gh.mygreen.xlsmapper.util.ArgUtils;
  26. import com.gh.mygreen.xlsmapper.util.CellPosition;
  27. import com.gh.mygreen.xlsmapper.util.Utils;
  28. import com.gh.mygreen.xlsmapper.validation.FieldError;
  29. import com.gh.mygreen.xlsmapper.validation.ObjectValidator;
  30. import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors;
  31. import com.gh.mygreen.xlsmapper.validation.fieldvalidation.FieldFormatter;


  32. /**
  33.  * Bean Validaion JSR-303(ver.1.0)/JSR-349(ver.1.1)/JSR-380(ver.2.0)を利用したValidator.
  34.  *
  35.  * @version 2.3
  36.  * @author T.TSUCHIE
  37.  *
  38.  */
  39. public class SheetBeanValidator implements ObjectValidator<Object> {
  40.    
  41.     private static final Logger logger = LoggerFactory.getLogger(SheetBeanValidator.class);
  42.    
  43.     /**
  44.      * BeanValidationのアノテーションの属性で、メッセージ中の変数から除外するもの。
  45.      * <p>メッセージの再構築を行う際に必要
  46.      */
  47.     private static final Set<String> EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES;
  48.     static {
  49.         Set<String> set = new HashSet<String>(3);
  50.         set.add("message");
  51.         set.add("groups");
  52.         set.add("payload");
  53.        
  54.         EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES = Collections.unmodifiableSet(set);
  55.     }
  56.    
  57.     private final Validator targetValidator;
  58.    
  59.     public SheetBeanValidator(final Validator targetValidator) {
  60.         ArgUtils.notNull(targetValidator, "targetValidator");
  61.         this.targetValidator = targetValidator;
  62.     }
  63.    
  64.     public SheetBeanValidator() {
  65.         this.targetValidator = createDefaultValidator();
  66.     }
  67.    
  68.     /**
  69.      * Bean Validaion のデフォルトのインスタンスを作成する。
  70.      * @return Validatorのインスタンス。
  71.      */
  72.     protected Validator createDefaultValidator() {
  73.        
  74.         final ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure()
  75.                 .messageInterpolator(new MessageInterpolatorAdapter(new ResourceBundleMessageResolver(), new MessageInterpolator()))
  76.                 .buildValidatorFactory();
  77.         final Validator validator = validatorFactory.usingContext()
  78.                 .getValidator();
  79.        
  80.         return validator;
  81.     }
  82.    
  83.     /**
  84.      * Bean ValidationのValidatorを取得する。
  85.      * @return Validatorのインスタンス。
  86.      */
  87.     public Validator getTargetValidator() {
  88.         return targetValidator;
  89.     }
  90.    
  91.     /**
  92.      * グループを指定して検証を実行する。
  93.      * @param targetObj 検証対象のオブジェクト。
  94.      * @param errors エラーオブジェクト
  95.      * @param groups BeanValiationのグループのクラス
  96.      */
  97.     @Override
  98.     public void validate(final Object targetObj, final SheetBindingErrors<?> errors, final Class<?>... groups) {
  99.        
  100.         ArgUtils.notNull(targetObj, "targetObj");
  101.         ArgUtils.notNull(errors, "errors");
  102.        
  103.         processConstraintViolation(getTargetValidator().validate(targetObj, groups), errors);
  104.        
  105.     }
  106.    
  107.     /**
  108.      * BeanValidationの検証結果をSheet用のエラーに変換する
  109.      * @param violations BeanValidationの検証結果
  110.      * @param errors シートのエラー
  111.      */
  112.     protected void processConstraintViolation(final Set<ConstraintViolation<Object>> violations,
  113.             final SheetBindingErrors<?> errors) {
  114.        
  115.         for(ConstraintViolation<Object> violation : violations) {
  116.            
  117.             final String fieldName = violation.getPropertyPath().toString();
  118.             final Optional<FieldError> fieldError = errors.getFirstFieldError(fieldName);
  119.            
  120.             if(fieldError.isPresent() && fieldError.get().isConversionFailure()) {
  121.                 // 型変換エラーが既存のエラーにある場合は、処理をスキップする。
  122.                 continue;
  123.             }
  124.            
  125.             final ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
  126.            
  127.             final String[] errorCodes = determineErrorCode(cd);
  128.            
  129.             final Map<String, Object> errorVars = createVariableForConstraint(cd);
  130.            
  131.             final String nestedPath = errors.buildFieldPath(fieldName);
  132.             if(Utils.isEmpty(nestedPath)) {
  133.                 // オブジェクトエラーの場合
  134.                 errors.createGlobalError(errorCodes)
  135.                     .variables(errorVars)
  136.                     .defaultMessage(violation.getMessageTemplate())
  137.                     .buildAndAddError();
  138.                
  139.             } else {
  140.                 // フィールドエラーの場合
  141.                
  142.                 // 親のオブジェクトから、セルの座標を取得する
  143.                 final Object parentObj = violation.getLeafBean();
  144.                 final Path path = violation.getPropertyPath();
  145.                 Optional<CellPosition> cellAddress = Optional.empty();
  146.                 Optional<String> label = Optional.empty();
  147.                 if(Path.class.isAssignableFrom(PathImpl.class)) {
  148.                     final String pathNodeName = getPathNodeName(path);
  149.                     cellAddress = new PositionGetterFactory().create(parentObj.getClass(), pathNodeName)
  150.                             .map(getter -> getter.get(parentObj)).orElse(Optional.empty());
  151.                    
  152.                     label = new LabelGetterFactory().create(parentObj.getClass(), pathNodeName)
  153.                             .map(getter -> getter.get(parentObj)).orElse(Optional.empty());
  154.                    
  155.                 }
  156.                
  157.                 // フィールドフォーマッタ
  158.                 Class<?> fieldType = errors.getFieldType(nestedPath);
  159.                 if(fieldType != null) {
  160.                     FieldFormatter<?> fieldFormatter = errors.findFieldFormatter(nestedPath, fieldType);
  161.                     if(fieldFormatter != null) {
  162.                         errorVars.putIfAbsent("fieldFormatter", fieldFormatter);
  163.                     }
  164.                 }
  165.                
  166.                 // 実際の値を取得する
  167.                 errorVars.putIfAbsent("validatedValue", violation.getInvalidValue());
  168.                
  169.                 errors.createFieldError(fieldName, errorCodes)
  170.                     .variables(errorVars)
  171.                     .address(cellAddress)
  172.                     .label(label)
  173.                     .defaultMessage(violation.getMessageTemplate())
  174.                     .buildAndAddError();
  175.                
  176.             }
  177.            
  178.         }
  179.        
  180.     }
  181.    
  182.     /**
  183.      * BeanValidationのPathの名称を取得する。
  184.      * <p>Hibernateのバージョンにより、パッケージが異なるのでリフレクションで取得する。
  185.      *
  186.      * @param path パス
  187.      * @return 名称
  188.      */
  189.     private String getPathNodeName(final Path path) {
  190.        
  191.         try {
  192.             Method getLeafNodeMethod = PathImpl.class.getMethod("getLeafNode");
  193.             Object leafNodeObj = getLeafNodeMethod.invoke(path);
  194.             if(leafNodeObj == null) {
  195.                 return null;
  196.             }
  197.            
  198.             Method getNodeNameMethod = NodeImpl.class.getMethod("getName");
  199.             Object nodeName = getNodeNameMethod.invoke(leafNodeObj);
  200.             return nodeName != null ? nodeName.toString() : null;
  201.            
  202.         } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
  203.             throw new RuntimeException("fail PathImple.getLeafNode().getName()", e);
  204.         }
  205.        
  206.     }
  207.    
  208.     /**
  209.      * エラーコードを決定する。
  210.      * <p>※ユーザ指定メッセージの場合はエラーコードは空。</p>
  211.      *
  212.      * @since 2.3
  213.      * @param descriptor フィールド情報
  214.      * @return エラーコード
  215.      */
  216.     protected String[] determineErrorCode(final ConstraintDescriptor<?> descriptor) {
  217.        
  218.         // バリデーション用アノテーションから属性「message」のでデフォルト値を取得し、変更されているかどう比較する。
  219.         String defaultMessage = null;
  220.         try {
  221.             Method messageMethod = descriptor.getAnnotation().annotationType().getMethod("message");
  222.             messageMethod.setAccessible(true);
  223.             defaultMessage = Objects.toString(messageMethod.getDefaultValue(), null);
  224.         } catch (NoSuchMethodException | SecurityException e) {
  225.             logger.warn("Fail getting annotation's attribute 'message' for " + descriptor.getAnnotation().annotationType().getSimpleName() , e);
  226.         }
  227.        
  228.         if(!descriptor.getMessageTemplate().equals(defaultMessage)) {
  229.             /*
  230.              * アノテーション属性「message」の値がデフォルト値から変更されている場合は、
  231.              * ユーザー指定メッセージとして判断し、エラーコードは空にしてユーザー指定メッセージを優先させる。
  232.              */
  233.             return new String[]{};
  234.            
  235.         } else {
  236.             // アノテーションのクラス名をもとに生成する。
  237.             return new String[]{
  238.                     descriptor.getAnnotation().annotationType().getSimpleName(),
  239.                     descriptor.getAnnotation().annotationType().getCanonicalName(),
  240.                     descriptor.getAnnotation().annotationType().getCanonicalName() + ".message"
  241.             };
  242.         }
  243.     }
  244.    
  245.     /**
  246.      * BeanValidationのアノテーションの値を元に、メッセージ変数を作成する。
  247.      * @param descriptor
  248.      * @return メッセージ変数
  249.      */
  250.     protected Map<String, Object> createVariableForConstraint(final ConstraintDescriptor<?> descriptor) {
  251.        
  252.         final Map<String, Object> vars = new HashMap<String, Object>();
  253.        
  254.         for(Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
  255.             final String attrName = entry.getKey();
  256.             final Object attrValue = entry.getValue();
  257.            
  258.             // メッセージ変数で必要ないものを除外する
  259.             if(EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES.contains(attrName)) {
  260.                 continue;
  261.             }
  262.            
  263.             vars.put(attrName, attrValue);
  264.         }
  265.        
  266.         return vars;
  267.        
  268.     }
  269.    
  270. }