0

Basically my use-case is to retry an http -request when a 401 occurs in an HttpOutboundGateway request . The request comes from a jms broker into the integration flow .

 @Bean
  IntegrationFlow bank2wallet(ConnectionFactory jmsConnectionFactory,
      MessageHandler creditWalletHttpGateway) {
    return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(jmsConnectionFactory)
        .destination(cp.getTransactionIn()))
        .<String, CreditRequest>transform(
            request -> new Gson().fromJson(request, CreditRequest.class))

        .enrichHeaders((headerEnricherSpec -> {
          // Todo get token from cache
          headerEnricherSpec.header(HttpHeaders.AUTHORIZATION, String.join(" ", "Bearer", ""));
          headerEnricherSpec.header(HttpHeaders.ACCEPT, "application/json");
          headerEnricherSpec.header(HttpHeaders.CONTENT_TYPE, "application/json");
        }))
        .handle(creditWalletHttpGateway, (e) -> e.advice(retryAdvice()))
        .get();
  }


@Bean
  MessageHandler creditWalletHttpGateway( @Value("${api.base.uri:https:/localhost/v3/sync}") URI uri) {
    HttpRequestExecutingMessageHandler httpHandler = new HttpRequestExecutingMessageHandler(uri);
    httpHandler.setExpectedResponseType(CreditResponse.class);
    httpHandler.setHttpMethod(HttpMethod.POST);
    return httpHandler;
  }

 @Bean
  RequestHandlerRetryAdvice retryAdvice() {
    RequestHandlerRetryAdvice requestHandlerRetryAdvice = new RequestHandlerRetryAdvice();
    requestHandlerRetryAdvice.setRecoveryCallback(errorMessageSendingRecoverer());
    return requestHandlerRetryAdvice;
  }

  @Bean
  ErrorMessageSendingRecoverer errorMessageSendingRecoverer() {
    return new ErrorMessageSendingRecoverer(recoveryChannel());
  }

  @Bean
  MessageChannel recoveryChannel() {
    return new DirectChannel();
  }

  @Bean
  MessageChannel retryChannel() {
    return new DirectChannel();
  }

  @Bean
  IntegrationFlow handleRecovery() {
    return IntegrationFlows.from("recoveryChannel")
        .log(Level.ERROR, "error", m -> m.getPayload())
        .<RuntimeException>handle((message) -> {

          
          MessagingException exception = (MessagingException) message.getPayload();
          Message<CreditRequest> originalCreditRequest = (Message<CreditRequest>) exception.getFailedMessage();
          // String token = gateway.getToken(configProperties);
          String token = UUID.randomUUID().toString();
          Message<CreditRequest> c = MessageBuilder.fromMessage(originalCreditRequest)
              .setHeader(ApiConstants.AUTHORIZATION, String.join(" ", "Bearer", token))
              .copyHeaders(message.getHeaders())
              .build();

          retryChannel().send(c);

        })
        .get();
  }

  @Bean
  IntegrationFlow creditRequestFlow() {

    return IntegrationFlows.from(retryChannel())
        .log(Level.INFO, "info", m ->  m.getPayload())
        .handle(Http.outboundGateway("https://localhost/v3/sync")
        .httpMethod(HttpMethod.POST)
        .expectedResponseType(CreditResponse.class))
        .get();

  }

Headers are enriched with the appropriate http header, Then i have an advice that retries the request with default simple policy , the issue with RequestHandlerAdvice approach is that it defaults the Exception Message in the handleRecovery Flow to a none HttpException class (MessageException), hence i cant check for HttpStatus code to re-route the message. So my question is basically how do i design a flow that retries a HttpOutBoundRequest based on HttpStatus 401.

Johnson Eyo
  • 113
  • 1
  • 9

1 Answers1

0

I resolved the issue by introducing a gateway to make the outbound http call and manage it using a recursive manner

@MessagingGateway
public interface B2WGateway {

    /**
     *
     * @param message
     * @return
     */
    @Gateway(requestChannel = "credit.input")
    CreditResponse bankToWallet(Message<CreditRequest> message);


}

Then isolated the http outbound integration flow

 @Bean
  IntegrationFlow credit() {

    return f -> f.log(Level.INFO, "info", m -> m.getHeaders())
        .handle(Http.outboundGateway(configProperties.getBankToWalletUrl())
            .httpMethod(HttpMethod.POST)
            .expectedResponseType(CreditResponse.class)
        .errorHandler(new ResponseErrorHandler() {
          @Override
          public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
            return clientHttpResponse.getStatusCode().equals(HttpStatus.UNAUTHORIZED);
          }

          @Override
          public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
              throw new AuthenticationRequiredException("Authentication Required");
          }
        }));
  }

Then resolve the message the handleRecovery to send the message after obtaining a token refresh

@Bean
  IntegrationFlow handleRecovery() {
    return IntegrationFlows.from("recoveryChannel")
        .log(Level.ERROR, "error", m -> m.getPayload())
        .<RuntimeException>handle((p, h) -> {
          MessageHandlingExpressionEvaluatingAdviceException exception = (MessageHandlingExpressionEvaluatingAdviceException) p;
          Message<CreditRequest> originalCreditRequest = (Message<CreditRequest>) exception
              .getFailedMessage();
          // String token = gateway.getToken(configProperties);
          String token = UUID.randomUUID().toString();
          Message<CreditRequest> c = MessageBuilder.fromMessage(originalCreditRequest)
              .setHeader(ApiConstants.AUTHORIZATION, String.join(" ", "Bearer", token))
              .copyHeaders(h)
              .build();
          return c;
        })
        .channel("credit.input")
        .get();
  }

Then modified the inception of the flow to use the gateway service and expression advice.

 @Bean
  IntegrationFlow bank2wallet(ConnectionFactory jmsConnectionFactory) {
    return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(jmsConnectionFactory)
        .destination(cp.getTransactionIn()))
        .<String, CreditRequest>transform(
            request -> new Gson().fromJson(request, CreditRequest.class))

        .enrichHeaders((headerEnricherSpec -> {
          // Todo get token from cache
          headerEnricherSpec.header(HttpHeaders.AUTHORIZATION, String.join(" ", "Bearer", ""));
          headerEnricherSpec.header(HttpHeaders.ACCEPT, "application/json");
          headerEnricherSpec.header(HttpHeaders.CONTENT_TYPE, "application/json");
        }))
        .handle((GenericHandler<CreditRequest>) (creditRequest, headers) -> gateway
            .bankToWallet(MessageBuilder.withPayload(creditRequest)
                .copyHeaders(headers)
                .build()), (e) -> e.advice(retryAdvice()))
        .get();
  }

Inspiration from Spring Integration - Manage 401 Error in http outbound adapter call

Johnson Eyo
  • 113
  • 1
  • 9