DATAREST-798 - Fixed invalid implementation of ValidationErrors.
Changed the implementation of ValidationErrors to be based on AbstractBeanPropertyBindingResult to consider the nesting implemented in superclasses and using a PersistentPropertyAccessor to lookup the property values. ValidatingRepositoryEventListener now uses this implementation if a PersistentEntity can be obtained for the type under consideration, falling back to a DirectFieldBindingResult otherwise.
This commit is contained in:
@@ -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<ObjectError> globalErrors = new ArrayList<ObjectError>();
|
||||
private List<FieldError> fieldErrors = new ArrayList<FieldError>();
|
||||
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<ObjectError> getGlobalErrors() {
|
||||
return globalErrors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FieldError> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
|
||||
@@ -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<Bar> bars = new ArrayList<Bar>();
|
||||
}
|
||||
|
||||
static class Bar {
|
||||
String field;
|
||||
}
|
||||
}
|
||||
@@ -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", "")));
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user