tytk
tytk

Reputation: 2332

Camel: How to build a route conditionally from configuration?

I have a RouteBuilder subclass which sets up my Camel routes. It is constructed by Spring. Initially it looked something like this:

@Override
public void configure() throws Exception {
    from(...)
    .process(...)
    .to(...)
}

What I would like to do is add an additional endpoint routing based on configuration. There is a Properties file that Spring uses to create the RouteBuilder bean and one of the fields it sets is boolean addAnotherEndpoint. If this boolean is true I want to add another to. If it's false I want it to fall back to its current behaviour. So I changed it to this:

@Override
public void configure() throws Exception {
    from(...)
    .process(...)
    .to(...)
    .choice()
        when(constant(addAnotherEndpoint)).to(...)
    .endChoice();
}

While this appears to have the desired behaviour I'm having issues writing unit tests for it (because it's pulling the value of addAnotherEndpoint from the properties file even though I'm trying to override it in my tests). Is there a better way of handling this? Will my current method have any unintended side-effects?

EDIT:

I am testing using a CamelSpringTestSupport subclass (using JUnit tests). In the @Before method I create an AdviceWithRouteBuilder that replaces all endpoints with mocks. In my @Test, I am attempting to override the value of addAnotherEndpoint which is taken from the Properties file:

@Test
public void testConditionalRouting() throws Exception {
    context.start();
    MyRouteBuilder routeBuilder = (MyRouteBuilder) applicationContext.getBean("myRouteBuilder");
    routeBuilder.setAddAnotherEndpoint(true);
    getMockEndpoint("myMockEndpoint").expectedMessageCount(1);
    sendMockMessage();
    assertMockEndpointsSatisfied();
    context.stop();
}

There is a corresponding test that sets addAnotherEndpoint to false and asserts that 0 messages were received. The problem is that overriding the value of this variable doesn't seem to be working. One test passes and the other fails depending on whether my Properties file says the value should be true or false. What this suggests to me is that the route is being constructed before I override setting (and therefore also before the context is started). I checked in a debugger and the setting is correctly overridden. It just doesn't seem to have any affect.

EDIT 2:

From my AdviceWithRouteBuilder:

@Override
public void configure() throws Exception {
    replaceFromWith(MOCK_FROM_ENDPOINT);
    interceptSendToEndpoint(FIRST_TO_ENDPOINT)
            .skipSendToOriginalEndpoint().to(MOCK_FIRST_TO_ENDPOINT);
    weaveById(MY_PROCESSOR_ENDPOINT).replace()
            .to(MOCK_MY_PROCESSOR_ENDPOINT);
    weaveById(SECOND_TO_ENDPOINT).replace()
            .to(MOCK_SECOND_TO_ENDPOINT);
}

This replaces each EIP with a mock endpoint. I have 4 tests that rely on these and they seem to be working as expected, the only problem is the conditional routing.

Aside from JUnit annotations, the only annotations on my test class are @Override on isUseAdviceWith() (returns true) and createApplicationContext(), which returns a new Spring Application Context.

I ran my tests without context.start() and the only one that passed was the one that asserts 0 messages were received (which makes sense if the route was not started). So I don't believe the context is being auto-started.

Upvotes: 2

Views: 5747

Answers (2)

JL_SO
JL_SO

Reputation: 1921

I have just found that

.when(exchange-> foo()==true)

is sufficient.

Upvotes: 1

tytk
tytk

Reputation: 2332

Here's what ended up working:

My test was fine but I had to rethink how I was building my route. What was happening was at the time the Spring context was created, the route was being built. It checked the value of addAnotherEndpoint at that time and set the constant based on that. But boolean is a primitive value, which means it was passed by value. Why does this matter? Because the constant() function simply sets the route based on the value of that constant at the time the route is built. Changing it later on via Spring had no affect because the value had already been read. What I needed to do was make it pass an object as a predicate so I could manipulate that same object later on as a bean.

Also, I changed from a choice() to a filter() as suggested. Here's the finished product:

@Override
public void configure() throws Exception {
    from(...)
    .process(...)
    .to(...)
    .choice()
        when(myBooleanPredicate).to(...)
    .endChoice();
}

And my custom predicate that could be manipulated via Spring:

public class BooleanPredicate implements Predicate {
  private boolean value;

  @Override
  public boolean matches(Exchange exchange) {
    return value;
  }

  public void setValue(boolean value) {
    this.value = value;
  }

This BooleanPredicate's internal value is set to addAnotherPredicate. It is reread each time the route is run since the BooleanPredicate is passed by reference.

Upvotes: 1

Related Questions