View Javadoc
1   package com.github.mygreen.supercsv.validation.beanvalidation;
2   
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.Set;
10  
11  import javax.validation.ConstraintViolation;
12  import javax.validation.Validation;
13  import javax.validation.Validator;
14  import javax.validation.ValidatorFactory;
15  import javax.validation.metadata.ConstraintDescriptor;
16  
17  import org.slf4j.Logger;
18  import org.slf4j.LoggerFactory;
19  
20  import com.github.mygreen.supercsv.builder.ColumnMapping;
21  import com.github.mygreen.supercsv.localization.MessageInterpolator;
22  import com.github.mygreen.supercsv.localization.ResourceBundleMessageResolver;
23  import com.github.mygreen.supercsv.validation.CsvBindingErrors;
24  import com.github.mygreen.supercsv.validation.CsvFieldError;
25  import com.github.mygreen.supercsv.validation.CsvValidator;
26  import com.github.mygreen.supercsv.validation.ValidationContext;
27  
28  /**
29   * BeanValidaion JSR-303(ver.1.0)/JSR-349(ver.1.1)/JSR-380(ver2.0)にブリッジする{@link CsvValidator}。
30   * 
31   * @version 2.4
32   * @since 2.0
33   * @author T.TSUCHIE
34   *
35   */
36  public class CsvBeanValidator implements CsvValidator<Object> {
37      
38      private static final Logger logger = LoggerFactory.getLogger(CsvBeanValidator.class);
39      
40      /**
41       * BeanValidationのアノテーションの属性で、メッセージ中の変数から除外するもの。
42       * <p>メッセージの再構築を行う際に必要
43       */
44      private static final Set<String> EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES;
45      static {
46          Set<String> set = new HashSet<String>(3);
47          set.add("message");
48          set.add("groups");
49          set.add("payload");
50          
51          EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES = Collections.unmodifiableSet(set);
52      }
53      
54      private final Validator targetValidator;
55      
56      public CsvBeanValidator(final Validator targetValidator) {
57          Objects.requireNonNull(targetValidator);
58          this.targetValidator = targetValidator;
59      }
60      
61      public CsvBeanValidator() {
62          this.targetValidator = createDefaultValidator();
63      }
64      
65      /**
66       * Bean Validatonのデフォルトのインスタンスを取得する。
67       * @return Validatorを取得します。
68       */
69      private Validator createDefaultValidator() {
70          final ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure()
71                  .messageInterpolator(new MessageInterpolatorAdapter(new ResourceBundleMessageResolver(), new MessageInterpolator()))
72                  .buildValidatorFactory();
73          
74          final Validator validator = validatorFactory.usingContext()
75                  .getValidator();
76          
77          return validator;
78      }
79      
80      /**
81       * BeanValidationのValidatorを取得する。
82       * @return Validatorを取得します。
83       */
84      public Validator getTargetValidator() {
85          return targetValidator;
86      }
87      
88      @Override
89      public void validate(final Object record, final CsvBindingErrors bindingErrors, 
90              final ValidationContext<Object> validationContext) {
91          validate(record, bindingErrors, validationContext, validationContext.getBeanMapping().getGroups());
92          
93      }
94      
95      /**
96       * グループを指定して検証を実行する。
97       * @param record 検証対象のオブジェクト。
98       * @param bindingErrors エラーオブジェクト
99       * @param validationContext 入力値検証のためのコンテキスト情報
100      * @param groups BeanValiationのグループのクラス
101      */
102     public void validate(final Object record, final CsvBindingErrors bindingErrors, 
103             final ValidationContext<Object> validationContext, final Class<?>... groups) {
104         Objects.requireNonNull(record);
105         Objects.requireNonNull(bindingErrors);
106         Objects.requireNonNull(validationContext);
107         
108         processConstraintViolation(getTargetValidator().validate(record, groups), bindingErrors, validationContext);
109     }
110     
111     /**
112      * BeanValidationの検証結果をSheet用のエラーに変換する
113      * @param violations BeanValidationの検証結果
114      * @param bindingErrors エラー情報
115      * @param validationContext 入力値検証のためのコンテキスト情報
116      */
117     private void processConstraintViolation(final Set<ConstraintViolation<Object>> violations,
118             final CsvBindingErrors bindingErrors, final ValidationContext<Object> validationContext) {
119         
120         for(ConstraintViolation<Object> violation : violations) {
121             
122             final String field = violation.getPropertyPath().toString();
123             final ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
124             
125             final String[] errorCodes = determineErrorCode(cd);
126             
127             final Map<String, Object> errorVars = createVariableForConstraint(cd);
128             
129             if(isCsvField(field, validationContext)) {
130                 // フィールドエラーの場合
131                 
132                 final CsvFieldError fieldError = bindingErrors.getFirstFieldError(field);
133                 if(fieldError != null && fieldError.isProcessingFailure()) {
134                     // CellProcessorで発生したエラーが既ににある場合は、処理をスキップする。
135                     continue;
136                 }
137                 
138                 final ColumnMapping columnMapping = validationContext.getBeanMapping().getColumnMapping(field).get();
139                 
140                 errorVars.put("lineNumber", validationContext.getCsvContext().getLineNumber());
141                 errorVars.put("rowNumber", validationContext.getCsvContext().getRowNumber());
142                 errorVars.put("columnNumber", columnMapping.getNumber());
143                 errorVars.put("label", columnMapping.getLabel());
144                 errorVars.computeIfAbsent("printer", key -> columnMapping.getFormatter());
145                 
146                 // 実際の値を取得する
147                 final Object fieldValue = violation.getInvalidValue();
148                 errorVars.computeIfAbsent("validatedValue", key -> fieldValue);
149                 
150                 bindingErrors.rejectValue(field, columnMapping.getField().getType(), 
151                         errorCodes, errorVars, violation.getMessageTemplate());
152                 
153             } else {
154                 // オブジェクトエラーの場合
155                 bindingErrors.reject(errorCodes, errorVars, violation.getMessageTemplate());
156                 
157             }
158             
159         }
160         
161         
162     }
163     
164     /**
165      * エラーコードを決定する。
166      * <p>※ユーザ指定メッセージの場合はエラーコードは空。</p>
167      * 
168      * @since 2.4
169      * @param descriptor フィールド情報
170      * @return エラーコード
171      */
172     protected String[] determineErrorCode(final ConstraintDescriptor<?> descriptor) {
173         
174         // バリデーション用アノテーションから属性「message」のでデフォルト値を取得し、変更されているかどう比較する。
175         String defaultMessage = null;
176         try {
177             Method messageMethod = descriptor.getAnnotation().annotationType().getMethod("message");
178             messageMethod.setAccessible(true);
179             defaultMessage = Objects.toString(messageMethod.getDefaultValue(), null);
180         } catch (NoSuchMethodException | SecurityException e) {
181             logger.warn("Fail getting annotation's attribute 'message' for " + descriptor.getAnnotation().annotationType().getSimpleName() , e);
182         }
183         
184         if(!descriptor.getMessageTemplate().equals(defaultMessage)) {
185             /*
186              * アノテーション属性「message」の値がデフォルト値から変更されている場合は、
187              * ユーザー指定メッセージとして判断し、エラーコードは空にしてユーザー指定メッセージを優先させる。
188              */
189             return new String[]{};
190             
191         } else {
192             // アノテーションのクラス名をもとに生成する。
193             return new String[]{
194                     descriptor.getAnnotation().annotationType().getSimpleName(),
195                     descriptor.getAnnotation().annotationType().getCanonicalName(),
196                     descriptor.getAnnotation().annotationType().getCanonicalName() + ".message"
197             };
198         }
199     }
200     
201     /**
202      * CSVのカラムのフィールドか判定する。
203      * @param field フィールド名
204      * @param validationContext CSVの検証情報
205      * @return trueのとき、CSVのカラムフィールド。
206      */
207     private boolean isCsvField(final String field, final ValidationContext<Object> validationContext) {
208         return validationContext.getBeanMapping().getColumnMapping(field).isPresent();
209     }
210     
211     /**
212      * BeanValidationのアノテーションの値を元に、メッセージ変数を作成する。
213      * @param descriptor
214      * @return メッセージ変数
215      */
216     private Map<String, Object> createVariableForConstraint(final ConstraintDescriptor<?> descriptor) {
217         
218         final Map<String, Object> vars = new HashMap<>();
219         
220         for(Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
221             final String attrName = entry.getKey();
222             final Object attrValue = entry.getValue();
223             
224             // メッセージ変数で必要ないものを除外する
225             if(EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES.contains(attrName)) {
226                 continue;
227             }
228             
229             vars.put(attrName, attrValue);
230         }
231         
232         return vars;
233         
234     }
235     
236 }