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

com.alon.spring.crud.api.exceptionhandler.ApiExceptionHandler Maven / Gradle / Ivy

Go to download

Fornece implementação básica e expansível para criação API's CRUD com Spring Boot e Spring Data JPA.

There is a newer version: 1.0.9
Show newest version
package com.alon.spring.crud.api.exceptionhandler;

import com.alon.spring.crud.api.exception.BeanValidationException;
import com.alon.spring.crud.api.exceptionhandler.Problem.Violation;
import com.alon.spring.crud.domain.service.exception.CrudException;
import com.alon.spring.crud.domain.service.exception.DataIntegrityException;
import com.alon.spring.crud.domain.service.exception.ProjectionException;
import com.fasterxml.jackson.databind.JsonMappingException.Reference;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.fasterxml.jackson.databind.exc.PropertyBindingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.mapping.PropertyReferenceException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
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.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.time.OffsetDateTime;
import java.util.List;
import java.util.stream.Collectors;

@ControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {

	private static Logger LOGGER = LoggerFactory.getLogger(ApiExceptionHandler.class);

	private MessageSource messageSource;
	
	public ApiExceptionHandler(MessageSource messageSource) {
		this.messageSource = messageSource;
	}

	@ExceptionHandler(Exception.class)
	public ResponseEntity handleUncaught(Exception ex, WebRequest request) {
		
		HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
		
		String detail = "An internal server problem has occurred. If the problem persists, contact your administrator.";
		
		Problem problem = createProblemBuilder(status, ProblemType.INTERNAL_ERROR, detail)
				.build();
		
		return handleExceptionInternal(ex, problem, new HttpHeaders(), status, request);
	}
	
	@ExceptionHandler(CrudException.class)
	public ResponseEntity handleCrudException(CrudException ex, WebRequest request) {
		
		Throwable cause = ExceptionUtils.getRootCause(ex);
		
		if (cause instanceof PropertyReferenceException)
			return handlePropertyReferenceException((PropertyReferenceException) cause, new HttpHeaders(), request);
		
		return handleUncaught(ex, request);
		
	}

	@ExceptionHandler(ProjectionException.class)
	public ResponseEntity handleProjectionException(ProjectionException ex, WebRequest request) {
		HttpStatus status;
		String detail;
		ProblemType type;

		if (request.getParameter("projection") != null) {
			status = HttpStatus.BAD_REQUEST;
			detail = ex.getMessage();
			type = ProblemType.INVALID_PARAMETER;
		} else {
			status = HttpStatus.INTERNAL_SERVER_ERROR;
			detail = "An error occurred when projecting the response. If the problem persists, contact your administrator.";
			type = ProblemType.INTERNAL_ERROR;
		}

		Problem problem = createProblemBuilder(status, type, detail).build();

		return handleExceptionInternal(ex, problem, new HttpHeaders(), status, request);
	}

	
	@ExceptionHandler(PropertyReferenceException.class)
	public ResponseEntity handlePropertyReferenceException(PropertyReferenceException ex, HttpHeaders headers, WebRequest request) {
		
		HttpStatus status = HttpStatus.BAD_REQUEST;
		
		String detail = String.format("Invalid property name %s", ex.getPropertyName());
		
		Problem problem = createProblemBuilder(status, ProblemType.UNRECOGNIZED_MESSAGE, detail)
				.build();
		
		return handleExceptionInternal(ex, problem, headers, status, request);
		
	}

	@Override
	protected ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		
		Throwable rootCause = ExceptionUtils.getRootCause(ex);
		
		if (rootCause instanceof InvalidFormatException)
			return handleInvalidFormatException((InvalidFormatException) rootCause, headers, status, request);
		else if (rootCause instanceof PropertyBindingException)
			return handlePropertyBindingException((PropertyBindingException) rootCause, headers, status, request);
		
		String detail = "Invalid request body.";
		
		Problem problem = createProblemBuilder(status, ProblemType.UNRECOGNIZED_MESSAGE, detail)
				.build();
		
		return handleExceptionInternal(ex, problem, headers, status, request);
	}
	
	@ExceptionHandler(InvalidFormatException.class)
	public ResponseEntity handleInvalidFormatException(InvalidFormatException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		
		String detail = "'%' is not a valid value for the '%s' property. The required type is %s.";
		
		String field = joinPath(ex.getPath());
		
		detail = String.format(detail, ex.getValue(), field, ex.getTargetType().getSimpleName());
		
		Problem problem = createProblemBuilder(status, ProblemType.UNRECOGNIZED_MESSAGE, detail)
				.build();
		
		return handleExceptionInternal(ex, problem, headers, status, request);
	}
	
	@ExceptionHandler(PropertyBindingException.class)
	public ResponseEntity handlePropertyBindingException(PropertyBindingException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		
		String field = joinPath(ex.getPath());
		String detail = String.format("Invalid property '%s'.", field);
		
		Problem problem = createProblemBuilder(status, ProblemType.UNRECOGNIZED_MESSAGE, detail)
				.build();
		
		return handleExceptionInternal(ex, problem, headers, status, request);
	}
	
	@Override
	protected ResponseEntity handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers,
			HttpStatus status, WebRequest request) {
		
		if (ex instanceof MethodArgumentTypeMismatchException) {
			return handleMethodArgumentTypeMismatchException(
					(MethodArgumentTypeMismatchException) ex, headers, status, request);
		}
	
		return super.handleTypeMismatch(ex, headers, status, request);
	}
	
	public ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		
		String detail = "'%s' is an invalid value for the '%s' URL parameter. Required type is '%s'.";
		
		detail = String.format(detail, ex.getValue(), ex.getName(), ex.getRequiredType().getSimpleName());
		
		Problem problem = createProblemBuilder(status, ProblemType.INVALID_PARAMETER, detail)
				.build();
		
		return handleExceptionInternal(ex, problem, headers, status, request);
	}
	
	@Override
	protected ResponseEntity handleBindException(BindException ex, HttpHeaders headers, HttpStatus status,
			WebRequest request) {
		
		return handleValidationInternal(ex, headers, status, request, ex.getBindingResult());
	}
	
	@Override
	public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
			HttpHeaders headers, HttpStatus status, WebRequest request) {
		
		return handleValidationInternal(ex, headers, status, request, ex.getBindingResult());
	}

	private ResponseEntity handleValidationInternal(Exception ex, HttpHeaders headers,
			HttpStatus status, WebRequest request, BindingResult bindingResult) {
		
		String detail = "Invalid field(s).";
		
		List problemFields = this.createProblemFields(bindingResult);
		
		Problem problem = createProblemBuilder(status, ProblemType.INVALID_DATA, detail)
				.violations(problemFields)
				.build();
		
		return handleExceptionInternal(ex, problem, headers, status, request);
	}

	@Override
	protected ResponseEntity handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
			HttpStatus status, WebRequest request) {
		
		String detail = String.format("Resource not found: '%s'", ex.getRequestURL());
		
		Problem problem = createProblemBuilder(status, ProblemType.NOT_FOUND, detail)
				.build();
		
		return super.handleExceptionInternal(ex, problem, headers, status, request);
	}
	
	@ExceptionHandler(BeanValidationException.class)
	public ResponseEntity handleBeanValidation(BeanValidationException ex, WebRequest request) {
		
		HttpStatus status = HttpStatus.BAD_REQUEST;
		String detail = "Invalid fields present.";
		
		List problemFields = this.createProblemFields(ex.getBindingResult());
		
		Problem problem = createProblemBuilder(status, ProblemType.INVALID_DATA, detail)
				.violations(problemFields)
				.build();
		
		return handleExceptionInternal(ex, problem, new HttpHeaders(), status, request);
	}
	
	@ExceptionHandler(DataIntegrityException.class)
	public ResponseEntity handleDataIntegrityException(DataIntegrityException ex, WebRequest request) {
		
		HttpStatus status = HttpStatus.NOT_FOUND;
		
		Problem problem = createProblemBuilder(status, ProblemType.INTEGRITY_VIOLATION, ex.getMessage())
				.build();
		
		return handleExceptionInternal(ex, problem, new HttpHeaders(), 
				status, request);
	}
	
	@ExceptionHandler(IllegalArgumentException.class)
	public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex, WebRequest request) {
		
		HttpStatus status = HttpStatus.BAD_REQUEST;
		
		String detail = "There is a problem with the data sent.";
		
		Violation violation = Violation.of()
				.context("Original error message")
				.message(ex.getMessage())
				.build();
		
		Problem problem = createProblemBuilder(status, ProblemType.INVALID_DATA, detail)
				.violations(List.of(violation))
				.build();
		
		return handleExceptionInternal(ex, problem, new HttpHeaders(), status, request);
	}

	@ExceptionHandler(ResponseStatusException.class)
	public ResponseEntity handleResponseStatusException(ResponseStatusException ex, WebRequest request) {
		HttpStatus status = ex.getStatus();
		String detail = ex.getReason();

		Problem problem = createProblemBuilder(status,
				ProblemType.getByStatusCode(status), detail).build();

		return handleExceptionInternal(ex, problem, new HttpHeaders(), status, request);
	}
	
	@Override
	protected ResponseEntity handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
			HttpStatus status, WebRequest request) {

		if (body != null)
			LOGGER.error(((Problem) body).getDetail(), ex);
		else
			LOGGER.error("An problem occurred and was handled by exception handler", ex);

		if (body == null) {
			body = Problem.of()
					.status(status.value())
					.title(status.getReasonPhrase())
					.build();
		} else if (body instanceof String) {
			body = Problem.of()
					.status(status.value())
					.title((String) body)
					.build();
		}
		
		return super.handleExceptionInternal(ex, body, headers, status, request);
	}
	
	private List createProblemFields(BindingResult bindingResult) {
		return bindingResult.getAllErrors().stream()
				.map(error -> {
					
					String message = messageSource.getMessage(error, LocaleContextHolder.getLocale());
					
					String name = error.getObjectName();
					
					if (error instanceof FieldError)
						name = ((FieldError) error).getField();
					
					return Problem.Violation.of()
							.context(name)
							.message(message)
							.build();
				})
				.collect(Collectors.toList());
	}
	
	private Problem.ProblemBuilder createProblemBuilder(HttpStatus status, ProblemType type, String detail) {
		
		return Problem.of()
				.timestamp(OffsetDateTime.now())
				.status(status.value())
				.type(type.getUri())
				.title(type.getTitle())
				.detail(detail);
		
	}
	
	private String joinPath(List path) {
		return path.stream()
				.map(Reference::getFieldName)
				.collect(Collectors.joining("."));
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy