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
30
31
32
33
34
35
36 public class CsvBeanValidator implements CsvValidator<Object> {
37
38 private static final Logger logger = LoggerFactory.getLogger(CsvBeanValidator.class);
39
40
41
42
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
67
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
82
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
98
99
100
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
113
114
115
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
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
167
168
169
170
171
172 protected String[] determineErrorCode(final ConstraintDescriptor<?> descriptor) {
173
174
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
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
203
204
205
206
207 private boolean isCsvField(final String field, final ValidationContext<Object> validationContext) {
208 return validationContext.getBeanMapping().getColumnMapping(field).isPresent();
209 }
210
211
212
213
214
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 }