How to handle exceptions with Swagger? - jersey

I am building some test APIs using swagger (1.5) and JAX-rs with Jersey (1.13) and I m trying to implement exception handling. For example I have the following code when receiving the results from my DB (Elasticsearch)
#POST
#Path("/category")
#ApiOperation(value="returns products")
#Produces({ "application/json" })
public Response getPostCategories(
#ApiParam(value="keyphrase, required=true) #QueryParam("keyphrase") String keyphrase,
#ApiParam(value="category) #QueryParam("category") String category,
#Context SecurityContext securityContext)
throws WebApplicationException {
SearchRequest searchRequest = new SearchRequest();
searchRequest.setKeyphrase(keyphrase);
searchRequest.setCategory(category);
SearchCategoryQuery categoryQuery = new SearchCategoryQuery();
String searchResponse = null;
try
{
searchResponse = categoryQuery.searchCategory(searchRequest);
}
catch (WebApplicationException ex)
{
throw new WebApplicationException(Response.status(Status.BAD_REQUEST).entity("results no found").type(javax.ws.rs.core.MediaType.APPLICATION_JSON).build());
}
return Response.ok(searchResponse).build();
}
However, in the output swagger always prints the same response
What I need instead is to receive the error messages I specify in each exception. Any ideas?

Swagger by itself does not handle application exceptions as yet.
You will either need to create custom Exception classes (that extend java.lang.exception) or use the existing ones (like WebApplicationException that you are already using) and make the API definition throw these errors. So basically you need to use Java/J2EE/Jersey to throw proper exceptions. Swagger UI will display them for you.
Check this link for details on REST exception handling with Spring.

Related

Better approach than defining an #ExceptionHandler for Exception.class to handle errors?

In my Spring Boot project I defined some #ExceptionHandler classes in a #ControllerAdvice to handle specific exceptions. These build an application-specific JSON reponse instead of the default provided by Spring Boot. So far this works fine for e.g. the MethodArgumentNotValidException which is thrown in case the request validation fails.
#ExceptionHandler(value = {MethodArgumentNotValidException.class})
public ResponseEntity<ApplicationResponse> handleValidationException(MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
log.error("Validation failed: {}", errors);
return buildErrorResponse(ApplicationError.REQUEST_BODY_VALIDATION_FAILED, errors.toString(), HttpStatus.BAD_REQUEST);
}
This approach seems reasonable to me for all exceptions I'm aware of. But what happens if an exception is thrown for which no #ExceptionHandler exists? Then Spring Boot's default will kick in resulting in a JSON response which looks different from my custom one.
My first idea was to add an #ExceptionHandler for the Exception class like this:
#ExceptionHandler(value = {Exception.class})
public ResponseEntity<ApplicationResponse> handleValidationException(Exception ex) {
log.error("An unhandled error occurred: {}", ex.getMessage());
return buildErrorResponse(ApplicationError.GENERIC_ERROR, HttpStatus.INTERNAL_SERVER_ERROR);
}
While this seems to be working I wonder if this approach could have some major drawbacks I'm not aware of. How would you response with a custom JSON structure for any error that might occur?
Spring has several ways to deal with an exception handler as you can see here. Use #ControllerAdvice with several #ExceptionHandler has the following advantages:
Centralized class to deal with not catched exceptions.
Customization about how you want to manage specific ones.
Regarding to include a #ExceptionHandler(value = {Exception.class}) to manage exceptions without an specific handler, a better option is create the next one: #ExceptionHandler(Throwable.class), in that way you will be able to manage all potential problems in your application.
About return a Json when in your application an error happens, you can configure it in the response itself. For example:
private ResponseEntity<ErrorResponseDto> buildErrorResponse(RestApiErrorCode errorCode, List<String> errorMessages, HttpStatus httpStatus) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(APPLICATION_JSON);
ErrorResponseDto error = new ErrorResponseDto(errorCode, errorMessages);
return new ResponseEntity<>(error, headers, httpStatus);
}
You can see the rest of the code here

Spring http status code - java.lang.IllegalArgumentException: No matching constant

I'm using The spring rest-template for calling the rest URL , I get a response from the server but the http-status code is invalid and the Spring throws , java.lang.IllegalArgumentException: No matching constant . Due to this exception the application is failing , this looks like a bug in the Spring code . Since the http status code received is not in the list spring framework is looking forit failed . Is there a Spring way to handle it ?
Spring seems to use the standard status code in their enum. You can find the status codes here: org.springframework.http.HttpStatus.
Probably the API you're querying is not returning a standard HTTP Status code. Your best bet is to create a custom error handler, like this:
var r = new RestTemplate();
r.setErrorHandler(new ResponseErrorHandler() {
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return response.getRawStatusCode() != 550;
}
#Override
public void handleError(ClientHttpResponse response) {
// Do nothing?
}
});
var response = r.exchange("https://httpbin.org/status/550", HttpMethod.GET, null, String.class);
System.out.println(response.getStatusCodeValue());
What we're saying is basically if the status code returned is 550 (not a standard code), we don't want to do anything about it.
Another option you have is, of course, to catch the exception and do something about it.
try {
// Call the API here
} catch (IllegalArgumentException e) {
// Do something about it here...
}

