001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.validation.beanvalidation;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.HashSet;
022import java.util.LinkedHashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.TreeMap;
027import javax.validation.ConstraintViolation;
028import javax.validation.ValidationException;
029import javax.validation.Validator;
030import javax.validation.metadata.BeanDescriptor;
031import javax.validation.metadata.ConstraintDescriptor;
032
033import org.springframework.beans.NotReadablePropertyException;
034import org.springframework.context.MessageSourceResolvable;
035import org.springframework.context.support.DefaultMessageSourceResolvable;
036import org.springframework.util.Assert;
037import org.springframework.util.ClassUtils;
038import org.springframework.validation.BindingResult;
039import org.springframework.validation.Errors;
040import org.springframework.validation.FieldError;
041import org.springframework.validation.ObjectError;
042import org.springframework.validation.SmartValidator;
043
044/**
045 * Adapter that takes a JSR-303 {@code javax.validator.Validator} and
046 * exposes it as a Spring {@link org.springframework.validation.Validator}
047 * while also exposing the original JSR-303 Validator interface itself.
048 *
049 * <p>Can be used as a programmatic wrapper. Also serves as base class for
050 * {@link CustomValidatorBean} and {@link LocalValidatorFactoryBean}.
051 *
052 * <p>Note that Bean Validation 1.1's {@code #forExecutables} method isn't supported
053 * on this adapter: We do not expect that method to be called by application code;
054 * consider {@link MethodValidationInterceptor} instead. If you really need programmatic
055 * {@code #forExecutables} access, call {@code #unwrap(Validator.class)} which will
056 * provide the native {@link Validator} object with {@code #forExecutables} support.
057 *
058 * @author Juergen Hoeller
059 * @since 3.0
060 */
061public class SpringValidatorAdapter implements SmartValidator, javax.validation.Validator {
062
063        private static final Set<String> internalAnnotationAttributes = new HashSet<String>(3);
064
065        static {
066                internalAnnotationAttributes.add("message");
067                internalAnnotationAttributes.add("groups");
068                internalAnnotationAttributes.add("payload");
069        }
070
071        private javax.validation.Validator targetValidator;
072
073
074        /**
075         * Create a new SpringValidatorAdapter for the given JSR-303 Validator.
076         * @param targetValidator the JSR-303 Validator to wrap
077         */
078        public SpringValidatorAdapter(javax.validation.Validator targetValidator) {
079                Assert.notNull(targetValidator, "Target Validator must not be null");
080                this.targetValidator = targetValidator;
081        }
082
083        SpringValidatorAdapter() {
084        }
085
086        void setTargetValidator(javax.validation.Validator targetValidator) {
087                this.targetValidator = targetValidator;
088        }
089
090
091        //---------------------------------------------------------------------
092        // Implementation of Spring Validator interface
093        //---------------------------------------------------------------------
094
095        @Override
096        public boolean supports(Class<?> clazz) {
097                return (this.targetValidator != null);
098        }
099
100        @Override
101        public void validate(Object target, Errors errors) {
102                if (this.targetValidator != null) {
103                        processConstraintViolations(this.targetValidator.validate(target), errors);
104                }
105        }
106
107        @Override
108        public void validate(Object target, Errors errors, Object... validationHints) {
109                if (this.targetValidator != null) {
110                        Set<Class<?>> groups = new LinkedHashSet<Class<?>>();
111                        if (validationHints != null) {
112                                for (Object hint : validationHints) {
113                                        if (hint instanceof Class) {
114                                                groups.add((Class<?>) hint);
115                                        }
116                                }
117                        }
118                        processConstraintViolations(
119                                        this.targetValidator.validate(target, ClassUtils.toClassArray(groups)), errors);
120                }
121        }
122
123        /**
124         * Process the given JSR-303 ConstraintViolations, adding corresponding errors to
125         * the provided Spring {@link Errors} object.
126         * @param violations the JSR-303 ConstraintViolation results
127         * @param errors the Spring errors object to register to
128         */
129        protected void processConstraintViolations(Set<ConstraintViolation<Object>> violations, Errors errors) {
130                for (ConstraintViolation<Object> violation : violations) {
131                        String field = determineField(violation);
132                        FieldError fieldError = errors.getFieldError(field);
133                        if (fieldError == null || !fieldError.isBindingFailure()) {
134                                try {
135                                        ConstraintDescriptor<?> cd = violation.getConstraintDescriptor();
136                                        String errorCode = determineErrorCode(cd);
137                                        Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, cd);
138                                        if (errors instanceof BindingResult) {
139                                                // Can do custom FieldError registration with invalid value from ConstraintViolation,
140                                                // as necessary for Hibernate Validator compatibility (non-indexed set path in field)
141                                                BindingResult bindingResult = (BindingResult) errors;
142                                                String nestedField = bindingResult.getNestedPath() + field;
143                                                if ("".equals(nestedField)) {
144                                                        String[] errorCodes = bindingResult.resolveMessageCodes(errorCode);
145                                                        bindingResult.addError(new ObjectError(
146                                                                        errors.getObjectName(), errorCodes, errorArgs, violation.getMessage()));
147                                                }
148                                                else {
149                                                        Object rejectedValue = getRejectedValue(field, violation, bindingResult);
150                                                        String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field);
151                                                        bindingResult.addError(new FieldError(
152                                                                        errors.getObjectName(), nestedField, rejectedValue, false,
153                                                                        errorCodes, errorArgs, violation.getMessage()));
154                                                }
155                                        }
156                                        else {
157                                                // got no BindingResult - can only do standard rejectValue call
158                                                // with automatic extraction of the current field value
159                                                errors.rejectValue(field, errorCode, errorArgs, violation.getMessage());
160                                        }
161                                }
162                                catch (NotReadablePropertyException ex) {
163                                        throw new IllegalStateException("JSR-303 validated property '" + field +
164                                                        "' does not have a corresponding accessor for Spring data binding - " +
165                                                        "check your DataBinder's configuration (bean property versus direct field access)", ex);
166                                }
167                        }
168                }
169        }
170
171        /**
172         * Determine a field for the given constraint violation.
173         * <p>The default implementation returns the stringified property path.
174         * @param violation the current JSR-303 ConstraintViolation
175         * @return the Spring-reported field (for use with {@link Errors})
176         * @since 4.2
177         * @see javax.validation.ConstraintViolation#getPropertyPath()
178         * @see org.springframework.validation.FieldError#getField()
179         */
180        protected String determineField(ConstraintViolation<Object> violation) {
181                String path = violation.getPropertyPath().toString();
182                int elementIndex = path.indexOf(".<");
183                return (elementIndex >= 0 ? path.substring(0, elementIndex) : path);
184        }
185
186        /**
187         * Determine a Spring-reported error code for the given constraint descriptor.
188         * <p>The default implementation returns the simple class name of the descriptor's
189         * annotation type. Note that the configured
190         * {@link org.springframework.validation.MessageCodesResolver} will automatically
191         * generate error code variations which include the object name and the field name.
192         * @param descriptor the JSR-303 ConstraintDescriptor for the current violation
193         * @return a corresponding error code (for use with {@link Errors})
194         * @since 4.2
195         * @see javax.validation.metadata.ConstraintDescriptor#getAnnotation()
196         * @see org.springframework.validation.MessageCodesResolver
197         */
198        protected String determineErrorCode(ConstraintDescriptor<?> descriptor) {
199                return descriptor.getAnnotation().annotationType().getSimpleName();
200        }
201
202        /**
203         * Return FieldError arguments for a validation error on the given field.
204         * Invoked for each violated constraint.
205         * <p>The default implementation returns a first argument indicating the field name
206         * (see {@link #getResolvableField}). Afterwards, it adds all actual constraint
207         * annotation attributes (i.e. excluding "message", "groups" and "payload") in
208         * alphabetical order of their attribute names.
209         * <p>Can be overridden to e.g. add further attributes from the constraint descriptor.
210         * @param objectName the name of the target object
211         * @param field the field that caused the binding error
212         * @param descriptor the JSR-303 constraint descriptor
213         * @return the Object array that represents the FieldError arguments
214         * @see org.springframework.validation.FieldError#getArguments
215         * @see org.springframework.context.support.DefaultMessageSourceResolvable
216         * @see org.springframework.validation.DefaultBindingErrorProcessor#getArgumentsForBindError
217         */
218        protected Object[] getArgumentsForConstraint(String objectName, String field, ConstraintDescriptor<?> descriptor) {
219                List<Object> arguments = new ArrayList<Object>();
220                arguments.add(getResolvableField(objectName, field));
221                // Using a TreeMap for alphabetical ordering of attribute names
222                Map<String, Object> attributesToExpose = new TreeMap<String, Object>();
223                for (Map.Entry<String, Object> entry : descriptor.getAttributes().entrySet()) {
224                        String attributeName = entry.getKey();
225                        Object attributeValue = entry.getValue();
226                        if (!internalAnnotationAttributes.contains(attributeName)) {
227                                if (attributeValue instanceof String) {
228                                        attributeValue = new ResolvableAttribute(attributeValue.toString());
229                                }
230                                attributesToExpose.put(attributeName, attributeValue);
231                        }
232                }
233                arguments.addAll(attributesToExpose.values());
234                return arguments.toArray();
235        }
236
237        /**
238         * Build a resolvable wrapper for the specified field, allowing to resolve the field's
239         * name in a {@code MessageSource}.
240         * <p>The default implementation returns a first argument indicating the field:
241         * of type {@code DefaultMessageSourceResolvable}, with "objectName.field" and "field"
242         * as codes, and with the plain field name as default message.
243         * @param objectName the name of the target object
244         * @param field the field that caused the binding error
245         * @return a corresponding {@code MessageSourceResolvable} for the specified field
246         * @since 4.3
247         */
248        protected MessageSourceResolvable getResolvableField(String objectName, String field) {
249                String[] codes = new String[] {objectName + Errors.NESTED_PATH_SEPARATOR + field, field};
250                return new DefaultMessageSourceResolvable(codes, field);
251        }
252
253        /**
254         * Extract the rejected value behind the given constraint violation,
255         * for exposure through the Spring errors representation.
256         * @param field the field that caused the binding error
257         * @param violation the corresponding JSR-303 ConstraintViolation
258         * @param bindingResult a Spring BindingResult for the backing object
259         * which contains the current field's value
260         * @return the invalid value to expose as part of the field error
261         * @since 4.2
262         * @see javax.validation.ConstraintViolation#getInvalidValue()
263         * @see org.springframework.validation.FieldError#getRejectedValue()
264         */
265        protected Object getRejectedValue(String field, ConstraintViolation<Object> violation, BindingResult bindingResult) {
266                Object invalidValue = violation.getInvalidValue();
267                if (!"".equals(field) && !field.contains("[]") &&
268                                (invalidValue == violation.getLeafBean() || field.contains("[") || field.contains("."))) {
269                        // Possibly a bean constraint with property path: retrieve the actual property value.
270                        // However, explicitly avoid this for "address[]" style paths that we can't handle.
271                        invalidValue = bindingResult.getRawFieldValue(field);
272                }
273                return invalidValue;
274        }
275
276
277        //---------------------------------------------------------------------
278        // Implementation of JSR-303 Validator interface
279        //---------------------------------------------------------------------
280
281        @Override
282        public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
283                Assert.state(this.targetValidator != null, "No target Validator set");
284                return this.targetValidator.validate(object, groups);
285        }
286
287        @Override
288        public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
289                Assert.state(this.targetValidator != null, "No target Validator set");
290                return this.targetValidator.validateProperty(object, propertyName, groups);
291        }
292
293        @Override
294        public <T> Set<ConstraintViolation<T>> validateValue(
295                        Class<T> beanType, String propertyName, Object value, Class<?>... groups) {
296
297                Assert.state(this.targetValidator != null, "No target Validator set");
298                return this.targetValidator.validateValue(beanType, propertyName, value, groups);
299        }
300
301        @Override
302        public BeanDescriptor getConstraintsForClass(Class<?> clazz) {
303                Assert.state(this.targetValidator != null, "No target Validator set");
304                return this.targetValidator.getConstraintsForClass(clazz);
305        }
306
307        @Override
308        @SuppressWarnings("unchecked")
309        public <T> T unwrap(Class<T> type) {
310                Assert.state(this.targetValidator != null, "No target Validator set");
311                try {
312                        return (type != null ? this.targetValidator.unwrap(type) : (T) this.targetValidator);
313                }
314                catch (ValidationException ex) {
315                        // ignore if just being asked for plain Validator
316                        if (javax.validation.Validator.class == type) {
317                                return (T) this.targetValidator;
318                        }
319                        throw ex;
320                }
321        }
322
323
324        /**
325         * Wrapper for a String attribute which can be resolved via a {@code MessageSource},
326         * falling back to the original attribute as a default value otherwise.
327         */
328        @SuppressWarnings("serial")
329        private static class ResolvableAttribute implements MessageSourceResolvable, Serializable {
330
331                private final String resolvableString;
332
333                public ResolvableAttribute(String resolvableString) {
334                        this.resolvableString = resolvableString;
335                }
336
337                @Override
338                public String[] getCodes() {
339                        return new String[] {this.resolvableString};
340                }
341
342                @Override
343                public Object[] getArguments() {
344                        return null;
345                }
346
347                @Override
348                public String getDefaultMessage() {
349                        return this.resolvableString;
350                }
351        }
352
353}