Search code examples
javaapache-camel

Passing bean route template parameters to a bean endpoint in Camel


I'm using Camel 4.0.0-RC2 and following the documentation on binding beans to a RouteTemplate. I have a simple route template to consume from a configurable fromEndpointUri and pass a configurable foo to my fooConsumer:

routeTemplate("myTemplate")
  // Define the parameters for the route template.
  .templateParameter ("fromEndpointUri")
  .templateBean      ("foo", Foo.class)

  // Define the route.
  .from ("{{fromEndpointUri}}")
  .bean (fooConsumer, "processFoo(${body}, #{{foo}})");

The route I create using the template is simply:

templatedRoute("myTemplate")
  .routeId   ("myTemplateRoute")
  .parameter ("fromEndpointUri", "direct:test")
  .bean      ("foo", Foo.class, ignored -> new Foo());

When sending a message through this route, an error is thrown No type converter available to convert from type: java.lang.String to the required type: com.example.Foo. The relevant stack trace is:

org.apache.camel.NoTypeConversionAvailableException: No type converter available to convert from type: java.lang.String to the required type: com.example.Foo
    at org.apache.camel.impl.converter.CoreTypeConverterRegistry.mandatoryConvertTo(CoreTypeConverterRegistry.java:279) ~[camel-base-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.MethodInfo$ParameterExpression.evaluateParameterValue(MethodInfo.java:717) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    ... 94 more
Wrapped by: org.apache.camel.component.bean.ParameterBindingException: Error during parameter binding on method: public java.util.List com.example.FooConsumer.processFoo(java.lang.String,com.example.Foo) at parameter #1 with type: class com.example.Foo with value type: class java.lang.String and value: #foo-1
    at org.apache.camel.component.bean.MethodInfo$ParameterExpression.evaluateParameterValue(MethodInfo.java:728) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.MethodInfo$ParameterExpression.evaluateParameterExpressions(MethodInfo.java:618) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.MethodInfo$ParameterExpression.evaluate(MethodInfo.java:591) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.MethodInfo.initializeArguments(MethodInfo.java:262) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.MethodInfo.createMethodInvocation(MethodInfo.java:270) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.BeanInfo.createInvocation(BeanInfo.java:266) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.AbstractBeanProcessor.process(AbstractBeanProcessor.java:128) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    at org.apache.camel.component.bean.BeanProcessor.process(BeanProcessor.java:81) ~[camel-bean-4.0.0-RC2.jar:4.0.0-RC2]
    ...

How do I pass the route's instance of Foo to my FooConsumer? Some variations I have tried are:

Variation Error
.bean(fooConsumer, "processFoo(${body}, {{foo}})") No type converter available to convert from type: java.lang.String to the required type: com.example.Foo
.to("bean:fooConsumer?method=processFoo(${body},#{{foo}})") No type converter available to convert from type: java.lang.String to the required type: com.example.Foo
.to("bean:fooConsumer?method=processFoo(${body},{{foo}})") No type converter available to convert from type: java.lang.String to the required type: com.example.Foo
.bean(fooConsumer, "processFoo(${body}, ${bean:#{{foo}}})") No bean could be found in the registry for: #foo-1
.bean(fooConsumer, "processFoo(${body}, ${bean:{{foo}}})") No bean could be found in the registry for: foo-1

Surely it's possible to do this since the examples show passing a myClient bean to a aws2-s3 endpoint using the #{{myClient}} syntax.


Solution

  • I've found one way of using these route templates like I'm trying to here, but it doesn't feel very clean. Perhaps I'm trying to use these in a way they aren't intended to be used. Anyhow, to get it to work, I created a inner ServiceActivator class in the class defining the route template:

    private class ServiceActivator {
      private final Foo foo;
    
      public ServiceActivator(final RouteTemplateContext routeTemplateContext) {
        this.foo = (
          routeTemplateContext
            .getLocalBeanRepository ()
            .lookupByNameAndType    ("foo", Foo.class)
          );
      }
    
      @Handler
      public void processFoo(@Body final String body) {
        // do something 
      }
    }
    

    Then, I updated the route template to:

    1. Remove the declaration of foo because having the definition makes Camel attempt to create a default value for it.
    2. Create this ServiceActivator on route creation.
    3. Reference the ServiceActivator in the bean definition of the route.
    routeTemplate("myTemplate")
      // Define the parameters for the route template
      .templateParameter ("fromEndpointUri")
      .templateBean      ("serviceActivator, ServiceActivator.class, ServiceActivator::new)
    
      // Define the route.
      .from("{{fromEndpointUri}}")
      .bean("{{serviceActivator}}")
    

    And left my templated route as:

    templatedRoute("myTemplate")
      .routeId   ("myTemplateRoute")
      .parameter ("fromEndpointUri", "direct:test")
      .bean      ("foo", Foo.class, ignored -> new Foo());
    

    Again, it seems almost like a hack rather than using this feature of a Camel in a way that it was intended to be used. From what I could tell stepping through the code, the route template parameters and beans are only available at the time of route creation, so this was a way of capturing all of that information during route creation to be used in later executions of the route.