Override not-found exception handler in micronaut - java

I'm trying to override the not-found exception handler in micronaut.
I want to throw my custom error format, but cannot find which exception handler I've to replace.
My current implementation:
#Produces
#Singleton
#Requires(classes = {Exception.class, ExceptionHandler.class})
//#Replaces(NotFoundExceptionHandler.class)
public class MyNotFoundExceptionHandler implements ExceptionHandler<Exception, HttpResponse<?>> {
#Override
public HttpResponse<?> handle(HttpRequest request, Exception exception) {
return HttpResponse.notFound();
}
}
The default micronaut exception handler throws this format on a not-found:
{
"message": "Page Not Found",
"_links": {
"self": {
"href": "/api/not-found",
"templated": false
}
}
}
I'm already overridden other default exception handlers, like ConstraintExceptionHandler and UnsatisfiedRouteHandler, but for the not-found the logger doesn't print the exception in the console:
20:43:23.863 [default-nioEventLoopGroup-1-5] DEBUG i.m.h.s.netty.RoutingInBoundHandler - Request GET /api/not-found
20:43:23.864 [default-nioEventLoopGroup-1-5] DEBUG i.m.h.s.netty.RoutingInBoundHandler - No matching route: GET /api/not-found
20:43:23.864 [default-nioEventLoopGroup-1-5] DEBUG i.m.web.router.RouteMatchUtils - Route match attribute for request (/api/not-found) not found
Anyone knows which exception handler to override?

you can find what you are looking for here https://guides.micronaut.io/latest/micronaut-error-handling-maven-java.html#global-error
With this code in any controller you should be ok:
#Error(status = HttpStatus.NOT_FOUND, global = true)
fun notFoundHandler(request: HttpRequest<*>): HttpResponse<JsonError> {
return HttpResponse.status<JsonError>(HttpStatus.NOT_FOUND).body(JsonError("Page '$request' Not Found"))
}

Related

How to handle ResourceAccessException in resttemplate using Spring integration

