diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ValidationErrors.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ValidationErrors.java index ce4dc5148..2c2e268e4 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ValidationErrors.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/ValidationErrors.java @@ -1,88 +1,100 @@ +/* + * Copyright 2012-2016 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.data.rest.core; -import static org.springframework.util.ReflectionUtils.*; - -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; - +import org.springframework.beans.BeansException; +import org.springframework.beans.ConfigurablePropertyAccessor; +import org.springframework.beans.DirectFieldAccessor; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; -import org.springframework.validation.AbstractErrors; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.util.Assert; +import org.springframework.validation.AbstractPropertyBindingResult; import org.springframework.validation.Errors; -import org.springframework.validation.FieldError; -import org.springframework.validation.ObjectError; /** - * An {@link Errors} implementation for use in the events mechanism of Spring Data REST. + * An {@link Errors} implementation for use in the events mechanism of Spring Data REST. Customizes actual field lookup + * by using a {@link PersistentPropertyAccessor} for actual value lookups. * * @author Jon Brisbin + * @author Oliver Gierke */ -public class ValidationErrors extends AbstractErrors { +public class ValidationErrors extends AbstractPropertyBindingResult { private static final long serialVersionUID = 8141826537389141361L; - private String name; - private Object entity; - private PersistentEntity persistentEntity; - private List globalErrors = new ArrayList(); - private List fieldErrors = new ArrayList(); + private final PersistentPropertyAccessor accessor; + private PersistentEntity entity; + + /** + * Creates a new {@link ValidationErrors} instance for the given source object and {@link PersistentEntity}. + * + * @param source the source object to gather validation errors on, must not be {@literal null}. + * @param entity the {@link PersistentEntity} for the given source instance, must not be {@literal null}. + */ + public ValidationErrors(Object source, PersistentEntity entity) { + + super(source.getClass().getSimpleName()); + + Assert.notNull(source, "Entity must not be null!"); + Assert.notNull(entity, "PersistentEntity must not be null!"); + Assert.isTrue(entity.getType().isInstance(source), + "Given source object is not of type of the given PersistentEntity"); - public ValidationErrors(String name, Object entity, PersistentEntity persistentEntity) { - this.name = name; this.entity = entity; - this.persistentEntity = persistentEntity; + this.accessor = entity.getPropertyAccessor(source); } + /* + * (non-Javadoc) + * @see org.springframework.validation.AbstractPropertyBindingResult#getPropertyAccessor() + */ @Override - public String getObjectName() { - return name; - } - - @Override - public void reject(String errorCode, Object[] errorArgs, String defaultMessage) { - globalErrors.add(new ObjectError(name, new String[] { errorCode }, errorArgs, defaultMessage)); - } - - @Override - public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) { - fieldErrors.add(new FieldError(name, field, getFieldValue(field), true, new String[] { errorCode }, errorArgs, - defaultMessage)); - } - - @Override - public void addAllErrors(Errors errors) { - globalErrors.addAll(errors.getAllErrors()); - } - - @Override - public List getGlobalErrors() { - return globalErrors; - } - - @Override - public List getFieldErrors() { - return fieldErrors; + public ConfigurablePropertyAccessor getPropertyAccessor() { + + return new DirectFieldAccessor(getTarget()) { + + @Override + public Object getPropertyValue(String propertyName) throws BeansException { + PersistentProperty property = entity.getPersistentProperty(propertyName); + return property == null ? null : accessor.getProperty(property); + } + }; } + /* + * (non-Javadoc) + * @see org.springframework.validation.AbstractBindingResult#getFieldValue(java.lang.String) + */ @Override public Object getFieldValue(String field) { - PersistentProperty prop = persistentEntity != null ? persistentEntity.getPersistentProperty(field) : null; - if (null == prop) { - return null; + + if (field.contains(".")) { + return super.getFieldValue(field); } - Method getter = prop.getGetter(); - if (null != getter) { - return invokeMethod(getter, entity); - } - Field fld = prop.getField(); - if (null != fld) { - return getField(fld, entity); - } - - return null; + return accessor.getProperty(entity.getPersistentProperty(field)); } + /* + * (non-Javadoc) + * @see org.springframework.validation.AbstractBindingResult#getTarget() + */ + @Override + public Object getTarget() { + return accessor.getBean(); + } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/ValidatingRepositoryEventListener.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/ValidatingRepositoryEventListener.java index 85b9ef17f..626969355 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/ValidatingRepositoryEventListener.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/event/ValidatingRepositoryEventListener.java @@ -23,12 +23,14 @@ import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectFactory; +import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.rest.core.RepositoryConstraintViolationException; import org.springframework.data.rest.core.ValidationErrors; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.validation.DirectFieldBindingResult; import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; @@ -167,8 +169,10 @@ public class ValidatingRepositoryEventListener extends AbstractRepositoryEventLi Class domainType = entity.getClass(); PersistentEntities persistentEntities = persistentEntitiesFactory.getObject(); - Errors errors = new ValidationErrors(domainType.getSimpleName(), entity, - persistentEntities.getPersistentEntity(domainType)); + PersistentEntity persistentEntity = persistentEntities.getPersistentEntity(domainType); + + Errors errors = persistentEntity == null ? new DirectFieldBindingResult(entity, domainType.getSimpleName()) + : new ValidationErrors(entity, persistentEntity); for (Validator v : getValidatorsForEvent(event)) { diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/ValidationErrorsUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/ValidationErrorsUnitTests.java new file mode 100644 index 000000000..ac254777f --- /dev/null +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/ValidationErrorsUnitTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016 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.data.rest.core; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; + +/** + * Unit tests for {@link ValidationErrors}. + * + * @author Oliver Gierke + */ +public class ValidationErrorsUnitTests { + + KeyValueMappingContext context = new KeyValueMappingContext(); + + /** + * @see DATAREST-798 + */ + @Test + public void exposesNestedViolationsCorrectly() { + + ValidationErrors errors = new ValidationErrors(new Foo(), context.getPersistentEntity(Foo.class)); + + errors.pushNestedPath("bars[0]"); + errors.rejectValue("field", "asdf"); + errors.popNestedPath(); + + assertThat(errors.getFieldError().getField(), is("bars[0].field")); + } + + static class Foo { + List bars = new ArrayList(); + } + + static class Bar { + String field; + } +} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/ValidatorIntegrationTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/ValidatorIntegrationTests.java index c492668a0..960f4ad76 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/ValidatorIntegrationTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/context/ValidatorIntegrationTests.java @@ -23,6 +23,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.mapping.context.PersistentEntities; import org.springframework.data.rest.core.RepositoryConstraintViolationException; import org.springframework.data.rest.core.RepositoryTestsConfig; @@ -59,10 +60,13 @@ public class ValidatorIntegrationTests { } @Autowired ConfigurableApplicationContext context; + @Autowired KeyValueMappingContext mappingContext; @Test(expected = RepositoryConstraintViolationException.class) public void shouldValidateLastName() throws Exception { + mappingContext.getPersistentEntity(Person.class); + // Empty name should be rejected by PersonNameValidator context.publishEvent(new BeforeSaveEvent(new Person("Dave", ""))); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryConstraintViolationExceptionMessage.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryConstraintViolationExceptionMessage.java index 521517f86..b9c64575c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryConstraintViolationExceptionMessage.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/RepositoryConstraintViolationExceptionMessage.java @@ -39,8 +39,8 @@ public class RepositoryConstraintViolationExceptionMessage { String message = accessor.getMessage(fieldError); - this.errors.add(new ValidationError(fieldError.getObjectName(), message, String.format("%s", - fieldError.getRejectedValue()), fieldError.getField())); + this.errors.add(new ValidationError(fieldError.getObjectName(), message, + String.format("%s", fieldError.getRejectedValue()), fieldError.getField())); } }