Hello guys,

These days I received the task to implement a redelivery process for one of our 
camel apps. 
In summary, our application consumes some REST services and, based on the 
user's configuration, it must handle the HttpOperationFailedException retries 
differently, for example:

- If the service returns HttpStatus 404 and the business error is 
"ClientNotFound",  our application should retry only 5 times (any other 404 
shouldn't be redelivered).
- Also, if the service returns HttpStatus 503 and the business error is 
"ServiceUnavailable",  our application should retry only 2 times. 
- Finally, each error should have its own retry count.

With that in mind, I soon found out that I couldn't rely on the 
maximumRedeliveries, since it's shared between all Exceptions (see 
https://stackoverflow.com/questions/13684775/camel-retry-control-with-multiple-exceptions),
 but I managed to accomplish my goal using the onWhen and retryWhile, like so:
        //For each user configuration, I append this to the route...
        .onException(HttpOperationFailedException .class)
                        .onWhen(simple("${exchangeProperty[status]} == " 
+cfg.getHttpStatus() + " && ${exchangeProperty[code]} == 
'"+cfg.getBusinessCode+"'"))
                        .retryWhile(simple("${exchangeProperty[ClientNotFound]} 
< "+cfg.getMaxRetries(), Boolean.class))

The issue is that while I was stressing the possible scenarios for this retry 
process, I reached one that I couldn't understand what was going on inside 
camel.

So, let's say that for the first 3 requests I receive the 404 - ClientNotFound, 
which are configured to do 5 retries, but after that, I start receiving a 404 - 
PageNotFound which should not perform any retry. 
In this case, I noticed that this new exception wasn't being rejected by the 
onWhen and the message got stuck in the retry loop. 

I understand that this infinity loop might be caused by the retryWhile 
condition since the ClientNotFound's counter never became greater than 5 and It 
could be solved by just adding the business code condition to it as shown below:

        .retryWhile(simple("${exchangeProperty[ClientNotFound]} < 
"+cfg.getMaxRetries()+" && ${exchangeProperty[code]} == 
'"+cfg.getBusinessCode+"'", Boolean.class))

But I can't understand why this infinity loop doesn't happen if I add a 
"generic" onException(HttpOperationFailedException .class) without any retry 
policy to my route OR if I raise an exception which is mapped by another 
onWhen, for example, 503 - ServiceUnavailable. 

I tried to debug the route execution, but since I'm a Camel newbie, I couldn’t 
understand what was going on!

Could someone help me understand what is really happening behind the curtains 
or what I'm doing wrong? 

See below the jUnit scenario for more details:

import java.util.concurrent.atomic.AtomicLong;

import org.apache.camel.EndpointInject;
import org.apache.camel.Exchange;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.junit.Test;

public class FooTest extends CamelTestSupport {

  private static final String DIRECT_DUMMY = "direct:dummy";

  @EndpointInject(uri = "mock:result")
  protected MockEndpoint resultEndpoint;

  @Produce(uri = "direct:start")
  protected ProducerTemplate template;

  @Test
  public void shouldRedeliverOnErrors() throws Exception {
    resultEndpoint.expectedBodiesReceived("Body");
    template.sendBodyAndHeader(DIRECT_DUMMY, "Body", "Header", "HV");
    resultEndpoint.assertIsNotSatisfied();
  }

  protected RouteBuilder createRouteBuilder() {
    return new RouteBuilder() {
      @Override
      public void configure() throws Exception {

        from(DIRECT_DUMMY)
          .onException(Exception.class)
          .onWhen(simple("${exchangeProperty[status]} == 404 && 
${exchangeProperty[code]} == 'ClientNotFound'"))
          .retryWhile(simple("${exchangeProperty[ClientNotFound]} < 5", 
Boolean.class))
          .bean(Processor.class, "maxRetriesReached")
          .end()
          .onException(Exception.class)
          .onWhen(simple("${exchangeProperty[status]} == 503 && 
${exchangeProperty[code]} == 'ServiceUnavailable'"))
          .retryWhile(simple("${exchangeProperty[ServiceUnavailable]} < 2", 
Boolean.class))
          .bean(Processor.class, "maxRetriesReached")
          .end()
//"generic" onException
//          .onException(Exception.class)
//          .bean(Processor.class, "noRetries")
//          .end()
          .bean(Processor.class, "process")
          .to(resultEndpoint)
          .end();

      }
    };
  }

  public static class Processor {

    private static AtomicLong retryState = new AtomicLong(0L);

    public void process(Exchange e) throws Exception {
      long count = retryState.getAndIncrement();
      if (count < 3) {
        System.err.println("count [" + count + "] status 404 code 
[ClientNotFound]");
        updatExchange(e, 404, "ClientNotFound");
        throw new Exception(); //I'm using Exception just for simplicity
      } else if (count < 8) {
        //This isn't expected to retry, but it does if the "generic" 
onException isn't present!
        System.err.println("count [" + count + "] status 404 code 
[PageNotFound]");
        updatExchange(e, 404, "PageNotFound");
        throw new Exception();
      } else {
        System.err.println("count [" + count + "] status 503 code 
[ServiceUnavailable]");
        updatExchange(e, 503, "ServiceUnavailable");
        throw new Exception();
      }
    }

    public void maxRetriesReached(Exchange e) {
      System.err.println("Max retries for code [" + e.getProperty("code", 
String.class) +"] reached!");
    }
    
    public void noRetries(Exchange e) {
      System.err.println("No retries for code [" + e.getProperty("code", 
String.class) +"]!");
    }
    
    private void updatExchange(Exchange e, int status, String code) {
      // Increments the retry counter for the specific code
      e.setProperty(code, e.getProperty(code, 0, Integer.class) + 1);
      // Updates the last exception code
      e.setProperty("code", code);
      // Update the last exception status
      e.setProperty("status", status);

    }
  }
}

BR.,

Fabricio G. Pellegrini

Reply via email to