Here I'm using Spring integration's http outbound gateway to make a http call. I have also added timeout for the call. I have configured the timeout using restemplate. Here whenever it's taking more time then it's throwing an ResourceAccessException but I want to handle that exception and want to send proper msg to the user. Even though I'm using a custom error handler but the exception is not getting handled by that. Below the code where I'm using scatter gather pattern and flow2 is the http call where I want to handle the exception-
#Autowired private RestTemplateConfig resttemplateconfig.
#Bean
public IntegrationFlow mainFlow() {
return flow ->
flow.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(flow1())
.recipientFlow(flow2()),
gatherer ->
gatherer
.releaseLockBeforeSend(true)
.releaseStrategy(group -> group.size() == 1))
.aggregate()
.to(saveCDResponseToDB());
}
#Bean
public IntegrationFlow flow2() {
return flow ->
flow.channel(c -> c.executor(Executors.newCachedThreadPool()))
.handle(
Http.outboundGateway(
"http://localhost:4444/test", resttemplateconfig.restTemplate())
.extractPayload(true)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class));
}
//RestTemplateConfig - The Resttemplate Config class where I'm setting the timeout and errorhandler.
#Configuration
public class RestTemplateConfig {
private final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(6);
#Autowired CustomErrorHandler errorHandler;
#Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(TIMEOUT);
requestFactory.setReadTimeout(TIMEOUT);
RestTemplate restTemplate = new RestTemplate(requestFactory);
errorHandler.setMessageConverters(restTemplate.getMessageConverters());
restTemplate.setErrorHandler(errorHandler);
return restTemplate;
}
}
//custom error handler
#Component
public class CustomServiceErrorHandler implements ResponseErrorHandler {
private List<HttpMessageConverter<?>> messageConverters;
#Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return hasError(response.getStatusCode());
}
protected boolean hasError(HttpStatus statusCode) {
return (statusCode.is4xxClientError() || statusCode.is5xxServerError());
}
#Override
public void handleError(ClientHttpResponse httpResponse) throws IOException {
if (httpResponse.getStatusCode().series() == SERVER_ERROR) {
// handle SERVER_ERROR
System.out.println("SERVER_ERROR");
} else if (httpResponse.getStatusCode().series() == CLIENT_ERROR) {
// handle CLIENT_ERROR
System.out.println("CLIENT_ERROR");
} else {
System.out.println("SOME_ERROR");
}
}
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
}
I'm getting below error that I want to handle -
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:4444/test": Read timed out; nested exception is java.net.SocketTimeoutException: Read timed out
If I'm adding .errorHandler(new CustomErrorHandler()) in Http.outboundgateway then I'm getting error saying -
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flow2' defined in class path resource [example/common/config/SpringIntegrationConfiguration.class]: Initialization of bean failed; nested exception is java.lang.RuntimeException: java.lang.IllegalArgumentException: the 'errorHandler' must be specified on the provided 'restTemplate'
the 'errorHandler' must be specified on the provided 'restTemplate'
It's probably clearly states that such an error handler must be configured on the externally provided RestTemplate.
When an IOException is thrown in the RestTemplate, that error handler is not invoked. See the source code of its doExecute() method:
...
response = request.execute();
handleResponse(url, method, response);
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
catch (IOException ex) {
...
throw new ResourceAccessException("I/O error on " + method.name() +
" request for \"" + resource + "\": " + ex.getMessage(), ex);
This type of exceptions has to be handled around that HTTP Gateway call.
See if you can set a custom errorChannel header - enrichHeaders() and handle this error in the dedicated IntegrationFlow. Since you use an ExecutorChannel, the async error handling must have an effect.
Another way (and I recall as showed you before) is to use an ExpressionEvaluatingRequestHandlerAdvice on that Http.outboundGateway() endpoint.
See more in docs: https://docs.spring.io/spring-integration/reference/html/messaging-endpoints.html#message-handler-advice-chain

Spock + spring boot web - get exception message

So, I'm using spring boot web and I want to test this method:
private void testException(HotelTvApp app) {
throw new InternalServerException("Test message");
}
Returning custom exception:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalServerException extends RuntimeException {
public InternalServerException(String message) {
super(message);
}
}
A controller that invokes this method returns following JSON data:
{
"timestamp": 1558423837560,
"status": 500,
"error": "Internal Server Error",
"message": "Test message",
"path": "/api/v1/test"
}
I want to write a test that will check if the exception message is correct. I tried this:
def "createRoom() should return 500 if trying to create room with already existing number"() {
given:
def uriRequest = UriComponentsBuilder.fromPath("/api/v1/test")
when:
def perform = mvc.perform(get(uriRequest.toUriString()))
then:
perform.andExpect(status().isInternalServerError())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath('$.message', is("Test message")))
}
But I get this exception:
Caused by: java.lang.AssertionError: Content type not set
How can I check the exception message here?
You can explicitly set the contentType as below
#ExceptionHandler(OrgNotFoundException.class)
public ResponseEntity<String> exceptionHandler(final Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body("Error");
}

JAX-RS interceptor mapping exceptions on void methods

I am trying to use an interceptor to introspect and change exceptions that occur on the backend. The interceptor looks something like this:
public class ApplicationErrorInterceptor {
private static final Logger LOGGER = Logger.getLogger(ApplicationErrorInterceptor.class);
#AroundInvoke
public Object handleException(InvocationContext context) {
try {
return context.proceed();
} catch (Exception ex) {
LOGGER.errorf(ex, "An unhandled exception occured!");
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("some custom 500 error text")
.build();
}
}
}
And the service using this interceptor looks something like this:
#Path("/somepath")
#Interceptors({ApplicationErrorInterceptor.class})
public interface SomeRestService {
#POST
#Path("getResponse")
Response getResponse();
#POST
#Path("getVoid")
void getVoid();
}
Assume the implementation for both of these methods throw an exception. I expect the exception to be mapped to a 500 server error with the supplied custom message in both cases. Unfortunately, the method returning void will get mapped to a 204 No Content response. If I remove the interceptor altogether, the default 500 server error occurs, which is at least the correct status code, but I lost the error customization

Netflix Feign - Propagate Status and Exception through Microservices

