Fix for #71. Made validation messages more robust and synced them between the Spring Validator based validation and the JSR-303 validation annotations. The output differed before but is now consistent.

This commit is contained in:
Jon Brisbin
2013-03-05 14:05:53 -06:00
parent 810b2144c8
commit 05f11a6cf9
8 changed files with 141 additions and 61 deletions

View File

@@ -1,10 +1,13 @@
package org.springframework.data.rest.example;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.example.jpa.Person;
import org.springframework.data.rest.example.jpa.PersonValidator;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
@@ -17,6 +20,17 @@ import org.springframework.hateoas.ResourceProcessor;
@ImportResource("classpath:META-INF/spring/security-config.xml")
public class RestExporterExampleRestConfig extends RepositoryRestMvcConfiguration {
@Bean public MessageSource messageSource() {
ReloadableResourceBundleMessageSource msgsrc = new ReloadableResourceBundleMessageSource();
msgsrc.setBasename("/WEB-INF/classes/ValidationMessages");
msgsrc.setFallbackToSystemLocale(false);
return msgsrc;
}
@Bean public PersonValidator beforeCreatePersonValidator() {
return new PersonValidator();
}
@Override protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.addResourceMappingForDomainType(Person.class)
.addResourceMappingFor("lastName")

View File

@@ -0,0 +1,21 @@
package org.springframework.data.rest.example.jpa;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
/**
* @author Jon Brisbin
*/
public class PersonValidator implements Validator {
@Override public boolean supports(Class<?> clazz) {
return Person.class.isAssignableFrom(clazz);
}
@Override public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmpty(errors, "firstName", "not.blank");
ValidationUtils.rejectIfEmpty(errors, "lastName", "not.blank");
}
}

View File

@@ -0,0 +1 @@
not.blank = Field {1} cannot be blank

View File

@@ -8,6 +8,7 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.validation.ConstraintViolationException;
import org.slf4j.Logger;
@@ -140,9 +141,10 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
ConstraintViolationException.class
})
@ResponseBody
public ResponseEntity handleConstraintViolationException(ConstraintViolationException cve) {
public ResponseEntity handleConstraintViolationException(Locale locale,
ConstraintViolationException cve) {
return response(null,
new ConstraintViolationExceptionMessage(cve, applicationContext),
new ConstraintViolationExceptionMessage(cve, applicationContext, locale),
HttpStatus.BAD_REQUEST);
}
@@ -150,9 +152,10 @@ public class AbstractRepositoryRestController implements ApplicationContextAware
RepositoryConstraintViolationException.class
})
@ResponseBody
public ResponseEntity handleRepositoryConstraintViolationException(RepositoryConstraintViolationException rcve) {
public ResponseEntity handleRepositoryConstraintViolationException(Locale locale,
RepositoryConstraintViolationException rcve) {
return response(null,
new RepositoryConstraintViolationExceptionMessage(rcve, applicationContext),
new RepositoryConstraintViolationExceptionMessage(rcve, applicationContext, locale),
HttpStatus.BAD_REQUEST);
}

View File

@@ -2,6 +2,7 @@ package org.springframework.data.rest.webmvc.support;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
@@ -16,10 +17,12 @@ public class ConstraintViolationExceptionMessage {
private final ConstraintViolationException cve;
private final List<ConstraintViolationMessage> messages = new ArrayList<ConstraintViolationMessage>();
public ConstraintViolationExceptionMessage(ConstraintViolationException cve, MessageSource msgSrc) {
public ConstraintViolationExceptionMessage(ConstraintViolationException cve,
MessageSource msgSrc,
Locale locale) {
this.cve = cve;
for(ConstraintViolation cv : cve.getConstraintViolations()) {
messages.add(new ConstraintViolationMessage(cv, msgSrc));
messages.add(new ConstraintViolationMessage(cv, msgSrc, locale));
}
}

View File

@@ -2,6 +2,7 @@ package org.springframework.data.rest.webmvc.support;
import static java.lang.String.*;
import java.util.Locale;
import javax.validation.ConstraintViolation;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -14,39 +15,41 @@ import org.springframework.context.MessageSource;
*/
public class ConstraintViolationMessage {
private final ConstraintViolation<?> violation;
private final String message;
private final ConstraintViolation<?> violation;
private final String message;
public ConstraintViolationMessage(ConstraintViolation<?> violation, MessageSource msgSrc) {
this.violation = violation;
this.message = msgSrc.getMessage(violation.getMessageTemplate(),
new Object[]{
violation.getLeafBean().getClass().getSimpleName(),
violation.getPropertyPath().toString(),
violation.getInvalidValue()
},
violation.getMessage(),
null);
}
public ConstraintViolationMessage(ConstraintViolation<?> violation,
MessageSource msgSrc,
Locale locale) {
this.violation = violation;
this.message = msgSrc.getMessage(violation.getMessageTemplate(),
new Object[]{
violation.getLeafBean().getClass().getSimpleName(),
violation.getPropertyPath().toString(),
violation.getInvalidValue()
},
violation.getMessage(),
locale);
}
@JsonProperty("entity")
public String getEntity() {
return violation.getRootBean().getClass().getName();
}
@JsonProperty("entity")
public String getEntity() {
return violation.getRootBean().getClass().getName();
}
@JsonProperty("message")
public String getMessage() {
return message;
}
@JsonProperty("message")
public String getMessage() {
return message;
}
@JsonProperty("invalidValue")
public String getInvalidValue() {
return format("%s", violation.getInvalidValue());
}
@JsonProperty("invalidValue")
public String getInvalidValue() {
return format("%s", violation.getInvalidValue());
}
@JsonProperty("property")
public String getProperty() {
return violation.getPropertyPath().toString();
}
@JsonProperty("property")
public String getProperty() {
return violation.getPropertyPath().toString();
}
}

View File

@@ -2,6 +2,7 @@ package org.springframework.data.rest.webmvc.support;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.context.MessageSource;
@@ -13,35 +14,69 @@ import org.springframework.validation.FieldError;
*/
public class RepositoryConstraintViolationExceptionMessage {
private final RepositoryConstraintViolationException violationException;
private final List<String> errors = new ArrayList<String>();
private final RepositoryConstraintViolationException violationException;
private final List<ValidationError> errors = new ArrayList<ValidationError>();
public RepositoryConstraintViolationExceptionMessage(RepositoryConstraintViolationException violationException,
MessageSource msgSrc) {
this.violationException = violationException;
public RepositoryConstraintViolationExceptionMessage(RepositoryConstraintViolationException violationException,
MessageSource msgSrc,
Locale locale) {
this.violationException = violationException;
for(FieldError fe : violationException.getErrors().getFieldErrors()) {
List<Object> args = new ArrayList<Object>();
args.add(fe.getObjectName());
args.add(fe.getField());
args.add(fe.getRejectedValue());
if(null != fe.getArguments()) {
for(Object o : fe.getArguments()) {
args.add(o);
}
}
for(FieldError fe : violationException.getErrors().getFieldErrors()) {
List<Object> args = new ArrayList<Object>();
args.add(fe.getObjectName());
args.add(fe.getField());
args.add(fe.getRejectedValue());
if(null != fe.getArguments()) {
for(Object o : fe.getArguments()) {
args.add(o);
}
}
String msg = msgSrc.getMessage(fe.getCode(),
args.toArray(),
fe.getDefaultMessage(),
null);
this.errors.add(msg);
}
}
String msg = msgSrc.getMessage(fe.getCode(),
args.toArray(),
fe.getDefaultMessage(),
locale);
this.errors.add(new ValidationError(fe.getObjectName(),
msg,
String.format("%s", fe.getRejectedValue()),
fe.getField()));
}
}
@JsonProperty("errors")
public List<String> getErrors() {
return errors;
}
@JsonProperty("errors")
public List<ValidationError> getErrors() {
return errors;
}
public static class ValidationError {
String entity;
String message;
String invalidValue;
String property;
public ValidationError(String entity, String message, String invalidValue, String property) {
this.entity = entity;
this.message = message;
this.invalidValue = invalidValue;
this.property = property;
}
public String getEntity() {
return entity;
}
public String getMessage() {
return message;
}
public String getInvalidValue() {
return invalidValue;
}
public String getProperty() {
return property;
}
}
}