2

I have two threads and each of the method in the thread throws an exception. How do I get all the errors that's thrown in each of the thread? In this code, the error channel just catch one of the errors. Basically my goal is to catch all the errors and send them to the caller (a rest controller). Any help will be appreciated. Thanks.

Integration.java

public IntegrationFlow provisionUserFlow() {
return IntegrationFlows.from("input.channel")
  .publishSubscribeChannel(Executors.newCachedThreadPool(),
      s -> s
            .subscribe(f -> f.handle(provisionerA, "provision"))
            .subscribe(f -> f.handle(provisionerB, "provision"))
  .get();
}

@ServiceActivator( inputChannel = "errorChannel", outputChannel = "replyChannel")

public boolean processErrors(Exception message) throws RuntimeException{

System.out.println("Message" + message.getMessage());
System.out.println ("******************************");

throw new RuntimeException(message.getMessage());
}

MGateway.java

@MessagingGateway(errorChannel = "errorChannel")
public interface MGateway {

@Gateway(requestChannel = "input.channel", replyChannel = "replyChannel") 
boolean invokeProvisioner(User user);
}

SOLUTION

@Bean
public IntegrationFlow provisionUserFlow() {
return
    IntegrationFlows.from("input.channel")
    .publishSubscribeChannel(Executors.newCachedThreadPool(),
        s -> s.applySequence(true)
            .subscribe(f -> f.enrichHeaders(e -> e.header(MessageHeaders.ERROR_CHANNEL, "errorChannel", true))
                .handle(provisionerA, "provision")
                .channel("aggregatorChannel")
            )
            .subscribe(f -> f.enrichHeaders(e -> e.header(MessageHeaders.ERROR_CHANNEL, "errorChannel", true))
                .handle(provisionerB, "provision")
                .channel("aggregatorChannel"))
            )
        .get();
}

@Bean
public IntegrationFlow aggregateFlow() {
return IntegrationFlows.from("aggregatorChannel")
    .channel( aggregatorChannel)
    .aggregate( a -> a.processor( collect, "aggregatingMethod"))
    .get();
}

@Transformer( inputChannel = "errorChannel", outputChannel = "aggregatorChannel")
public Message<?> errorChannelHandler(ErrorMessage errorMessage) throws RuntimeException {

Message<?> failedMessage =  ((MessagingException) errorMessage.getPayload())
    .getFailedMessage();

Exception exception = (Exception) errorMessage.getPayload();

return  MessageBuilder.withPayload( exception.getMessage())
       .copyHeadersIfAbsent( failedMessage.getHeaders() )
       .build();
}
ray9898
  • 91
  • 1
  • 9

1 Answers1

1

You see @Gateway is just Java method. It has one return and may throw one exception. I'm still confused why people think that Spring Integration works somehow different. It is fully based on Java and does nothing magic - only calls java methods.

Now let's imaging what you'd do if you develop just with raw Java. Right, you would wait for relies from both threads and build a single return to the caller.

The same we can do with Spring Integration. Just need to use Aggregator EIP. You can catch error messages in that error channel and correlate them via their failedMessages. The .publishSubscribeChannel() can be supplied with the option:

/**
 * Specify whether to apply the sequence number and size headers to the
 * messages prior to invoking the subscribed handlers. By default, this
 * value is <code>false</code> meaning that sequence headers will
 * <em>not</em> be applied. If planning to use an Aggregator downstream
 * with the default correlation and completion strategies, you should set
 * this flag to <code>true</code>.
 * @param applySequence true if the sequence information should be applied.
 */
public void setApplySequence(boolean applySequence) {

which is false by default. The aggregator then can just rely on the default correlationStrategy and gather the group of errors for you to return to the replyChannel in headers.

All the info you can find in the Reference Manual:

https://docs.spring.io/spring-integration/docs/4.3.12.RELEASE/reference/html/messaging-routing-chapter.html#aggregator

https://docs.spring.io/spring-integration/docs/4.3.12.RELEASE/reference/html/configuration.html#namespace-errorhandler

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • Thank you for the explanation. Can you provide more example? I have aggregator setup already and one way I can think of is to gather the errors and send them to the aggregate channel. Is this what you suggested as well? I am still trying to find a better way such as sending all the errors to the error channel. But I am not sure if this is possible to do. Thanks again. – ray9898 Oct 02 '17 at 13:07
  • Hi Artem Bilan, I updated the code to use aggregator. but I get Null correlation not allowed. Maybe the CorrelationStrategy is failing. I copied the message header to the returned message so that the it contains correlation ID. any ideas? – ray9898 Oct 02 '17 at 17:29
  • Not sure why you send normal messages to the `aggregatorChannel` as well, but let it be. That might be a part of your logic. The `errorChannel` receives `ErrorMessage`s and mostly they are without `headers`. What you need is the `payload` of the `ErrorMessage` - usually it is `MessagingException`. And this one has `failedMessage` property. And already that one has all the needed information. – Artem Bilan Oct 02 '17 at 17:40
  • After copying the failedMessage header to the my returned message, the Null correlation error is gone (code is updated), but I dont get any responses from the controller that calls the messaging gateway. Is this because I dont specify which method that receives input from aggregatingChannel? I tried to use @Aggregator annotation and specify inputchannel on the aggregatorMethod but still no reply. – ray9898 Oct 02 '17 at 18:18
  • I sent normal messages to aggregatorChannel because I simply just want to receive it in my aggregatorMethod. My goal is sending back these two exception messages to the controller. Please let me know if this is not a good approach. – ray9898 Oct 02 '17 at 18:19
  • OK! So, is that OK to send errors to the same aggregator? You talk about some reply into the caller with `these two exception messages`, but your `aggregatingMethod` is `boolean`. Right, this correlates with the `invokeUserProvisioner` `@Gateway` definition. but won't produce any strings. Maybe you just need to throw some aggregated exception in the end? Let's revise what you really need! You can't return to `boolean` anything unless `boolean`. – Artem Bilan Oct 02 '17 at 18:36
  • Yeah, I am sorry what I meant is I just want to return 500 with these two aggregated exception messages. Yes, I think I can just throw some aggregated exception in the end. But, currently my controller is hang after sending a message, even I specified replyChannel as the output channel of the transformer. – ray9898 Oct 02 '17 at 18:49
  • Oh I think I know why is hanging if I specify the output channel as `reply-channel`. Because `invokeUserProvisioner` is expecting a boolean. If I return true/false in the transformer, the controller wont hang. But why is it hanging when I specify the output channel as `aggregateChannel`? Is it possibly because I dont specify the input channel in `aggregatingMethod`? – ray9898 Oct 02 '17 at 19:00
  • The `replyChannel` is not related. All logic internally is based on the `replyChannel` and `errorChannel` headers. What we have to realize here is the gateway is request/reply scenario: one request - one reply. So, if we have `publishSubscribeChannel` downstream, we should definitely consider an aggregator to build reply. Here I think we should consider good results and errors as equal and really collect them via the same aggregator. There in the aggregation function we may decide to throw an exception to make `500 Server Error` in the controller or just return `true` if no errors at all – Artem Bilan Oct 02 '17 at 19:06
  • Now about hanging. You are missing the fact that the downstream part after `publishSubscribeChannel()` in the main flow is one more subscriber to this channel: https://github.com/spring-projects/spring-integration-java-dsl/wiki/Spring-Integration-Java-DSL-Reference#sub-flows-support. You have to define a new `IntegrationFlow` starting with that `aggregatorChannel`. This `provisionUserFlow()` must finish with this `publishSubscribeChannel(Executors.newCachedThreadPool(), ... )` definition. – Artem Bilan Oct 02 '17 at 19:11
  • Sorry I dont really understand why I need a new IntegrationFlow. Could you please explain a little bit more? Isn't what aggregator is used for to wait the threads until they are all finished and aggregate the errors/good results, then provide one reply to the gateway? – ray9898 Oct 02 '17 at 19:36
  • That's correct. I only say you that you have one more issue in your configuration. And it is as such the aggregtor becomes as one more subscriber to the `publishSubscribeChannel()`. The main flow after this one is one more subscriber. That's why I say you that you should sever an aggregator to one more flow and connect it via that `aggregatorChannel` – Artem Bilan Oct 02 '17 at 19:51
  • 1
    Hi Artem, I found this link http://forum.spring.io/forum/spring-projects/integration/129704-error-handling-aggregator-and-response-problems that has sample with intermediate gateway (https://gist.github.com/garyrussell/5510524). Is this similar with what you suggested? I think I might understand the concept a little bit, but I am not sure how to do it in spring DSL. Could you please provide a sample code? – ray9898 Oct 03 '17 at 17:24
  • Yes, there is `.gateway()` in Java DSL with the same purpose. Indeed, that approach might work for you as well. – Artem Bilan Oct 03 '17 at 17:59
  • But again: don't forget that main flow is one more subscriber to the `publishSubscribeChannel()`. Essentially it is a subscriber by the flow definition, everything rest with the intermediate `.subscribe()` is just additional fruits. – Artem Bilan Oct 03 '17 at 18:00
  • I see - there are three subscribers actually in the flow (the two subsciber in `publishSubscribeChannel` and the `aggregator`). I created a new workflow called `aggregateFlow` (code is in the update 2 section above), but still the controller still hang :(. The two `provision` functions just throw an exception. – ray9898 Oct 03 '17 at 19:12
  • Oh well... Maybe you have a simple Spring Boot app to share with me via GitHub? – Artem Bilan Oct 03 '17 at 19:23
  • Actually, my issue is because I did not specify the error channel. After specifying the error channel in the message header, I can see the exception and the good result are aggregated. I put the solution in my post. Thank you again for the help! – ray9898 Oct 03 '17 at 20:51
  • Hi Artem, I know I already checked the post. But I still have another issue. I want to send the exception in the `aggregateMethod` to the controller who calls the gateway. But since I defined an error channel, the error actually goes to the error-channel instead of being raised to the controller. I tried to modify the error-channel header to `replyChannel`, but it still does not work. Any ideas? Another way I can think of is sending an object that contains the error message as the gateway return so that the controller can get it, but not sure if that is a good approach. – ray9898 Oct 05 '17 at 19:33
  • You know you can return an `Exception` from the aggregator to the gateway. There is a logic like `if (reply instanceof Throwable) ... rethrow(error)`. So, no need to change gateway's return type! – Artem Bilan Oct 05 '17 at 19:54
  • Yeah that's what I do, I checked if the type of message is `ErrorMessage`, and I just throw a `RunTimeException`, but the controller is hanging. Actually, after debugging more, it does not even go to the error channel, and the error-channel defined in the header in one of the message in the list. After the `aggregateMethod`, isn't it supposed to go back to the main thread? Because there is no more end-point after `.aggregate`. Should I add another endpoint that retrieve message from `aggregateChannel` and specify the output channel as `replyChannel`? – ray9898 Oct 05 '17 at 20:34
  • After enabling the debugging mode, I actually get `message sent to null channel`. – ray9898 Oct 05 '17 at 21:17
  • `message sent to null channel`. That's might be a result of the message discarding in the aggregator. I think it's time to write a simple Spring Boot app and let to play with it locally. – Artem Bilan Oct 05 '17 at 22:07