I'm using Netflix Feign to call to one operation of a Microservice A to other other operation of a Microservice B which validates a code using Spring Boot.
The operation of Microservice B throws an exception in case of the validation has been bad. Then I handled in the Microservices and return a HttpStatus.UNPROCESSABLE_ENTITY (422) like next:
#ExceptionHandler({
ValidateException.class
})
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
#ResponseBody
public Object validationException(final HttpServletRequest request, final validateException exception) {
log.error(exception.getMessage(), exception);
error.setErrorMessage(exception.getMessage());
error.setErrorCode(exception.getCode().toString());
return error;
}
So, when Microservice A calls to B in a interface as next:
#Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
#RequestLine("GET /other")
void otherOperation(#Param("other") String other );
#Headers("Content-Type: " + MediaType.APPLICATION_JSON_UTF8_VALUE)
#RequestLine("GET /code/validate")
Boolean validate(#Param("prefix") String prefix);
static PromotionClient connect() {
return Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.target(PromotionClient.class, Urls.SERVICE_URL.toString());
}
and the validations fails it returns a internal error 500 with next message:
{
"timestamp": "2016-08-05T09:17:49.939+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "feign.FeignException",
"message": "status 422 reading Client#validate(String); content:\n{\r\n \"errorCode\" : \"VALIDATION_EXISTS\",\r\n \"errorMessage\" : \"Code already exists.\"\r\n}",
"path": "/code/validate"
}
But I need to return the same as the Microservice operation B.
Which would be the best ways or techniques to propagate Status and Exceptions through microservices using Netflix Feign?
You could use a feign ErrorDecoder
https://github.com/OpenFeign/feign/wiki/Custom-error-handling
Here is an example
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyBadRequestException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
For spring to pick up the ErrorDecoder you have to put it on the ApplicationContext:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
Shameless plug for a little library I did that uses reflection to dynamically rethrow checked exceptions (and unchecked if they are on the Feign interface) based on an error code returned in the body of the response.
More information on the readme :
https://github.com/coveo/feign-error-decoder
OpenFeign's FeignException doesn't bind to a specific HTTP status (i.e. doesn't use Spring's #ResponseStatus annotation), which makes Spring default to 500 whenever faced with a FeignException. That's okay because a FeignException can have numerous causes that can't be related to a particular HTTP status.
However you can change the way that Spring handles FeignExceptions. Simply define an ExceptionHandler that handles the FeignException the way you need it (see here):
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(FeignException.class)
public String handleFeignStatusException(FeignException e, HttpServletResponse response) {
response.setStatus(e.status());
return "feignError";
}
}
This example makes Spring return the same HTTP status that you received from Microservice B. You can go further and also return the original response body:
response.getOutputStream().write(e.content());
Write your custom exception mapper and register it. You can customize responses.
Complete example is here
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable ex) {
return Response.status(500).entity(YOUR_RETURN_OBJ_HERE).build();
}
}
Since 2017 we've created a library that does this from annotations (making it fairly easy to, just like for requests/etc, to code this up by annotations).
it basically allows you to code error handling as follows:
#ErrorHandling(codeSpecific =
{
#ErrorCodes( codes = {401}, generate = UnAuthorizedException.class),
#ErrorCodes( codes = {403}, generate = ForbiddenException.class),
#ErrorCodes( codes = {404}, generate = UnknownItemException.class),
},
defaultException = ClassLevelDefaultException.class
)
interface GitHub {
#ErrorHandling(codeSpecific =
{
#ErrorCodes( codes = {404}, generate = NonExistentRepoException.class),
#ErrorCodes( codes = {502, 503, 504}, generate = RetryAfterCertainTimeException.class),
},
defaultException = FailedToGetContributorsException.class
)
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
You can find it in the OpenFeign organisation:
https://github.com/OpenFeign/feign-annotation-error-decoder
disclaimer: I'm a contributor to feign and the main dev for that error decoder.
What we do is as follows:
Share common jar which contains exceptions with both microservices.
1.) In microservices A convert exception to a DTO class lets say ErrorInfo.
Which will contain all the attributes of your custom exception with a String exceptionType, which will contain exception class name.
2.) When it is received at microservice B it will be handled by ErrorDecoder in microservice B and It will try to create an exception object from exceptionType as below:
#Override
public Exception decode(String methodKey, Response response) {
ErrorInfo errorInfo = objectMapper.readValue(details, ErrorInfo.class);
Class exceptionClass;
Exception decodedException;
try {
exceptionClass = Class.forName(errorInfo.getExceptionType());
decodedException = (Exception) exceptionClass.newInstance();
return decodedException;
}
catch (ClassNotFoundException e) {
return new PlatformExecutionException(details, errorInfo);
}
return defaultErrorDecoder.decode(methodKey, response);
}

