Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling custom exceptions (i18n) with Spring Data REST

I'm using Spring Boot 1.5.4 with Spring JPA, Spring Data REST, HATEOAS... I'm looking for a best practice (Spring way) to customize exceptions Spring Data REST is managing adding the i18n support.

I looked at the class MessageException (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 start point.

A typical Spring Data REST exception is very nice:

    {
    "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:

  1. Localize error and message fields (i18n)
  2. possibly change the message text to something else (always localized)

I didn't find any reference in Spring Data REST doc about how to customize or localize exception (https://docs.spring.io/spring-data/rest/docs/current/reference/html/). I hope there is a elegant way to do that.

I added in my WebMvcConfigurerAdapter this:

@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;
}

I guess I could be able to intercept exceptions in this way:

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

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

Is there a way to put all together using Spring best practices?

like image 243
drenda Avatar asked Mar 09 '23 18:03

drenda


1 Answers

@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);
    }

see spring doc 7.15.1 Internationalization using MessageSource


" how I should create exceptionMessageObject to be like the one Spring Data REST creates? "

create you own error wraper like :

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

"How to have different messages for different exceptions? Should I create a long if else chain checking the class of the exception? "

create resolver ,

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 "";
}

or use methods with @ExceptionHandler({ CustomException1.class }) , @ExceptionHandler({ CustomException1.class }).... and do put in each method just errror.code , all other part are similar

 @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());
    }

How set httpStatus? I would like use the default status Spring Data REST use for commons exceptions...

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());
}
like image 51
xyz Avatar answered Mar 11 '23 06:03

xyz