All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gitlab.oliverlj.jsonapi.configuration.JsonApiResponseEntityExceptionHandler Maven / Gradle / Ivy

Go to download

A spring boot starter which brings json api converter (https://github.com/jasminb/jsonapi-converter) for your great spring project

The newest version!
package com.gitlab.oliverlj.jsonapi.configuration;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolationException;
import org.eclipse.jdt.annotation.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.util.WebUtils;
import com.github.jasminb.jsonapi.JSONAPIDocument;
import com.github.jasminb.jsonapi.JSONAPISpecConstants;
import com.github.jasminb.jsonapi.models.errors.Error;
import com.github.jasminb.jsonapi.models.errors.Source;
import com.gitlab.oliverlj.jsonapi.configuration.converters.ConstraintViolationConverter;
import com.gitlab.oliverlj.jsonapi.configuration.converters.ErrorConverter;
import com.gitlab.oliverlj.jsonapi.exceptions.UnprocessableEntityException;

/**
 * A response entity exception handler for request which accepts {@code application/vnd.api+json}
 * that wish to provide centralized exception handling across all {@code @RequestMapping} methods.
 * 
 * @author Olivier LE JACQUES ([email protected])
 *
 */
@ControllerAdvice
@ComponentScan(basePackageClasses = ErrorConverter.class)
public class JsonApiResponseEntityExceptionHandler {

  @Autowired
  private ConstraintViolationConverter constraintViolationConverter;

  @ExceptionHandler(value = {ResponseStatusException.class})
  protected ResponseEntity handleResponseStatusException(ResponseStatusException ex, WebRequest request) {
    if (canAcceptJsonApi(request)) {
      JSONAPIDocument body = JSONAPIDocument.createErrorDocument(Arrays.asList(newError(ex)));
      return handleExceptionInternal(ex, body, new HttpHeaders(), ex.getStatus(), request);
    } else {
      throw ex;
    }
  }

  @ExceptionHandler(value = {UnprocessableEntityException.class})
  protected ResponseEntity handleUnprocessableEntity(UnprocessableEntityException ex, WebRequest request) {
    if (canAcceptJsonApi(request)) {
      HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY;
      JSONAPIDocument body = JSONAPIDocument.createErrorDocument(Arrays.asList(newError(ex)));
      return handleExceptionInternal(ex, body, new HttpHeaders(), status, request);
    } else {
      throw ex;
    }
  }

  private Error newError(ResponseStatusException ex) {
    Error error = new Error();
    error.setStatus(String.valueOf(ex.getStatus().value()));
    String reason = ex.getReason();
    if (StringUtils.hasText(reason)) {
      error.setTitle(reason);
      error.setDetail(ex.getMessage());
    } else {
      error.setTitle(ex.getMessage());
    }
    return error;
  }


  private Error newError(UnprocessableEntityException ex) {
    Error error = new Error();
    error.setStatus(String.valueOf(HttpStatus.UNPROCESSABLE_ENTITY.value()));
    error.setTitle(ex.getMessage());
    String field = ex.getField();
    if (!field.isEmpty()) {
      error.setSource(new Source());
      error.getSource().setPointer(String.join("/", JSONAPISpecConstants.DATA, JSONAPISpecConstants.ATTRIBUTES, field));
    }
    return error;
  }

  private Error newError(FieldError fieldError) {
    Error error = new Error();
    error.setStatus(String.valueOf(HttpStatus.UNPROCESSABLE_ENTITY.value()));
    error.setTitle(fieldError.getDefaultMessage());
    error.setSource(new Source());
    error.getSource().setPointer(String.join("/", JSONAPISpecConstants.DATA, JSONAPISpecConstants.ATTRIBUTES, fieldError.getField()));
    return error;
  }

  @ExceptionHandler(value = {MethodArgumentNotValidException.class})
  protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, WebRequest request) throws MethodArgumentNotValidException {
    if (canAcceptJsonApi(request)) {
      HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY;
      BindingResult bindingResult = ex.getBindingResult();
      List errors = bindingResult.getAllErrors().stream().filter(error -> error instanceof FieldError).map(FieldError.class::cast).map(this::newError)
          .collect(Collectors.toList());
      JSONAPIDocument body = JSONAPIDocument.createErrorDocument(errors);
      return handleExceptionInternal(ex, body, new HttpHeaders(), status, request);
    } else {
      throw ex;
    }
  }

  @ExceptionHandler(value = {ConstraintViolationException.class})
  protected ResponseEntity handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
    if (canAcceptJsonApi(request)) {
      HttpStatus status = HttpStatus.UNPROCESSABLE_ENTITY;
      List errors = ex.getConstraintViolations().stream().map(constraintViolationConverter::convert).collect(Collectors.toList());
      JSONAPIDocument body = JSONAPIDocument.createErrorDocument(errors);
      return handleExceptionInternal(ex, body, new HttpHeaders(), status, request);
    } else {
      throw ex;
    }
  }

  private boolean canAcceptJsonApi(WebRequest request) {
    return Arrays.asList(request.getHeaderValues(HttpHeaders.ACCEPT)).stream().anyMatch(JsonApiHttpMessageConverter.APPLICATION_JSON_API_VALUE::equals);
  }

  /**
   * A single place to customize the response body of all Exception types.
   * 

* The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE} request attribute * and creates a {@link ResponseEntity} from the given body, headers, and status. * * @param ex the exception * @param body the body for the response * @param headers the headers for the response * @param status the response status * @param request the current request */ protected ResponseEntity handleExceptionInternal(Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, RequestAttributes.SCOPE_REQUEST); } return new ResponseEntity<>(body, headers, status); } }