I have a message flow where I'd like to filter/drop the message if the result of a GET HTTP call (via http:outbound-gateway) is statusCode 200 - i.e case already exists. Put another way if the call gets a 404 (not found) the flow should continue. Ideally any other status codes or exceptions should go to errorHandler (as now)
I've tried calling a gateway that uses a chain with http:outbound-gateway with a request-handler-advice-chain, thinking I can trap the 404 and then just let things continue as if it was not an error, then test the statusCode 404 in 'filter'. However hitting a few issues
- The onFailureExpression expression doesn't detect statusCode 404, log shows HttpClientErrorException so presumably exception wasn't caught ?
- What value should I return from onFailureExpression ? null or payload or #payload ? ideally I want the payload before the HTTP call to remain as is.
- Do I need trapException true ?
- Surprised there isn't an easier way ? shame can't just declare on http:outbound-gateway httpStatusCodes that are allowed and treated as normal. I didn't think an errorChannel and error handler was the right way to go as I want the original flow to continue if 404 status code. So looks odd to put wanted regular behaviour in an error flow, and already have a higher level error handler.
2020-11-24 10:46:45,060 [main] DEBUG org.springframework.web.client.RestTemplate - GET request for "http://localhost:9095/ccacase/V1/case/challenge/CHG123456789" resulted in 404 (Not Found); invoking error handler
2020-11-24 10:46:45,134 [main] DEBUG org.springframework.integration.channel.DirectChannel - postSend (sent=true) on channel 'getChallengeCaseChannel', message: GenericMessage [payload=uk.gov.voa.integration.ccacasecheck.json.CdbEvent@624b3544[id=22,eventType=MIGRATE_CHALLENGE_CASE,asstRef=<null>,ccaCaseRef=CHG123456789,assessmentStatus=<null>,settlementCode=<null>,eventDateTime=2020-11-23T12:05,uarn=<null>,sourceActivityId=<null>,activityAction=<null>,additionalProperties={}], headers={replyChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@54f6b629, errorChannel=org.springframework.messaging.core.GenericMessagingTemplate$TemporaryReplyChannel@54f6b629, originalPayload=uk.gov.voa.integration.ccacasecheck.json.CdbEvent@624b3544[id=22,eventType=MIGRATE_CHALLENGE_CASE,asstRef=<null>,ccaCaseRef=CHG123456789,assessmentStatus=<null>,settlementCode=<null>,eventDateTime=2020-11-23T12:05,uarn=<null>,sourceActivityId=<null>,activityAction=<null>,additionalProperties={}], message_id=22, history=cdbEventsAMQPChannel,routeEventChain,migrateChallengeCaseEventChannel,migrateChallengeCaseEventChain,statusFlow,getChallengeCaseChannel, id=6d99e890-fc56-74a8-1187-fbc3457685c8, timestamp=1606214804576}]
2020-11-24 10:46:45,135 [main] DEBUG org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor - SpEL Expression evaluation failed with Exception.org.springframework.web.client.HttpClientErrorException: 404 Not Found
Caused by: org.springframework.messaging.MessagingException: failed to transform message headers; nested exception is org.springframework.messaging.MessageHandlingException: Expression evaluation failed: @statusFlow.exchange(#root).headers[http_statusCode]; nested exception is org.springframework.web.client.HttpClientErrorException: 404 Not Found
at org.springframework.integration.transformer.HeaderEnricher.transform(HeaderEnricher.java:128)
at org.springframework.integration.transformer.MessageTransformingHandler.handleRequestMessage(MessageTransformingHandler.java:89)
... 72 more
Caused by: org.springframework.messaging.MessageHandlingException: Expression evaluation failed: @statusFlow.exchange(#root).headers[http_statusCode]; nested exception is org.springframework.web.client.HttpClientErrorException: 404 Not Found
at org.springframework.integration.util.AbstractExpressionEvaluator.evaluateExpression(AbstractExpressionEvaluator.java:143)
at org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor.processMessage(ExpressionEvaluatingMessageProcessor.java:72)
at org.springframework.integration.transformer.support.ExpressionEvaluatingHeaderValueMessageProcessor.processMessage(ExpressionEvaluatingHeaderValueMessageProcessor.java:71)
at org.springframework.integration.transformer.HeaderEnricher.transform(HeaderEnricher.java:119)
... 73 more
Caused by: org.springframework.web.client.HttpClientErrorException: 404 Not Found
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
<int:chain id="migrateChallengeCaseEventChain" input-channel="migrateChallengeCaseEventChannel">
<int:header-enricher>
<int:header name="caseStatusCode" expression="@statusFlow.exchange(#root).headers[http_statusCode]" />
</int:header-enricher>
<!-- to check http_statusCode header and drop any message with found 200 statusCode -->
<int:filter ref="status200Filter"/>
<int:transformer ref="migrateChallengeCaseTransformer" />
<int:transformer ref="jsonValidationTransformer" />
<int:object-to-json-transformer object-mapper="springJacksonObjectMapper" />
<int:header-enricher>
<int:header name="contentType" value="application/json;charset=UTF-8" overwrite="true"/>
</int:header-enricher>
<int-amqp:outbound-channel-adapter
amqp-template="amqpTemplate" exchange-name="cdbEvents.exchange"
routing-key="migrateCdbCcaChallengeCase.request.queue.binding" />
</int:chain>
<int:gateway id="statusFlow" default-request-channel="getChallengeCaseChannel" />
<int:channel id="getChallengeCaseChannel" />
<!-- Call API to see if Case already exists, 200 status code we want to filter/drop message -->
<int:chain id="getChallengeCaseChain" input-channel="getChallengeCaseChannel">
<int-http:outbound-gateway id="httpOutboundGatewayChallengeCaseGet"
expected-response-type="java.lang.String"
http-method="GET" charset="UTF-8"
extract-request-payload="true"
request-factory="httpRequestFactory"
url="${mule.case.data.service.uri}/${case.challenge.subpath}/{ccaCaseRefValue}">
<int-http:uri-variable name="ccaCaseRefValue" expression="headers['originalPayload'].ccaCaseRef"/>
<int-http:request-handler-advice-chain>
<bean class="org.springframework.integration.handler.advice.ExpressionEvaluatingRequestHandlerAdvice">
<!-- If true, the result of evaluating the onFailureExpression will be returned as the result -->
<property name="returnFailureExpressionResult" value="true" />
<property name="onSuccessExpression" value="payload" />
<!-- If true, any exception will be caught and null returned. Default false -->
<property name="trapException" value="true" />
<!-- Set the expression to evaluate against the root message after a failed handler invocation. The exception is available as the variable #exception. Defaults to payload, if failureChannel is configured. -->
<property name="onFailureExpression" value="#exception.cause.statusCode == 404 ? payload : #exception"/>
<!-- Set the channel name to which to send the ErrorMessage after evaluating the failure expression. -->
<!-- <property name="failureChannel" ref="#headers['replyChannel']" /> -->
</bean>
</int-http:request-handler-advice-chain>
</int-http:outbound-gateway>
</int:chain>
Solution used was to implement a custom ErrorHandler that extends the default and overrides hasError
/**
* To be used with an 'int-http:outbound-gateway' to make a HTTP call and allow a statusCode 404
* response to be treated as normal (as well as a 200 statusCode).
* Normally a statusCode 404 would throw a
* org.springframework.web.client.HttpClientErrorException: 404 Not Found
* and expect the caller to deal with an error-channel.
* This allows processing to continue and subsequently test for a 404 response
* and route differently
*
*/
public class Allow404StatusCodeResponseHandler extends DefaultResponseErrorHandler {
/**
* OVERRIDEN
*
* Indicate whether the given response has any errors.
* <p>Implementations will typically inspect the
* {@link ClientHttpResponse#getStatusCode() HttpStatus} of the response.
* @param response the response to inspect
* @return {@code true} if the response indicates an error; {@code false} otherwise
* @throws IOException in case of I/O errors
*/
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
int rawStatusCode = response.getRawStatusCode();
return (rawStatusCode == 404 || rawStatusCode == 200) ? false : true; // 404 to be treated as normal and testable downstream
}
}
Then set this in int-http:outbound-gateway