0

I'm using Spring-integration in my project and the pattern used is scatter-gather. Here three parallel processes are being carried out. The flow2 is a outbound gateway method and if that service is down then I want to handle the Httpstatus exception and want to send null. Actually if that service is down then the whole flow is getting stopped. But I want to handle that exception and send null and then want to continue with the aggregate method and end the flow.

Below is the code - -

//Config file

 @Configuration
        public class IntegrationConfiguration {
          @Autowired LionsServiceImpl lionsService;
        
          long dbId = new SequenceGenerator().nextId();
      //   Main flow
      @Bean
  public IntegrationFlow flow() {
    return flow ->
        flow.handle(validatorService, "validateRequest")
            .split()
            .channel(c -> c.executor(Executors.newCachedThreadPool()))
            .scatterGather(
                scatterer ->
                    scatterer
                        .applySequence(true)
                        .recipientFlow(flow1())
                        .recipientFlow(flow2())
                        .recipientFlow(flow3()),
                gatherer ->
                    gatherer
                        .releaseLockBeforeSend(true)
                        .releaseStrategy(group -> group.size() == 2))
            .aggregate(prepareSomeRequest())
            .to(getDec());
  }

  //   Saving the request to the database
  @Bean
  public IntegrationFlow flow1() {
    return integrationFlowDefinition ->
        integrationFlowDefinition
            .channel(c -> c.executor(Executors.newCachedThreadPool()))
            .handle(
                (payload, header) -> {
                  ObjectMapper mapper = new ObjectMapper();
                  try {
                    String jsonString = mapper.writeValueAsString(payload);
                    JsonNode request = mapper.readTree(jsonString);
                    JsonNode csID = request.get("ApplicationDetails").get("CustomerId");
                    int customerID = mapper.treeToValue(csID, Integer.class);

                    return lionService.saveRequest(
                        payload,
                        String.valueOf(dbId),
                        customerID,
                        ((SourceSystem) Objects.requireNonNull(header.get("sourceSystem")))
                            .getSourceSystemCode());
                  } catch (JsonProcessingException e) {
                    throw new RuntimeException(e);
                  }
                }
                )
            .nullChannel();
  }

  // 
  @Bean
  public IntegrationFlow flow3() {
    return integrationFlowDefinition ->
        integrationFlowDefinition
            .channel(c -> c.executor(Executors.newCachedThreadPool()))
            .transform(
                message ->
                    loansService.someMethod(
                        (LionRequest) message));
  }

 //Here I'm calling a service through HTTPOUTBOUNDGATEWAY and if that called service is down then it throws HTTP STAtus error so I want to handle that and want to send null from this flow.
  @Bean
  public IntegrationFlow flow2() {
    return integrationFlowDefinition ->
        integrationFlowDefinition
            .channel(c -> c.executor(Executors.newCachedThreadPool()))
            .handle(
                (payload, header) ->
                    loansService.someMethod2(
                        (LionRequest) payload,
                        (SourceSystem) (Objects.requireNonNull(header.get("sourceSystem")))))
            .handle(
                Http.outboundGateway(someurl)
                    .httpMethod(HttpMethod.POST)
                    .expectedResponseType(String.class)
                       );
  }


  @Bean
  public IntegrationFlow getDec() {
    return flow ->
        flow.handle(
            Http.outboundGateway(ServiceURL)
                .httpMethod(HttpMethod.POST)
                .expectedResponseType(CrResponse.class));
  }


  @Bean
  public MessageChannel replyChannel() {
    return MessageChannels.executor("output-flow", outputExecutor()).get();
  }

  @Bean
  public ThreadPoolTaskExecutor outputExecutor() {
    ThreadPoolTaskExecutor pool = new ThreadPoolTaskExecutor();
    pool.setCorePoolSize(4);
    pool.setMaxPoolSize(4);
    return pool;
  }


//here I want to take out null from messages which is sent by flow2 if the called service is down and then I want to send null to someMethod2 method.

  public MessageGroupProcessor prepareSomeRequest() {
    return group -> {
      String cData;
      Object CDReq;

      List<Message<?>> messages = group.streamMessages().collect(Collectors.toList());

      ArrayList<Object> payloads = (ArrayList<Object>) messages.get(0).getPayload();

      if (payloads.get(0).toString().contains("tribv")) {
        cData= payloads.get(0).toString();
        logger.atInfo().log("Customer data from Data Sourcing Service : " + cData);
        CDReq= payloads.get(1);
      } else {
        cData= payloads.get(1).toString();
        logger.atInfo().log("Customer data from Data Sourcing Service : " + cData);
        CDReq = payloads.get(0);
      }

      Object fReq =
          lionservice.someMethod2(cData, CDReq);

      SomeRequest somreq= new SomeRequest();

      ObjectMapper mapper = new ObjectMapper();

      JsonNode req = mapper.valueToTree(freq);
      creditDecisionRequest.setfsfgg(req);
      creditDecisionRequest.setR("234565432");
      creditDecisionRequest.setD(String.valueOf(dbId));
      creditDecisionRequest.setCID("33333333");
      creditDecisionRequest.setSourceSystemCode(SourceSystem.ONE.getSourceSystemCode());

      return somreq;
    };
  }

Gateway

    @Gateway(requestChannel = "flow.input")
  void processLionRequest(
      @Payload Message lionRequest, @Header("sourceSystem") SourceSystem sourceSystem);

Can I use something like .errorHandler() in the outboundgateway? But how do I use that?

 @Bean
  public IntegrationFlow flow2() {
    return integrationFlowDefinition ->
        integrationFlowDefinition
            .channel(c -> c.executor(Executors.newCachedThreadPool()))
            .handle(
                (payload, header) ->
                    lionService.prepareSomeRequest(
                        (LionRequest) payload,
                        (SourceSystem) (Objects.requireNonNull(header.get("sourceSystem")))))
            .handle(
                Http.outboundGateway(someurl)
                    .httpMethod(HttpMethod.POST)
                    .expectedResponseType(String.class),
                c -> c.advice(expressionAdvice()));
  }



    @Bean
  public Advice expressionAdvice() {
    ExpressionEvaluatingRequestHandlerAdvice advice =
        new ExpressionEvaluatingRequestHandlerAdvice();
    advice.setSuccessChannelName("success.input");
    advice.setOnSuccessExpressionString("payload + ' was successful'");
    advice.setFailureChannelName("failure.input");
    advice.setOnFailureExpressionString("Failed");
    advice.setReturnFailureExpressionResult(true);
    advice.setTrapException(true);
    return advice;
  }

  @Bean
  public IntegrationFlow success() {
    return f -> f.handle(System.out::println);
  }

  @Bean
  public IntegrationFlow failure() {
    return f -> f.handle(System.out::println);
  }

  public String adviceOnFailure() {
    return "Failed";
  }

I'm doing something like that but getting error as below -

[GenericMessage [payload=[org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'Failed' cannot be found on object of type 'org.springframework.messaging.support.GenericMessage' - maybe not public or not valid?], headers={sequenceNumber=1, sequenceDetails=[[f596d446-9816-e13b-240c-f365338a5eb4, 1, 1]], replyChannel=nullChannel, sourceSystem=ONE, sequenceSize=1, correlationId=f596d446-9816-e13b-240c-f365338a5eb4, id=5592b1da-19fd-0567-c728-71b47d46b2d5, timestamp=1658382273446}]]

I want String "Failed" to be in the message, so that I can take that string and process further. Kindly help.

1 Answers1

0

There is no null payload concept in messaging, so even if you handle an error, you definitely cannot return null as a reply from that Outbound Gateway call.

See Request Handler Advice pattern in the framework, in particular an ExpressionEvaluatingRequestHandlerAdvice implementation. It does handle error for the specific message handler and may return a compensation reply which you then can aggregator and process respectively.

The doc is here: https://docs.spring.io/spring-integration/docs/current/reference/html/messaging-endpoints.html#message-handler-advice-chain

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • It's nothing like I need to send null only. I can return some String as well if the exception occurs then I can have a if else statement inside aggregator() method. So how can I just handle that exception and send a String or flag type just to indicate that exception has occurred? – Somnath Mukherjee Jul 18 '22 at 17:43
  • I wonder why the doc I pointed in my answer doesn't meat your requirements... – Artem Bilan Jul 18 '22 at 17:57
  • Sorry but not able relate with my use case by seeing the documentation. – Somnath Mukherjee Jul 18 '22 at 18:04
  • You want something like `try...catch` around your ` Http.outboundGateway()`. So, that is exactly what we do with the `AbstractRequestHandlerAdvice`. And the mentioned `ExpressionEvaluatingRequestHandlerAdvice` is for your request. If you want, you can open its source code to determine how it works. – Artem Bilan Jul 18 '22 at 18:12
  • I'm using the same as u suggested. Kindly see the updated question and please suggest for the error that I'm getting here. – Somnath Mukherjee Jul 18 '22 at 18:43
  • With that `adviceOnFailure()` you essentially make that String as a service. You probably want to calm that method against every message instead: `handle((p, h) -> return adviceOnFailure();)` – Artem Bilan Jul 18 '22 at 19:13
  • In case of httpoutbound success the flow is delegating to aggregate() but in case of failure it's exiting from the flow. Here I want to send some string in the payload and want to continue with the full flow i.e., aggregate(someMethod()).to(someMethod2()). Kindly advice. – Somnath Mukherjee Jul 18 '22 at 19:37
  • See JavaDocs of the class I advised for your. It has this option `returnFailureExpressionResult`. – Artem Bilan Jul 18 '22 at 19:39
  • Hi @Artem, I have used the same as per your advice, but I'm not getting desired message. Kindly see my edited POST , there I've put the code. – Somnath Mukherjee Jul 21 '22 at 05:57