I have a following flow implemented in Spring Integration DSL:
Take feed from HTTP
Enrich errorChannel header (point of handling all exception downstream here).
Transform message to be a Collection
Split Collection into sepearate messages
Send each message to next processing channels
#Bean
public IntegrationFlow inboundHttpFlow(
Puller puller,
HeaderEnricher errorHandlingChannelHeaderEnricher,
FeedTransformer feedTransformer,
MessageChannel outputFeedIntegrationChannel
) {
final Consumer<SourcePollingChannelAdapterSpec> pollingSpec = spec ->
spec
.poller(Pollers.cron(SCHEDULE_EVERY_HALF_MINUTE)
.errorChannel(INBOUND_ERROR_CHANNEL));
return IntegrationFlows
.from(puller, pollingSpec)
.enrichHeaders(errorHandlingChannelHeaderEnricher)
.transform(feedTransformer)
.split()
.channel(outputFeedIntegrationChannel)
.get();
}
Where my errorHandlingChannelHeaderEnricher is:
#Component
public class ErrorHandlingChannelHeaderEnricher implements HeaderEnricher {
#Override
public void accept(HeaderEnricherSpec spec) {
spec.header(
MessageHeaders.ERROR_CHANNEL,
INBOUND_ERROR_CHANNEL,
true
);
}
}
When feedTransformer throws an exception in working app then it goes to set errorChannel as expected. But I don't know how to write a test to test if thrown exception goes to errorChannel defined in header?
When I'm trying to simulate it in test given way, it doesn't work because exception is thrown back into caller instead of errorChannel:
// given
Throwable transformerException = new IllegalStateException();
when(feedTransformerMock.apply(any())).thenThrow(transformerException);
// when
var testFeedMessage = MessageBuilder
.withPayload(pullerResult)
.build();
inboundHttpFlow.getInputChannel().send(testFeedMessage); // excetpion returns to caller here
// then
verify(errorHandlerSpy).accept(transformerException);
And exception is typical:
org.springframework.integration.transformer.MessageTransformationException: Failed to transform Message; nested exception is org.springframework.messaging.MessageHandlingException: nested exception is java.lang.IllegalStateException, failedMessage=GenericMessage [payload=test-payload, headers={errorChannel=inboundErrorChannel, id=f77a6a01-9bca-5af3-8352-7edb4c5e94b0, timestamp=1556019833867}]
, failedMessage=GenericMessage [payload=test-payload, headers={errorChannel=inboundErrorChannel, id=f77a6a01-9bca-5af3-8352-7edb4c5e94b0, timestamp=1556019833867}]
I assume that because of DirectChannel and lack of poller in this test example in compare to real flow (with poller).
Is there any way to simulate that throwing exception and checking if it really goes to errorChannel defined in header?
It's not clear what you are trying to test.
Why do you need to test the framework?
You don't need to enrich the headers since you already have an error channel on the poller.
You are correct; sending to a DirectChannel will result in the exception being thrown to the caller.
If you really want to test the framework, mock the Puller instead of sending to the input channel.
Related
Let's take the following consumer method for a RabbitMQ queue. Ths payload received from the queue is in JSON format, so I register a bean returning a Jackson2JsonMessageConverter. This basically works fine.
Now I'd like to add a validation of the QueueResponse object, similar to when using Jackson in a #RestController, e.g. if the JSON field does not exist or contains an invalid value. In this case, I'd like the code to execute the catch block, i.e. throwing an AmqpRejectAndDontRequeueException.
Thus, I added #Payload #Valid as described in the documentation. But I don't know what to do in the validationErrorHandler method. I don't understand the return statement from the documentation. What would I need to do there to reach the catch block?
#RabbitListener(queues = QUEUE_NAME, messageConverter = "jackson2MessageConverter", errorHandler="validationErrorHandler")
public void consume(#Payload #Valid QueueResponse queueResponse) {
try {
processMessage(queueResponse);
} catch (Exception e) {
throw new AmqpRejectAndDontRequeueException(e.getMessage());
}
}
#Bean
public MessageConverter jackson2MessageConverter() {
return new Jackson2JsonMessageConverter(objectMapper);
}
// Not sure what to do here...
#Bean
public RabbitListenerErrorHandler validationErrorHandler() {
return (m, e) -> {
...
};
}
If the error handler exits normally, the message will be acknowledged (discarded).
If the error handler throws an exception, the message will either be requeued (and redelivered) or discarded (and optionally sent to a dead letter queue), depending on the exception type, container properties, and queue arguments.
Basically what I do in the RabbitListenerErrorHandler is the following:
check how many times I requeued a message by looking into the count property in the x-death header
then decide to either requeue the message by throwing an AmqpRejectAndDontRequeueException or not. In this last case, instead of just discard the message, send it to a parking-lot exchange (bound to a queue with no consumer, in my case these queues are monitored externally) with additional information (for instance the stack trace of the last failure)
On my quarkus rest project i have a restclient that uses mutiny:
#Path("/")
#RegisterRestClient(configKey = "my-api")
#RegisterClientHeaders
#RegisterProvider(MyExceptionMapper.class)
public interface MyClient {
#POST
#Path("path")
Uni<MyBean> get(String body);
}
I wanna handle propery non 2XX httpError so i have made my ExceptionMaper
public class MyExceptionMapper implements ResponseExceptionMapper<MyException> {
#Override
public MyException toThrowable(Response response) {
//TODO
return new MyException();
}
}
a bad call on the client shows that MyExceptionMapper handle the response but the exception raises and does not became a failure on my Uni Client response object
Uni<MyBean> bean = myClient.get("") // i do not have a failure in case of 4XX http
.onFailure().invoke(fail -> System.out.println("how can i get here?"));
Am i using mutiny on a rest client in the wrong way?
Thanks
UPDATE
ok i forgot to add the dependency quarkus-rest-client-mutiny, adding this i notice 2 things,
i still pass through Myexceptionmapper
i also produce a Uni.failure, but the exception into the failure is not the custom exception i created into MyExceptionmapper but a RestEasyWebApplicationException
Failure : org.jboss.resteasy.client.exception.ResteasyWebApplicationException: Unknown error, status code 400
at org.jboss.resteasy.client.exception.WebApplicationExceptionWrapper.wrap(WebApplicationExceptionWrapper.java:107)
at org.jboss.resteasy.microprofile.client.DefaultResponseExceptionMapper.toThrowable(DefaultResponseExceptionMapper.java:21)
Does the ExceptionMapper becomes useless in this context?
I think this is a bug in quarkus-rest-client-mutiny. I created an Github issue based on your findings.
It will work as you expect if you switch to quarkus-rest-client-reactive
I use a global exception handler in my spring rest app and I would like to hide jdbc exceptions, but it doesn't work as expected. I shut down the database to force a connection exception and I can see the following exception in the log and I receive the default spring error response, but not the one I defined in the exception handler
java.lang.IllegalStateException: Could not resolve parameter [1] in public org.springframework.http.ResponseEntity<java.lang.Object> ...
throws java.io.IOException: No suitable resolver
Here's the code.
#ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({JDBCConnectionException.class})
public ResponseEntity<Object> dbError(JDBCConnectionException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) throws IOException
{
Map<String,Object> body = new HashMap<>();
body.put("errorId",Long.valueOf(201));
body.put("state",HttpStatus.SERVICE_UNAVAILABLE.value());
body.put("message", "internal failure");
body.put("time", new Date().toString());
return new ResponseEntity<>(body, headers, status);
}
Hope you can help me.
I've found the failure...spring can not resolve these two parameters, for that kind of exception.
HttpHeaders headers,
HttpStatus status
It's obviouse the exception mentioned paramter [1]
java.lang.IllegalStateException: Could not resolve parameter [1] in public org.springframework.http.ResponseEntity<java.lang.Object> ...
throws java.io.IOException: No suitable resolver
I removed these two parameters and the exception handler handles the exception.
This code works now
#ExceptionHandler(JDBCConnectionException.class)
public ResponseEntity<Object> dbError(Exception ex,
WebRequest request)
{
Map<String,Object> body = new HashMap<>();
body.put("errorId",Long.valueOf(201));
body.put("state",HttpStatus.SERVICE_UNAVAILABLE.value());
body.put("message", "internal failure");
body.put("time", new Date().toString());
return new ResponseEntity<Object>(body, HttpStatus.INTERNAL_SERVER_ERROR);
}
As the annotation implies #ControllerAdvice is used as an extension on your REST endpoints, these exception handlers will process the exception for the REST API and does not influence how it is logged in the console. It will instead determine how exceptions are reported to your end users and allow you to write concise error messages without leaking information about your program.
If you want to completely catch an exception and not only for the REST API take a look at this blog.
However I would not recommend doing this since this will greatly reduce the information available to you as a developer, this information cannot be seen by end users and therefore the REST API custom exception should provide enough abstraction.
I hope this helps you.
I have a spring integration flow defined in the flow DSL syntax. One of my handlers is a Webflux.outboundGateway. When the remote URI is not accessible, an exception is thrown and sent to the "errorChannel". I'm trying to have the flow to retry, but so far with no success (the call is never retried). Here is what my configuration looks like:
#Bean
public IntegrationFlow retriableFlow() {
return IntegrationFlows
.from(...)
.handle(
WebFlux.outboundGateway(m ->
UriComponentsBuilder.fromUriString(remoteGateway + "/foo/bar")
.build()
.toUri(), webClient)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class)
.replyPayloadToFlux(true), e -> e.advice(retryAdvice())
)
// [ ... ]
.get();
}
#Bean
public Advice retryAdvice() {
RequestHandlerRetryAdvice advice = new RequestHandlerRetryAdvice();
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy retryPolicy = new ExponentialBackOffPolicy();
retryPolicy.setInitialInterval(1000);
retryPolicy.setMaxInterval(20000);
retryTemplate.setBackOffPolicy(retryPolicy);
advice.setRetryTemplate(retryTemplate);
return advice;
}
Should I be using something different than the RequestHandlerRetryAdvice? If so, what should it be?
Webflux is, by definition, async, which means the Mono (reply) is satisfied asynchronously when the request completes/fails, not on the calling thread. Hence the advice won't help because the "send" part of the request is always successful.
You would have to perform retries via a flow on the error channel (assigned somewhere near the start of the flow). With, perhaps, some header indicating how many times you have retried.
The ErrorMessage has properties failedMessage and cause; you can resend the failedMessage.
You could turn off async so the calling thread blocks, but that really defeats the whole purpose of using WebFlux.
I have a simple TCP connection factory implemented in Spring Integration:
#Bean
#ServiceActivator(inputChannel = "toTcpChannel")
public TcpSendingMessageHandler tcpOutClient() throws Exception {
TcpSendingMessageHandler sender = new TcpSendingMessageHandler();
sender.setConnectionFactory(clientFactory());
sender.setClientMode(false);
sender.afterPropertiesSet();
return sender;
}
#Bean
public AbstractClientConnectionFactory clientFactory() {
final TcpNioClientConnectionFactory factory = new TcpNioClientConnectionFactory(tcpHost, tcpPort);
factory.setSingleUse(true);
return factory;
}
#EventListener
public void handleTcpConnectionOpenEvent(TcpConnectionOpenEvent event) throws Exception {
LOGGER.info("TCP connection OPEN event: {}", event.getConnectionId());
// HERE I would like to have "myCustomID" header here.
}
I am looking for getting the custom ID that I am providing via Gateway in the produced TcpConnectionOpenEvent (or similar via interceptors)
#Gateway(requestChannel="toTcpChannel")
public void sendToTcp(#Payload String message, #Header("myCustomID") Long myCustomID);
I know this is an event not a message but I do know how to get the Connection ID that I will receive in the input channel in any other way.
I am creating a type of hash map of my custom id – connection id.
I cannot use a custom correlation via aggregator because the response message will not contain any information about the previously sent message. Any suggestions will be welcome.
Oh! I see. Not sure what you are going to do from your custom TcpSendingMessageHandler, but as far as ApplicationEventPublisher is single-threaded, you can store the connectionId in the ThreadLocal variable and obtain it from there after send operation.