package me.yufan.gossip.rest.support; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Strings; import com.google.inject.Inject; import lombok.extern.slf4j.Slf4j; import me.yufan.gossip.rest.response.ValidateErrorResponse; import org.jboss.resteasy.api.validation.ResteasyConstraintViolation; import org.jboss.resteasy.api.validation.ResteasyViolationException; import javax.validation.ValidationException; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import java.util.List; import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; import static javax.ws.rs.core.Response.Status.BAD_REQUEST; import static org.jboss.resteasy.api.validation.Validation.VALIDATION_HEADER; /** * handle {@see javax.validation.ValidationException} for frontend friendly response. * The check logic is based {@see org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper} */ @Slf4j @Provider public class GossipValidateErrorProvider implements ExceptionMapper<ValidationException> { private final ObjectMapper objectMapper; @Inject public GossipValidateErrorProvider(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public Response toResponse(ValidationException exception) { if (exception instanceof ResteasyViolationException) { ResteasyViolationException resteasyViolationException = ResteasyViolationException.class.cast(exception); Exception e = resteasyViolationException.getException(); if (e == null) { return buildViolationReportResponse(resteasyViolationException); } } // Common response return exceptionResponse(exception); } private Response exceptionResponse(Exception exception) { ValidateErrorResponse errorResponse = new ValidateErrorResponse(unwrapException(exception)); return Response.status(BAD_REQUEST).header(VALIDATION_HEADER, "true") .type(APPLICATION_JSON_TYPE).entity(errorResponse).build(); } private Response buildViolationReportResponse(ResteasyViolationException exception) { ResponseBuilder builder = Response.status(BAD_REQUEST); builder.header(VALIDATION_HEADER, "true"); builder.type(APPLICATION_JSON_TYPE); List<ResteasyConstraintViolation> violations = exception.getViolations(); if (violations.isEmpty()) { builder.entity(new ValidateErrorResponse(exception.toString())); } else { if (log.isDebugEnabled()) { try { log.debug(objectMapper.writeValueAsString(violations)); } catch (JsonProcessingException e) { log.debug("", e); // Useless, just make sonar and compiler happy w(゚Д゚)w } } builder.entity(new ValidateErrorResponse(violations)); } return builder.build(); } // No recursion on exception handle private String unwrapException(final Throwable t) { if (t != null) { StringBuilder sb = new StringBuilder(128); Throwable throwable = t; int depth = 0; sb.append(throwable.toString()); while (throwable.getCause() != null && throwable != throwable.getCause()) { sb.append("["); throwable = throwable.getCause(); sb.append(throwable.toString()); depth++; } sb.append(Strings.repeat("]", depth)); return sb.toString(); } else { return ""; } } }