From 065a7bd8ac505ac6872a73e2fb442d8fc1399a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Reitmann?= Date: Fri, 11 Nov 2016 12:18:02 +0100 Subject: [PATCH] Added workarround for missing validation error messages (closes #706) --- pom.xml | 2 +- ...ryConstraintViolationExceptionMessage.java | 97 +++++++++++++++++++ .../rest/errors/ExceptionTranslator.java | 27 ++++++ .../rest/errors/ExceptionTranslatorTest.java | 21 ++-- 4 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/CustomRepositoryConstraintViolationExceptionMessage.java diff --git a/pom.xml b/pom.xml index 61ca24faa1..0d9dd600bc 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ spring-boot-starter-parent org.springframework.boot - 1.4.1.RELEASE + 1.4.2.RELEASE diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/CustomRepositoryConstraintViolationExceptionMessage.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/CustomRepositoryConstraintViolationExceptionMessage.java new file mode 100644 index 0000000000..97f8ff5479 --- /dev/null +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/CustomRepositoryConstraintViolationExceptionMessage.java @@ -0,0 +1,97 @@ +package eu.dzhw.fdz.metadatamanagement.common.rest.errors; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.data.rest.core.RepositoryConstraintViolationException; +import org.springframework.data.rest.webmvc.support.RepositoryConstraintViolationExceptionMessage; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Custom dto for validation errors of spring data rest repositories. + * This is necessary due to issue #706. + * It is a modified copy of {@link RepositoryConstraintViolationExceptionMessage}. + * + * @author René Reitmann + * + */ +public class CustomRepositoryConstraintViolationExceptionMessage { + + private final List errors = new ArrayList(); + + /** + * Construct the list of error dtos. + * + * @param violationException The validation exception. + * @param accessor message source for internationalization. + */ + public CustomRepositoryConstraintViolationExceptionMessage( + RepositoryConstraintViolationException violationException, MessageSourceAccessor accessor) { + + for (ObjectError globalError : violationException.getErrors().getGlobalErrors()) { + String message = accessor.getMessage(globalError); + this.errors.add(new ValidationError(globalError.getObjectName(), message, null, null)); + } + + for (FieldError fieldError : violationException.getErrors() + .getFieldErrors()) { + + String message = accessor.getMessage(fieldError); + + this.errors.add(new ValidationError(fieldError.getObjectName(), message, + String.format("%s", fieldError.getRejectedValue()), fieldError.getField())); + } + } + + @JsonProperty("errors") + public List getErrors() { + return errors; + } + + /** + * The error dto. + * + * @author René Reitmann + */ + public static class ValidationError { + + private final String entity; + private final String message; + private final String invalidValue; + private final String property; + + /** + * Construct the error dto. + * @param entity the name of the entity + * @param message the internationalized message + * @param invalidValue the rejected value of the property + * @param property the name of the property (empty for global errors) + */ + 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; + } + } +} diff --git a/src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslator.java b/src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslator.java index 75031f6e50..8d42a58932 100644 --- a/src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslator.java +++ b/src/main/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslator.java @@ -2,7 +2,11 @@ import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.support.MessageSourceAccessor; import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.data.rest.core.RepositoryConstraintViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.security.access.AccessDeniedException; @@ -21,6 +25,13 @@ @ControllerAdvice public class ExceptionTranslator { + private MessageSourceAccessor messageSourceAccessor; + + @Autowired + public ExceptionTranslator(MessageSource messageSource) { + this.messageSourceAccessor = new MessageSourceAccessor(messageSource); + } + @ExceptionHandler(ConcurrencyFailureException.class) @ResponseStatus(HttpStatus.CONFLICT) @ResponseBody @@ -91,4 +102,20 @@ public JsonParsingError processHttpMessageNotReadableException( } return new JsonParsingError(errorMessage); } + + /** + * Handles {@link RepositoryConstraintViolationException}s by returning {@code 400 Bad Request}. + * Introduces a custom dto for validation errors of spring data rest repositories. + * This is necessary due to issue #706. + * @param exception the exception to handle. + * @return 400 bad request + */ + @ExceptionHandler + @ResponseBody + @ResponseStatus(HttpStatus.BAD_REQUEST) + CustomRepositoryConstraintViolationExceptionMessage handleRepositoryConstraintViolationException( + RepositoryConstraintViolationException exception) { + return new CustomRepositoryConstraintViolationExceptionMessage( + exception, messageSourceAccessor); + } } diff --git a/src/test/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslatorTest.java b/src/test/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslatorTest.java index 992de572a3..0b64ca29ef 100644 --- a/src/test/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslatorTest.java +++ b/src/test/java/eu/dzhw/fdz/metadatamanagement/common/rest/errors/ExceptionTranslatorTest.java @@ -12,8 +12,11 @@ import java.util.ArrayList; import java.util.List; +import javax.inject.Inject; + import org.junit.Test; import org.mockito.Mockito; +import org.springframework.context.MessageSource; import org.springframework.core.MethodParameter; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.security.access.AccessDeniedException; @@ -22,11 +25,6 @@ import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; -import eu.dzhw.fdz.metadatamanagement.common.rest.errors.CustomParameterizedException; -import eu.dzhw.fdz.metadatamanagement.common.rest.errors.ErrorDto; -import eu.dzhw.fdz.metadatamanagement.common.rest.errors.ExceptionTranslator; -import eu.dzhw.fdz.metadatamanagement.common.rest.errors.ParameterizedErrorDto; - /** * No Integration Test. No need for application Context. * @@ -35,10 +33,13 @@ */ public class ExceptionTranslatorTest { + @Inject + private MessageSource messageSource; + @Test public void testProcessConcurencyError() { // Arrange - ExceptionTranslator exceptionTranslator = new ExceptionTranslator(); + ExceptionTranslator exceptionTranslator = new ExceptionTranslator(messageSource); // Act ErrorDto dto = @@ -53,7 +54,7 @@ public void testProcessConcurencyError() { @Test public void testProcessValidationError() { // Arrange - ExceptionTranslator exceptionTranslator = new ExceptionTranslator(); + ExceptionTranslator exceptionTranslator = new ExceptionTranslator(messageSource); BindingResult bindingResult = Mockito.mock(BindingResult.class); List fieldErrors = new ArrayList<>(); fieldErrors.add(new FieldError("objectName", "field", "message")); @@ -72,7 +73,7 @@ public void testProcessValidationError() { @Test public void testProcessParameterizedValidationError() { // Arrange - ExceptionTranslator exceptionTranslator = new ExceptionTranslator(); + ExceptionTranslator exceptionTranslator = new ExceptionTranslator(messageSource); // Act ParameterizedErrorDto dto = exceptionTranslator @@ -87,7 +88,7 @@ public void testProcessParameterizedValidationError() { @Test public void testProcessAccessDeniedExcpetion() { // Arrange - ExceptionTranslator exceptionTranslator = new ExceptionTranslator(); + ExceptionTranslator exceptionTranslator = new ExceptionTranslator(messageSource); // Act ErrorDto dto = @@ -102,7 +103,7 @@ public void testProcessAccessDeniedExcpetion() { @Test public void testProcessMethodNotSupportedException() { // Arrange - ExceptionTranslator exceptionTranslator = new ExceptionTranslator(); + ExceptionTranslator exceptionTranslator = new ExceptionTranslator(messageSource); // Act ErrorDto dto = exceptionTranslator.processMethodNotSupportedException(