Jersey custom ExceptionMapper not used if exception occurs when processing request body

I'm facing a tricky behavior from my REST resource.
The exposed method is expecting a complex json object :
#Path(RestURIConstant.NOTIFICATION_ROOT_URI)
#Component
#Scope("request")
public class NotificationResource implements RestURIConstant {
/** Notification service. */
#Autowired
private INotificationService notificationService;
#Path(RestURIConstant.COMPLEMENT_URI)
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response processNotification(final EventDTO event)
throws BusinessException, TechnicalException {
checkParameters(event);
notificationService.processEvent(event);
return Response.ok().build();
}
}
EventDTO has two enum fields : notificationType and eventType :
public class EventDTO {
private ENotificationType notificationType;
private EEventType eventType;
private String eventDate;
private String userName;
//... getters, setters
}
What I want is to map exception from any kind of data validation error to get at the end a json response with an error code and error message. And after following jax-rs jersey: Exception Mapping for Enum bound FormParam :
So for the ENoticationType I wrote :
public enum ENotificationEventType {
RULE,
ALARM,
ACK,
INFO;
#JsonCreator
public static ENotificationEventType fromString(final String typeCode)
throws BusinessException {
if (typeCode == null) {
throw new BusinessException(ValidationCode.VALUE_REQUIRED, "type");
}
try {
return valueOf(typeCode);
} catch (final IllegalArgumentException iae) {
throw new BusinessException(ValidationCode.UNSUPPORTED_VALUE, "type", typeCode, Arrays.toString(values()));
}
}
}
And for the Mapper I wrote :
#Provider
#Singleton
public class BusinessExceptionMapper implements ExceptionMapper<BusinessException> {
#Override
public Response toResponse(final BusinessException exception) {
Status status = Status.INTERNAL_SERVER_ERROR;
// If a validationCode error = unsupported version => CODE 410
if (exception.getErrorCode().equals(ValidationCode.UNSUPPORTED_API_VERSION)) {
status = Status.GONE;
} else if (exception.getErrorCode().getClass().isAssignableFrom(ValidationCode.class)) {
// If a validationCode error then BAD_REQUEST (400) HTTP
status = Status.BAD_REQUEST;
} else if (exception.getErrorCode().getClass().isAssignableFrom(NotFoundCode.class)) { // CODE 404
status = Status.NOT_FOUND;
} else if (exception.getErrorCode().getClass().isAssignableFrom(SecurityCode.class)) { // CODE 401
status = Status.UNAUTHORIZED;
} else if (exception.getErrorCode().getClass().isAssignableFrom(AdminSecurityCode.class)) { // CODE 401
status = Status.UNAUTHORIZED;
}
return Response.status(status).type(MediaType.APPLICATION_JSON)
.entity(ErrorMessageHelper.createErrorMessageHelper(
exception.getErrorCode(), exception.getMessage()))
.build();
}
And my application-context contains <context:component-scan base-package=" com.technicolor.hovis.backend.rest, com.technicolor.hovis.admin.rest" />
I already read several answers to questions relative to Exception mapping in jersey but in my case, it's not that the mapping is not recognized but that it's not applied in all cases :
the exceptions thrown by checkParameters are mapped and the result is as expected
but if an invalid enum is sent, the #JsonCreator method is called, throw the same type of exception but this one is not mapped as expected.
So The response looks like :
<data contentType="text/plain;charset=UTF-8" contentLength="176">
<![CDATA[Unsupported type value : 'ALARN'. Expected values are [RULE, ALARM, ACK, INFO] (through reference chain: EventDTO["type"])]]>
</data>
And not the expected :
{
"code": 6,
"message": "Unsupported type value : 'ALARN'. Expected values are [RULE, ALARM, ACK, INFO]"
}
Any idea ?
Cyril
I would try changing the BusinessExceptionMapper to:
public class BusinessExceptionMapper implements ExceptionMapper<Exception>
If I understand correctly, the problem is when deserializng the parameter you except, in that case an IOException will be thrown and not BusinessException which I am guessing is your custom notification. So you can either extend ExceptionMapper<Exception> or ExceptionMapper<IOException>
I noticed that when I throw a checked exception in JSONCreator (just like you did), Jackson catches it and wraps it in IllegalArgumentException, so IllegalArgumentException ends up in ExceptionMapper.
Maybe this was cause of your problems?

Categories

Resources