Skip to content

Instantly share code, notes, and snippets.

@ehabqadah
Last active July 18, 2020 16:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ehabqadah/30e17bebe0b7b00e40c6565868d0ec37 to your computer and use it in GitHub Desktop.
Save ehabqadah/30e17bebe0b7b00e40c6565868d0ec37 to your computer and use it in GitHub Desktop.
/**
* * Handle all exceptions and java bean validation errors
* for all endpoints income data that use the @Valid annotation
*
* @author Ehab Qadah
*/
@ControllerAdvice
public class GeneralExceptionHandler extends ResponseEntityExceptionHandler {
public static final String ACCESS_DENIED = "Access denied!";
public static final String INVALID_REQUEST = "Invalid request";
public static final String ERROR_MESSAGE_TEMPLATE = "message: %s %n requested uri: %s";
public static final String LIST_JOIN_DELIMITER = ",";
public static final String FIELD_ERROR_SEPARATOR = ": ";
private static final Logger local_logger = LoggerFactory.getLogger(GeneralExceptionHandler.class);
private static final String ERRORS_FOR_PATH = "errors {} for path {}";
private static final String PATH = "path";
private static final String ERRORS = "error";
private static final String STATUS = "status";
private static final String MESSAGE = "message";
private static final String TIMESTAMP = "timestamp";
private static final String TYPE = "type";
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception,
HttpHeaders headers,
HttpStatus status, WebRequest request) {
List<String> validationErrors = exception.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> error.getField() + FIELD_ERROR_SEPARATOR + error.getDefaultMessage())
.collect(Collectors.toList());
return getExceptionResponseEntity(exception, HttpStatus.BAD_REQUEST, request, validationErrors);
}
@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException exception,
HttpHeaders headers, HttpStatus status,
WebRequest request) {
return getExceptionResponseEntity(exception, status, request,
Collections.singletonList(exception.getLocalizedMessage()));
}
@ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity<Object> handleConstraintViolation(
ConstraintViolationException exception, WebRequest request) {
final List<String> validationErrors = exception.getConstraintViolations().stream().
map(violation ->
violation.getPropertyPath() + FIELD_ERROR_SEPARATOR + violation.getMessage())
.collect(Collectors.toList());
return getExceptionResponseEntity(exception, HttpStatus.BAD_REQUEST, request, validationErrors);
}
/**
* A general handler for all uncaught exceptions
*/
@ExceptionHandler({Exception.class})
public ResponseEntity<Object> handleAllExceptions(Exception exception, WebRequest request) {
ResponseStatus responseStatus =
exception.getClass().getAnnotation(ResponseStatus.class);
final HttpStatus status =
responseStatus!=null ? responseStatus.value():HttpStatus.INTERNAL_SERVER_ERROR;
final String localizedMessage = exception.getLocalizedMessage();
final String path = request.getDescription(false);
String message = (StringUtils.isNotEmpty(localizedMessage) ? localizedMessage:status.getReasonPhrase());
logger.error(String.format(ERROR_MESSAGE_TEMPLATE, message, path), exception);
return getExceptionResponseEntity(exception, status, request, Collections.singletonList(message));
}
/**
* Build a detailed information about the exception in the response
*/
private ResponseEntity<Object> getExceptionResponseEntity(final Exception exception,
final HttpStatus status,
final WebRequest request,
final List<String> errors) {
final Map<String, Object> body = new LinkedHashMap<>();
final String path = request.getDescription(false);
body.put(TIMESTAMP, Instant.now());
body.put(STATUS, status.value());
body.put(ERRORS, errors);
body.put(TYPE, exception.getClass().getSimpleName());
body.put(PATH, path);
body.put(MESSAGE, getMessageForStatus(status));
final String errorsMessage = CollectionUtils.isNotEmpty(errors) ?
errors.stream().filter(StringUtils::isNotEmpty).collect(Collectors.joining(LIST_JOIN_DELIMITER))
:status.getReasonPhrase();
local_logger.error(ERRORS_FOR_PATH, errorsMessage, path);
return new ResponseEntity<>(body, status);
}
private String getMessageForStatus(HttpStatus status) {
switch (status) {
case UNAUTHORIZED:
return ACCESS_DENIED;
case BAD_REQUEST:
return INVALID_REQUEST;
default:
return status.getReasonPhrase();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment