Skip to content

Commit

Permalink
Added workarround for missing validation error messages (closes #706)
Browse files Browse the repository at this point in the history
  • Loading branch information
René Reitmann committed Nov 11, 2016
1 parent 6110ace commit 065a7bd
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 11 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>1.4.1.RELEASE</version>
<version>1.4.2.RELEASE</version>
<relativePath />
</parent>

Expand Down
Original file line number Diff line number Diff line change
@@ -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<ValidationError> errors = new ArrayList<ValidationError>();

/**
* 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<ValidationError> 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*
Expand All @@ -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 =
Expand All @@ -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<FieldError> fieldErrors = new ArrayList<>();
fieldErrors.add(new FieldError("objectName", "field", "message"));
Expand All @@ -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
Expand All @@ -87,7 +88,7 @@ public void testProcessParameterizedValidationError() {
@Test
public void testProcessAccessDeniedExcpetion() {
// Arrange
ExceptionTranslator exceptionTranslator = new ExceptionTranslator();
ExceptionTranslator exceptionTranslator = new ExceptionTranslator(messageSource);

// Act
ErrorDto dto =
Expand All @@ -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(
Expand Down

0 comments on commit 065a7bd

Please sign in to comment.