1
votes

I'm using an API that works in 2 steps:

  1. It starts processing of a document in async way where it provides you an id that you use for step 2
  2. It provides an endpoint where you can get the results but only when they are ready. So basically it will always give you a 200 response with some details like the status of the processing.

So the question is how can I implement a custom "success" criteria for the HTTP outbound gateway. I would also like to combine it with a RetryAdvice which I already have implemented.

I've tried the following but first of all the message's payload that is provided in the HandleMessageAdvice is empty, and secondly the retry is not triggered:

.handle(Http.outboundGateway("https://northeurope.api.cognitive.microsoft.com/vision/v3" +
        ".0/read/analyzeResults/abc")
        .mappedRequestHeaders("Ocp-Apim-Subscription-Key")
        .httpMethod(HttpMethod.GET), c -> c.advice(this.advices.retryAdvice())
              .handleMessageAdvice(new AbstractHandleMessageAdvice() {
    @Override
    protected Object doInvoke(MethodInvocation invocation, Message<?> message) throws Throwable {
        String body = (String) message.getPayload();
        if (StringUtils.isEmpty(body))
            throw new RuntimeException("Still analyzing");
        JSONObject document = new JSONObject(body);
        if (document.has("analyzeResult"))
            return message;
        else
            throw new RuntimeException("Still analyzing");
    }
}))

I've found this answer from Artem from 4 years back but first of all I didn't find the reply channel method on the outbound gateway and secondly not sure if this scenario has already been improved in the newer version of Spring Integaration: http outbound retry with conditions (For checker condition).

UPDATE

Following Artem's suggestion I have the following:

.handle(Http.outboundGateway("https://northeurope.api.cognitive.microsoft.com/vision/v3" +
        ".0/read/analyzeResults/abc")
        .mappedRequestHeaders("Ocp-Apim-Subscription-Key")
        .httpMethod(HttpMethod.GET), c -> c.advice(advices.verifyReplySuccess())
        .advice(advices.retryUntilRequestCompleteAdvice()))

And the advice:

@Bean
public Advice verifyReplySuccess() {
    return new AbstractRequestHandlerAdvice() {
        @Override
        protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
            try {
                Object payload = ((MessageBuilder) callback.execute()).build().getPayload();
                String body = (String) ((ResponseEntity) payload).getBody();
                JSONObject document = new JSONObject(body);
                if (document.has("analyzeResult"))
                    return message;
            } catch (JSONException e) {
                throw new RuntimeException(e);
            }
            throw new RuntimeException("Still analyzing");
        }
    };
}

But now when I debug the doInvoke method, the body of the payload is null. It's strange as when I execute the same GET request using Postman, the body is correctly returned. Any idea?

The body from response using Postman looks like this:

{
    "status": "succeeded",
    "createdDateTime": "2020-09-01T10:55:52Z",
    "lastUpdatedDateTime": "2020-09-01T10:55:57Z",
    "analyzeResult": {
        "version": "3.0.0",
        "readResults": [
            {
                "page": 1,........

Here is the payload that I get from the outbound gateway using callback:

enter image description here

<200,[Transfer-Encoding:"chunked", Content-Type:"application/json; charset=utf-8", x-envoy-upstream-service-time:"27", CSP-Billing-Usage:"CognitiveServices.ComputerVision.Transaction=1", apim-request-id:"a503c72f-deae-4299-9e32-625d831cfd91", Strict-Transport-Security:"max-age=31536000; includeSubDomains; preload", x-content-type-options:"nosniff", Date:"Tue, 01 Sep 2020 19:48:36 GMT"]>
2

2 Answers

1
votes

There is indeed no request and reply channel options in Java DSL because you simply wrap that handle() into channel() configuration or just chain endpoints in the flow natural way and they are going to exchange messages using implicit direct channels in between. You can look into Java DSL IntegrationFlow as a <chain> in the XML configuration.

Your advice configuration is a bit wrong: you need declare your custom advice as a first in a chain, so when exception is thrown from there a retry one is going to handle it.

You should also consider to implement an AbstractRequestHandlerAdvice to align it with the RequestHandlerRetryAdvice logic.

You implement there a doInvoke(), call ExecutionCallback.execute() and analyze the result to return as is or throw a desired exception. A result of that call for HttpRequestExecutingMessageHandler is going to be an AbstractIntegrationMessageBuilder and probably a ResponseEntity as a payload to check for your further logic.

0
votes

Following Artem's suggestion I came up with the following (additional trick was to set the expectedResponseType to String as otherwise using the ResponseEntity the body was empty):

.handle(Http.outboundGateway("https://northeurope.api.cognitive.microsoft.com/vision/v3" +
        ".0/read/analyzeResults/abc")
        .mappedRequestHeaders("Ocp-Apim-Subscription-Key")
        .httpMethod(HttpMethod.GET).expectedResponseType(String.class),
        c -> c.advice(advices.retryUntilRequestCompleteAdvice())
              .advice(advices.verifyReplySuccess()))

And the advice:

@Bean
public Advice verifyReplySuccess() {
    return new AbstractRequestHandlerAdvice() {
        @Override
        protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) {
            Object payload = ((MessageBuilder) callback.execute()).build().getPayload();
            if (((String) payload).contains("analyzeResult"))
                return payload;
            else
                throw new RuntimeException("Still analyzing");
        }
    };
}