1

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"]>
Blink
  • 1,408
  • 13
  • 21

2 Answers2

1

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.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Thanks Artem. Please take a look at my UPDATE. – Blink Sep 01 '20 at 19:53
  • What you show there is not a ` body` of the `ResponseEntity`. All those are just HTTP headers and status `200 OK`. No body in that HTTP response as far as I see. – Artem Bilan Sep 01 '20 at 20:24
  • That's a copy of the whole payload from the IntelliJ debugger. It doesn't show the body as it's null. I could post an image if needed... – Blink Sep 01 '20 at 21:32
  • What does body have to be then? I thought null means you still have to retry... – Artem Bilan Sep 01 '20 at 23:59
  • Please take a look at the UPDATE again, I added the postman response. Can it be that it has something to do with the Transfer-Encoding:"chunked"? Maybe by using callback it's not properly "chunked" and the remaining chunks are not requested? – Blink Sep 02 '20 at 07:27
  • Hi Artem, any idea what I might be doing wrong? Thanks. – Blink Sep 04 '20 at 13:25
  • I don't know how that is possible because the further logic in the `HttpRequestExecutingMessageHandler` is really based on the `getBody()` from the `ResponseEntity`. Can you try with the `HttpComponentsClientHttpRequestFactory` for that `Http.outboundGateway()` instead of default one? – Artem Bilan Sep 04 '20 at 14:44
  • You mean like this: .requestFactory(new HttpComponentsClientHttpRequestFactory()) ? Unfortunately the body is still null... Any other idea? – Blink Sep 07 '20 at 08:01
  • So I'm just re-implementing it using simple handle with restTemplate and I'm still getting empty body as response so it definitely has nothing to do with the outbound gateway I guess... – Blink Sep 07 '20 at 14:56
  • Consult with your service provider what could be a reason for such an empty body from them. – Artem Bilan Sep 07 '20 at 15:05
  • Ok I just figured it out... I was using restTemplate with ResponseEntity instead of ResponseEntity. Now it works with restTemplate. Unfortunately the problem with outbound gateway is still unresolved :/ – Blink Sep 07 '20 at 15:18
  • Why don’t you make an expected response type as that String, too? – Artem Bilan Sep 07 '20 at 15:19
  • Ok this was almost solved I thought I tested it properly but I guess not really... The problem that I have now is that when the RuntimeException("Still analyzing") is thrown, the retry is not happening. Shall I be doing something different than throwing an Exception? – Blink Sep 25 '20 at 12:56
  • 1
    I guess you talk about your answer. The problem that those advices are wrapped to each other into a chain. So, the next advice is called withing the previous. Therefore to make your retry advice working after an exception from another one, that `advices.retryUntilRequestCompleteAdvice()` should be definitely first in the chain. – Artem Bilan Sep 25 '20 at 13:05
0

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");
        }
    };
}
Blink
  • 1,408
  • 13
  • 21