Instantiate IWebContext inside JmsListener in Spring

I have a Spring app running as a rest api.
Let's assume that at some point, a message with some info is generated and stored in a AWS SQS queue.
When JMSListener is called, Im trying to generate a pdf report with thymeleaf and openhtmltopdf. I'm having troubles while instantiating IWebContext because it needs HttpServletRequest, HttpServletResponse, and Locale as params. Locale is not a problem as I could include it as a part of the SQS message, but I'm stuck with REQ and RES.
Code i'm using:
IWebContext ctx = new WebContext(¿REQUEST?, ¿RESPONSE?, servletContext, locale, mapParams);
String processedHtml = templateEngine.process(template, ctx);
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useSVGDrawer(new BatikSVGDrawer());
builder.useFastMode();
builder.withHtmlContent(processedHtml, baseUrl);
builder.toStream(bos);
builder.run();
return bos.toByteArray();
} catch (Exception e) {
logger.error("xxx");
}
As it is being called inside #JmsListener(destination = "${aws.sqs.queue.name}") annotated method, I cannot use none of the following options:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
Because:
RequestContextHolder.getRequestAttributes()
is always null.
Thanks and regards.
I don't think you should be using an IWebContext for this. Instead, just use org.thymeleaf.context.Context.

How can I get data from a response after `RestTemplate` throws a `RestClientException` because it couldn't parse it

Using restTemplate.exchange(uri, method, entity, responseType) to make a REST call fails with a RestClientException when the response is of the wrong responseType. E.g.,
org.springframework.web.client.RestClientException: Error while extracting response for type [java.util.List<java.lang.Byte>] and content type [application/json;charset=UTF-8];
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Numeric value (281759) out of range of Java byte
Since this is not a RestClientResponseException we don't seem to have access to the response data like status code and body (not even in their raw form).
Is there a way to get (raw) data from the original (unparsable) response? (for logging)
Try parsing the response as String. See this answer - the similar concept can be used with the exchange method.
EDIT: If the exception does not occur always and you still want to be able to map the correct responses easily, you could override the corresponding MessageConverter (which is actually throwing the exception) and do anything you want afterwards, because the converter gives you a raw HttpInputMessage.
Assuming you are using MappingJackson2HttpMessageConverter it should look sth. like this (not tested though):
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper) {
#Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
try {
return super.read(type, contextClass, inputMessage);
} catch (HttpMessageNotReadableException e) {
// LOG here...
throw e;
}
}
};
}
Try to add StringHttpMessageConverter in RestTemplate's messageConverters
StringHttpMessageConverter stringHttpMessageConverter
= new StringHttpMessageConverter(StandardCharsets.UTF_8);
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter
= new MappingJackson2HttpMessageConverter(objectMapper);
RestTemplate restTemplate = new RestTemplate(factory);
restTemplate.setMessageConverters(
List.of(stringHttpMessageConverter, jackson2HttpMessageConverter));
final RestTemplate restTemplate = new RestTemplate();
try {
restTemplate.exchange(uri, method, entity, responseType);
} catch (RestClientException e) {
//for logging exact message
restTemplate.getForObject("https://httpbin.org/ip", String.class);
}

Produce a JSONP with a ContainerRequestContext#abortWith

I have this Jersey2-based application, with a custom ContainerRequestFilter.
When the filter(ContainerRequestContext) method is called I want to do a check and, if needed, I want to be able to stop the request before entering the main logic of the application.
At the moment I'm using the ContainerRequestContext#abortWith method to block the call and return an "error" response to the client.
My application returns JSONP to the client, and if I block with abortWith the response is always a JSON.
Looking at the jersey sources I found
org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor that is responsible of the JSONP serialization.
In the abortWith flow I see it fails to find the JSONP annotation, but I don't know where it search for it.
My method has it, in fact in the "normal" scenario (without the abortWith) I see correctly the JSONP format.
I found the solution.
The ContainerRequestFilter#filter method was something like
public void filter(final ContainerRequestContext crc) throws IOException {
if (/* logic */) {
CustomObject ret = new CustomObject();
ret.error = "error message";
crc.abortWith(Response.ok(ret)).build());
}
}
JsonWithPaddingInterceptor expected a response with a JSONP annotation so I retrieve them from the ResourceInfo#resourceMethod, with something like
public void filter(final ContainerRequestContext crc) throws IOException {
if (/* logic */) {
Annotation[] as = this.resourceInfo.getResourceMethod().getAnnotations();
CustomObject ret = new CustomObject();
ret.error = "error message";
crc.abortWith(Response.ok().entity(ret, as).build());
}
}
this way the annotation is correctly found

Resources