/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation.beanvalidation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.ConstraintViolation;
import javax.validation.Payload;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorFactory;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Tests against Hibernate Validator 5.x.
*
* @author Juergen Hoeller
*/
public class ValidatorFactoryTests {
@Test
public void testSimpleValidation() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
assertEquals(2, result.size());
for (ConstraintViolation<ValidPerson> cv : result) {
String path = cv.getPropertyPath().toString();
if ("name".equals(path) || "address.street".equals(path)) {
assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NotNull);
}
else {
fail("Invalid constraint violation with path '" + path + "'");
}
}
Validator nativeValidator = validator.unwrap(Validator.class);
assertTrue(nativeValidator.getClass().getName().startsWith("org.hibernate"));
assertTrue(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory);
assertTrue(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory);
validator.destroy();
}
@Test
public void testSimpleValidationWithCustomProvider() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setProviderClass(HibernateValidator.class);
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
assertEquals(2, result.size());
for (ConstraintViolation<ValidPerson> cv : result) {
String path = cv.getPropertyPath().toString();
if ("name".equals(path) || "address.street".equals(path)) {
assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NotNull);
}
else {
fail("Invalid constraint violation with path '" + path + "'");
}
}
Validator nativeValidator = validator.unwrap(Validator.class);
assertTrue(nativeValidator.getClass().getName().startsWith("org.hibernate"));
assertTrue(validator.unwrap(ValidatorFactory.class) instanceof HibernateValidatorFactory);
assertTrue(validator.unwrap(HibernateValidatorFactory.class) instanceof HibernateValidatorFactory);
validator.destroy();
}
@Test
public void testSimpleValidationWithClassLevel() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.setName("Juergen");
person.getAddress().setStreet("Juergen's Street");
Set<ConstraintViolation<ValidPerson>> result = validator.validate(person);
assertEquals(1, result.size());
Iterator<ConstraintViolation<ValidPerson>> iterator = result.iterator();
ConstraintViolation<?> cv = iterator.next();
assertEquals("", cv.getPropertyPath().toString());
assertTrue(cv.getConstraintDescriptor().getAnnotation() instanceof NameAddressValid);
}
@Test
public void testSpringValidationFieldType() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.setName("Phil");
person.getAddress().setStreet("Phil's Street");
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
assertEquals(1, errors.getErrorCount());
assertThat("Field/Value type mismatch", errors.getFieldError("address").getRejectedValue(),
instanceOf(ValidAddress.class));
}
@Test
public void testSpringValidation() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validator.validate(person, result);
assertEquals(2, result.getErrorCount());
FieldError fieldError = result.getFieldError("name");
assertEquals("name", fieldError.getField());
List<String> errorCodes = Arrays.asList(fieldError.getCodes());
assertEquals(4, errorCodes.size());
assertTrue(errorCodes.contains("NotNull.person.name"));
assertTrue(errorCodes.contains("NotNull.name"));
assertTrue(errorCodes.contains("NotNull.java.lang.String"));
assertTrue(errorCodes.contains("NotNull"));
fieldError = result.getFieldError("address.street");
assertEquals("address.street", fieldError.getField());
errorCodes = Arrays.asList(fieldError.getCodes());
assertEquals(5, errorCodes.size());
assertTrue(errorCodes.contains("NotNull.person.address.street"));
assertTrue(errorCodes.contains("NotNull.address.street"));
assertTrue(errorCodes.contains("NotNull.street"));
assertTrue(errorCodes.contains("NotNull.java.lang.String"));
assertTrue(errorCodes.contains("NotNull"));
}
@Test
public void testSpringValidationWithClassLevel() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.setName("Juergen");
person.getAddress().setStreet("Juergen's Street");
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validator.validate(person, result);
assertEquals(1, result.getErrorCount());
ObjectError globalError = result.getGlobalError();
List<String> errorCodes = Arrays.asList(globalError.getCodes());
assertEquals(2, errorCodes.size());
assertTrue(errorCodes.contains("NameAddressValid.person"));
assertTrue(errorCodes.contains("NameAddressValid"));
}
@Test
public void testSpringValidationWithAutowiredValidator() throws Exception {
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(
LocalValidatorFactoryBean.class);
LocalValidatorFactoryBean validator = ctx.getBean(LocalValidatorFactoryBean.class);
ValidPerson person = new ValidPerson();
person.expectsAutowiredValidator = true;
person.setName("Juergen");
person.getAddress().setStreet("Juergen's Street");
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validator.validate(person, result);
assertEquals(1, result.getErrorCount());
ObjectError globalError = result.getGlobalError();
List<String> errorCodes = Arrays.asList(globalError.getCodes());
assertEquals(2, errorCodes.size());
assertTrue(errorCodes.contains("NameAddressValid.person"));
assertTrue(errorCodes.contains("NameAddressValid"));
ctx.close();
}
@Test
public void testSpringValidationWithErrorInListElement() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.getAddressList().add(new ValidAddress());
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validator.validate(person, result);
assertEquals(3, result.getErrorCount());
FieldError fieldError = result.getFieldError("name");
assertEquals("name", fieldError.getField());
fieldError = result.getFieldError("address.street");
assertEquals("address.street", fieldError.getField());
fieldError = result.getFieldError("addressList[0].street");
assertEquals("addressList[0].street", fieldError.getField());
}
@Test
public void testSpringValidationWithErrorInSetElement() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ValidPerson person = new ValidPerson();
person.getAddressSet().add(new ValidAddress());
BeanPropertyBindingResult result = new BeanPropertyBindingResult(person, "person");
validator.validate(person, result);
assertEquals(3, result.getErrorCount());
FieldError fieldError = result.getFieldError("name");
assertEquals("name", fieldError.getField());
fieldError = result.getFieldError("address.street");
assertEquals("address.street", fieldError.getField());
fieldError = result.getFieldError("addressSet[].street");
assertEquals("addressSet[].street", fieldError.getField());
}
@Test
public void testInnerBeanValidation() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
MainBean mainBean = new MainBean();
Errors errors = new BeanPropertyBindingResult(mainBean, "mainBean");
validator.validate(mainBean, errors);
Object rejected = errors.getFieldValue("inner.value");
assertNull(rejected);
}
@Test
public void testValidationWithOptionalField() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
MainBeanWithOptional mainBean = new MainBeanWithOptional();
Errors errors = new BeanPropertyBindingResult(mainBean, "mainBean");
validator.validate(mainBean, errors);
Object rejected = errors.getFieldValue("inner.value");
assertNull(rejected);
}
@Test
public void testListValidation() throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
ListContainer listContainer = new ListContainer();
listContainer.addString("A");
listContainer.addString("X");
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(listContainer, "listContainer");
errors.initConversion(new DefaultConversionService());
validator.validate(listContainer, errors);
FieldError fieldError = errors.getFieldError("list[1]");
assertEquals("X", errors.getFieldValue("list[1]"));
}
@NameAddressValid
public static class ValidPerson {
@NotNull
private String name;
@Valid
private ValidAddress address = new ValidAddress();
@Valid
private List<ValidAddress> addressList = new LinkedList<>();
@Valid
private Set<ValidAddress> addressSet = new LinkedHashSet<>();
public boolean expectsAutowiredValidator = false;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ValidAddress getAddress() {
return address;
}
public void setAddress(ValidAddress address) {
this.address = address;
}
public List<ValidAddress> getAddressList() {
return addressList;
}
public void setAddressList(List<ValidAddress> addressList) {
this.addressList = addressList;
}
public Set<ValidAddress> getAddressSet() {
return addressSet;
}
public void setAddressSet(Set<ValidAddress> addressSet) {
this.addressSet = addressSet;
}
}
public static class ValidAddress {
@NotNull
private String street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NameAddressValidator.class)
public @interface NameAddressValid {
String message() default "Street must not contain name";
Class<?>[] groups() default {};
Class<?>[] payload() default {};
}
public static class NameAddressValidator implements ConstraintValidator<NameAddressValid, ValidPerson> {
@Autowired
private Environment environment;
@Override
public void initialize(NameAddressValid constraintAnnotation) {
}
@Override
public boolean isValid(ValidPerson value, ConstraintValidatorContext context) {
if (value.expectsAutowiredValidator) {
assertNotNull(this.environment);
}
boolean valid = (value.name == null || !value.address.street.contains(value.name));
if (!valid && "Phil".equals(value.name)) {
context.buildConstraintViolationWithTemplate(
context.getDefaultConstraintMessageTemplate()).addPropertyNode("address").addConstraintViolation().disableDefaultConstraintViolation();
}
return valid;
}
}
public static class MainBean {
@InnerValid
private InnerBean inner = new InnerBean();
public InnerBean getInner() {
return inner;
}
}
public static class MainBeanWithOptional {
@InnerValid
private InnerBean inner = new InnerBean();
public Optional<InnerBean> getInner() {
return Optional.ofNullable(inner);
}
}
public static class InnerBean {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy=InnerValidator.class)
public static @interface InnerValid {
String message() default "NOT VALID";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default {};
}
public static class InnerValidator implements ConstraintValidator<InnerValid, InnerBean> {
@Override
public void initialize(InnerValid constraintAnnotation) {
}
@Override
public boolean isValid(InnerBean bean, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
if (bean.getValue() == null) {
context.buildConstraintViolationWithTemplate("NULL").addPropertyNode("value").addConstraintViolation();
return false;
}
return true;
}
}
public static class ListContainer {
@NotXList
private List<String> list = new LinkedList<>();
public void addString(String value) {
list.add(value);
}
public List<String> getList() {
return list;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Constraint(validatedBy = NotXListValidator.class)
public @interface NotXList {
String message() default "Should not be X";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class NotXListValidator implements ConstraintValidator<NotXList, List<String>> {
@Override
public void initialize(NotXList constraintAnnotation) {
}
@Override
public boolean isValid(List<String> list, ConstraintValidatorContext context) {
context.disableDefaultConstraintViolation();
boolean valid = true;
for (int i = 0; i < list.size(); i++) {
if ("X".equals(list.get(i))) {
context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addBeanNode().inIterable().atIndex(i).addConstraintViolation();
valid = false;
}
}
return valid;
}
}
}