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 org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 import com.github.mygreen.supercsv.builder.ColumnMapping;
15 import com.github.mygreen.supercsv.localization.MessageInterpolator;
16 import com.github.mygreen.supercsv.localization.ResourceBundleMessageResolver;
17 import com.github.mygreen.supercsv.validation.CsvBindingErrors;
18 import com.github.mygreen.supercsv.validation.CsvFieldError;
19 import com.github.mygreen.supercsv.validation.CsvValidator;
20 import com.github.mygreen.supercsv.validation.ValidationContext;
21
22 import jakarta.validation.ConstraintViolation;
23 import jakarta.validation.Validation;
24 import jakarta.validation.Validator;
25 import jakarta.validation.ValidatorFactory;
26 import jakarta.validation.metadata.ConstraintDescriptor;
27
28
29
30
31
32
33
34
35 public class JakartaCsvBeanValidator implements CsvValidator<Object> {
36
37 private static final Logger logger = LoggerFactory.getLogger(JakartaCsvBeanValidator.class);
38
39
40
41
42
43 private static final Set<String> EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES;
44 static {
45 Set<String> set = new HashSet<String>(3);
46 set.add("message");
47 set.add("groups");
48 set.add("payload");
49
50 EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES = Collections.unmodifiableSet(set);
51 }
52
53 private final Validator targetValidator;
54
55 public JakartaCsvBeanValidator(final Validator targetValidator) {
56 Objects.requireNonNull(targetValidator);
57 this.targetValidator = targetValidator;
58 }
59
60 public JakartaCsvBeanValidator() {
61 this.targetValidator = createDefaultValidator();
62 }
63
64
65
66
67
68 private Validator createDefaultValidator() {
69 final ValidatorFactory validatorFactory = Validation.byDefaultProvider().configure()
70 .messageInterpolator(new JakartaMessageInterpolatorAdapter(new ResourceBundleMessageResolver(), new MessageInterpolator()))
71 .buildValidatorFactory();
72
73 final Validator validator = validatorFactory.usingContext()
74 .getValidator();
75
76 return validator;
77 }
78
79
80
81
82
83 public Validator getTargetValidator() {
84 return targetValidator;
85 }
86
87 @Override
88 public void validate(final Object record, final CsvBindingErrors bindingErrors,
89 final ValidationContext<Object> validationContext) {
90 validate(record, bindingErrors, validationContext, validationContext.getBeanMapping().getGroups());
91
92 }
93
94
95
96
97
98
99
100
101 public void validate(final Object record, final CsvBindingErrors bindingErrors,
102 final ValidationContext<Object> validationContext, final Class<?>... groups) {
103 Objects.requireNonNull(record);
104 Objects.requireNonNull(bindingErrors);
105 Objects.requireNonNull(validationContext);
106
107 processConstraintViolation(getTargetValidator().validate(record, groups), bindingErrors, validationContext);
108 }
109
110
111
112
113
114
115
116 private void processConstraintViolation(final Set<ConstraintViolation<Object>> violations,
117 final CsvBindingErrors bindingErrors, final ValidationContext<Object> validationContext) {
118
119 for(ConstraintViolation<Object> violation : violations) {
120
121 final String field = violation.getPropertyPath().toString();
122 final ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
123
124 final String[] errorCodes = determineErrorCode(cd);
125
126 final Map<String, Object> errorVars = createVariableForConstraint(cd);
127
128 if(isCsvField(field, validationContext)) {
129
130
131 final CsvFieldError fieldError = bindingErrors.getFirstFieldError(field);
132 if(fieldError != null && fieldError.isProcessingFailure()) {
133
134 continue;
135 }
136
137 final ColumnMapping columnMapping = validationContext.getBeanMapping().getColumnMapping(field).get();
138
139 errorVars.put("lineNumber", validationContext.getCsvContext().getLineNumber());
140 errorVars.put("rowNumber", validationContext.getCsvContext().getRowNumber());
141 errorVars.put("columnNumber", columnMapping.getNumber());
142 errorVars.put("label", columnMapping.getLabel());
143 errorVars.computeIfAbsent("printer", key -> columnMapping.getFormatter());
144
145
146 final Object fieldValue = violation.getInvalidValue();
147 errorVars.computeIfAbsent("validatedValue", key -> fieldValue);
148
149 bindingErrors.rejectValue(field, columnMapping.getField().getType(),
150 errorCodes, errorVars, violation.getMessageTemplate());
151
152 } else {
153
154 bindingErrors.reject(errorCodes, errorVars, violation.getMessageTemplate());
155
156 }
157
158 }
159
160
161 }
162
163
164
165
166
167
168
169
170
171 protected String[] determineErrorCode(final ConstraintDescriptor<?> descriptor) {
172
173
174 String defaultMessage = null;
175 try {
176 Method messageMethod = descriptor.getAnnotation().annotationType().getMethod("message");
177 messageMethod.setAccessible(true);
178 defaultMessage = Objects.toString(messageMethod.getDefaultValue(), null);
179 } catch (NoSuchMethodException | SecurityException e) {
180 logger.warn("Fail getting annotation's attribute 'message' for " + descriptor.getAnnotation().annotationType().getSimpleName() , e);
181 }
182
183 if(!descriptor.getMessageTemplate().equals(defaultMessage)) {
184
185
186
187
188 return new String[]{};
189
190 } else {
191
192 return new String[]{
193 descriptor.getAnnotation().annotationType().getSimpleName(),
194 descriptor.getAnnotation().annotationType().getCanonicalName(),
195 descriptor.getAnnotation().annotationType().getCanonicalName() + ".message"
196 };
197 }
198 }
199
200
201
202
203
204
205
206 private boolean isCsvField(final String field, final ValidationContext<Object> validationContext) {
207 return validationContext.getBeanMapping().getColumnMapping(field).isPresent();
208 }
209
210
211
212
213
214
215 private Map<String, Object> createVariableForConstraint(final ConstraintDescriptor<?> descriptor) {
216
217 final Map<String, Object> vars = new HashMap<>();
218
219 for(Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
220 final String attrName = entry.getKey();
221 final Object attrValue = entry.getValue();
222
223
224 if(EXCLUDE_MESSAGE_ANNOTATION_ATTRIBUTES.contains(attrName)) {
225 continue;
226 }
227
228 vars.put(attrName, attrValue);
229 }
230
231 return vars;
232
233 }
234
235 }