Handling custom exceptions (i18n) with Spring Data REST

I use Spring Boot 1.5.4 with Spring JPA, Spring Data REST, HATEOAS ... I'm looking for best practice (Spring way) to configure exceptions Spring Data REST manages the addition of i18n support.

I examined the MessageException class ( https://github.com/spring-projects/spring-data-rest/blob/master/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc /support/ExceptionMessage.java ) as a starting point.

The typical Data REST Spring exception is very good:

    {
    "timestamp": "2017-06-24T16:08:54.107+0000",
    "status": 500,
    "error": "Internal Server Error",
    "exception": "org.springframework.dao.InvalidDataAccessApiUsageException",
    "message": "org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint",
    "path": "/api/v1/workSessions/start"
}

What I'm trying to do is:

  • Localize error and message fields (i18n)
  • it is possible to change the message text to something else (always localized)

Spring Data REST , (https://docs.spring.io/spring-data/rest/docs/current/reference/html/). , .

WebMvcConfigurerAdapter :

@Bean
public LocaleResolver localeResolver() {
    return new SmartLocaleResolver();
}

public class SmartLocaleResolver extends CookieLocaleResolver {

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String acceptLanguage = request.getHeader("Accept-Language");
        if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
            return super.determineDefaultLocale(request);
        }
        return request.getLocale();
    }

}

@Bean
public ResourceBundleMessageSource messageSource() {
    ResourceBundleMessageSource source = new ResourceBundleMessageSource();
    source.setBasenames("i18n/messages"); // name of the resource bundle
    source.setUseCodeAsDefaultMessage(true);
    return source;
}

, :

    @ControllerAdvice(annotations = RepositoryRestController.class)
public class GenericExceptionHandler {

    @ExceptionHandler
    public ResponseEntity handle(Exception e, Locale locale) {
          //missing part...
            return new ResponseEntity(exceptionMessageObject, new HttpHeaders(), httpStatus);
    }

, Spring?

+4
2
@ControllerAdvice(annotations = RepositoryRestController.class)
public class GenericExceptionHandler {
    @Autowired
    private MessageSource messageSource;

    @ExceptionHandler
    //if you don't use Exception e in method you can remove it , live only Locale
    public ResponseEntity handle(Exception e, Locale locale) {

            String errorMessage = messageSource.getMessage(
                                 "error.message", new Object[]{},locale);  
            //set message  or return it instead of exceptionMessageObject
            exceptionMessageObject.setMessage(exceptionMessageObject);

            return new ResponseEntity(exceptionMessageObject, 
                   new HttpHeaders(), httpStatus);
    }

. spring doc 7.15.1 MessageSource


" exceptionMessageObject SpringData REST ?"

wraper , :

public class CustomError {
    private HttpStatus status;
    private String message;
    private Exception originalException;//if you need it        
    // getter setter
}

" ? ?"

,

private String resolveExceptionToMessage(Exception exceptio){
    //or put in map<Exceptio,String error.message.type1> 
    // String errorCode = map.get(excptio);
    //eturn messageSource.getMessage(errorCode , new Object[]{},locale);
    if(exceptio instanceof ....){
        return messageSource.getMessage("error.message.type1", new Object[]{},locale);
    }
    return "";
}

@ExceptionHandler ({CustomException1.class}), @ExceptionHandler ({CustomException1.class}).... errror.code,

 @ExceptionHandler({ CustomException1.class})
    public ResponseEntity handleException1() {
        return createError(code for this exceptio 1);
    }
    @ExceptionHandler({ CustomException2.class})
    public ResponseEntity handleException2() {
        return createError(code for this exceptio 2);
    }
    private ResponseEntity createError(String errorCode ) {
            CustomError customError = new CustomError();
            customError.setHttpStatus(HttpStatus.BAD_REQUEST);
            String errorMessage = messageSource.getMessage(
                                 errorCode , new Object[]{},locale); 

            customError.setMessage(errorMessage );
            customError.setOriginalException(e);
            return new ResponseEntity<Object>(customError, new HttpHeaders(), 
                          customError.getStatus());
    }

httpStatus? spring Data REST ...

public ResponseEntity handle(Exception e, Locale locale) {
        CustomError customError = new CustomError();
        customError.setHttpStatus(HttpStatus.BAD_REQUEST);
        customError.setMessage(resolveExceptionToMessage(e));
        customError.setOriginalException(e);
        return new ResponseEntity<Object>(customError, new HttpHeaders(), 
                      customError.getStatus());
}
+3

@sbjavateam . . , , .

@ControllerAdvice, ErrorAttributes:

public class CustomErrorAttributes extends DefaultErrorAttributes {

    private Logger log = LogManager.getLogger();

    @Autowired
    private MessageSource messageSource;

    @Override
    public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
        Locale locale = LocaleContextHolder.getLocale();
        Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
        Throwable throwable = getError(requestAttributes);

        /**
         * Adding the cause if present
         */
        if (throwable != null && throwable.getCause() != null) {
            Throwable cause = throwable.getCause();
            Map<String, Object> causeErrorAttributes = new HashMap<>();
            causeErrorAttributes.put("exception", cause.getClass().getName());
            causeErrorAttributes.put("message", cause.getMessage());
            errorAttributes.put("cause", causeErrorAttributes);
        }

        /**
         * Customizing the message for every exception
         */
        if (throwable instanceof InvalidDataAccessApiUsageException) {
            String message = messageSource.getMessage(throwable.getClass().getName(), new Object[] {}, locale);
            errorAttributes.put("message", message);
        }
        return errorAttributes;
    }
}

, bean WebMvcConfigurerAdapter @Component. :

@EnableHypermediaSupport(type = { HypermediaType.HAL })
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    @Bean
    public CustomErrorAttributes myCustomErrorAttributes() {
        return new CustomErrorAttributes();
    }

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:/i18n/messages");
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
        messageSource.setFallbackToSystemLocale(false);
        return messageSource;
    }
}

, , .

0

Source: https://habr.com/ru/post/1680026/


All Articles