Camel routeBuilder.weaveById fails when using an ID that is a templated route parameter

746 views Asked by At

Summary:

I am getting an IllegalArgumentException from my junit trying to test a templated route that is built from a template that I created in a 2nd RouteBuilder class.

You can see in the CreateTemplate class I'm trying to use the 3rd template parameter to set the .id() string for the TO file producer. This is done so that I can find (via weaveById) and mock this producer in the junit. However, it seems that the id is not setup correctly, and the weaveById fails.

If I create the route created directly, I mean, not via the template, this works just fine, and I am able to mock the producer with the weaveById() method.

Using spring-boot 2.7.0, Camel 3.16.0, and openjdk-11.

What am I missing here? Thanks for your help.

Note: I did find a workaround which is to use weaveByType(). This is commented out in the junit but does work fine. But would still like to understand why the weaveById() is not working here.

Problem details and code:

The junit test class:

package com.jvh.routes;

import org.apache.camel.CamelContext;
import org.apache.camel.EndpointInject;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.AdviceWith;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.spring.junit5.CamelSpringBootTest;
import org.apache.camel.test.spring.junit5.UseAdviceWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;

@SpringBootTest
@CamelSpringBootTest
@UseAdviceWith
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class RouteTest {

   @Autowired
   private CamelContext camelContext;

   @Autowired
   private ProducerTemplate producerTemplate;

   @EndpointInject("mock:endpointMock")
   private MockEndpoint mockEndpoint;


   @BeforeEach
   public void setup() throws Exception {

      mockEndpoint.reset();

      AdviceWith.adviceWith(camelContext, "my-test-file-route", routeBuilder -> {
         routeBuilder.replaceFromWith("direct:start");
         routeBuilder.weaveById("my-internal-route-id").replace().to(mockEndpoint);  // fails
         //routeBuilder.weaveByType(ToDefinition.class).replace().to(mockEndpoint);  // works
         routeBuilder.setLogRouteAsXml(false);
      });

      camelContext.start();
   }

   @AfterEach
   public void tearDown() {
      camelContext.stop();
   }

   @Test
   void test_processMessageToRestApi_Ok() throws Exception {

      final String testMsg = "{ test msg }";

      // Expect output file endpoint mock to receive 1 message
      mockEndpoint.expectedMessageCount(1);
      mockEndpoint.expectedBodiesReceived(testMsg);

      // Message body
      producerTemplate.sendBody("direct:start", testMsg);
      mockEndpoint.assertIsSatisfied();
   }
}

The template creation class:

package com.jvh.routes;

import org.apache.camel.LoggingLevel;
import org.apache.camel.Ordered;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.stereotype.Component;

@Component
public class CreateTemplate extends RouteBuilder {

   @Override
   public int getOrder() {
      return Ordered.HIGHEST;  // required to ensure the template is created first
   }

   @Override
   public void configure() {

      routeTemplate("myTemplateId")
      .templateParameter("input-directory-param-id")
      .templateParameter("output-directory-param-id")
      .templateParameter("route-id-param-id")

      .from("file:{{input-directory-param-id}}")
      .log(LoggingLevel.INFO, log, "--> got file ${header.CamelFileName}")
      .to("file:{{output-directory-param-id}}")
      .id("{{route-id-param-id}}")              // set id here to be able to mock this producer
      .log(LoggingLevel.INFO, log, "--> done");
   }
}

The templated route creation class:

package com.jvh.routes;

import org.apache.camel.CamelContext;
import org.apache.camel.Ordered;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.builder.TemplatedRouteBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CreateRoute extends RouteBuilder {

   @Autowired
   private CamelContext camelContext;

   @Override
   public int getOrder() {
      return Ordered.LOWEST;  // route created after the template
   }

   @Override
   public void configure() throws Exception {

      final String routeId = TemplatedRouteBuilder.builder(camelContext, "myTemplateId")
            .routeId("my-test-file-route")
            .parameter("input-directory-param-id", "input-directory")
            .parameter("output-directory-param-id", "output-directory")
            .parameter("route-id-param-id", "my-internal-route-id")     // parameter to set the producer id
            .add();
      log.info("Route {} created from template", routeId);
   }
}

The exception thrown when I run the junit test:

java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]]
    at org.apache.camel.builder.AdviceWithTasks$1.task(AdviceWithTasks.java:226)
    at org.apache.camel.builder.AdviceWith.doAdviceWith(AdviceWith.java:222)
    at org.apache.camel.builder.AdviceWith.adviceWith(AdviceWith.java:75)
    at com.jvh.routes.RouteTest.setup(RouteTest.java:38)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    <snip...>

Some relavent debug logs from the junit run:

<snip...>
2022-05-23 16:36:58.535 DEBUG 10916 --- [           main] o.a.camel.spring.SpringCamelContext      : onApplicationEvent: org.springframework.test.context.event.AfterTestMethodEvent[source=[DefaultTestContext@50fe837a testClass = RouteTest, testInstance = com.jvh.routes.RouteTest@510da778, testMethod = test_processMessageToRestApi_Ok@RouteTest, testException = java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]], mergedContextConfiguration = [WebMergedContextConfiguration@3a62c01e testClass = RouteTest, locations = '{}', classes = '{class com.jvh.TestApp}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1187c9e8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16aa8654, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@61eaec38, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@31920ade, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@16b2bb0c, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@5ae50ce6], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]]
2022-05-23 16:36:58.535 DEBUG 10916 --- [           main] tractDirtiesContextTestExecutionListener : After test method: context [DefaultTestContext@50fe837a testClass = RouteTest, testInstance = com.jvh.routes.RouteTest@510da778, testMethod = test_processMessageToRestApi_Ok@RouteTest, testException = java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]], mergedContextConfiguration = [WebMergedContextConfiguration@3a62c01e testClass = RouteTest, locations = '{}', classes = '{class com.jvh.TestApp}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1187c9e8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16aa8654, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@61eaec38, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@31920ade, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@16b2bb0c, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@5ae50ce6], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]], class annotated with @DirtiesContext [true] with mode [AFTER_EACH_TEST_METHOD], method annotated with @DirtiesContext [false] with mode [null].
2022-05-23 16:36:58.535 DEBUG 10916 --- [           main] o.s.w.c.s.GenericWebApplicationContext   : Closing org.springframework.web.context.support.GenericWebApplicationContext@15515c51, started on Mon May 23 16:36:49 EDT 2022
2022-05-23 16:36:58.535 DEBUG 10916 --- [           main] o.a.camel.spring.SpringCamelContext      : onApplicationEvent: org.springframework.context.event.ContextClosedEvent[source=org.springframework.web.context.support.GenericWebApplicationContext@15515c51, started on Mon May 23 16:36:49 EDT 2022]
2022-05-23 16:36:58.543 DEBUG 10916 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 2147483647
2022-05-23 16:36:58.543 DEBUG 10916 --- [           main] o.a.c.main.SimpleMainShutdownStrategy    : Shutdown called
2022-05-23 16:36:58.545 DEBUG 10916 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
2022-05-23 16:36:58.545 DEBUG 10916 --- [           main] o.s.t.c.w.ServletTestExecutionListener   : Resetting RequestContextHolder for test context [DefaultTestContext@50fe837a testClass = RouteTest, testInstance = com.jvh.routes.RouteTest@510da778, testMethod = test_processMessageToRestApi_Ok@RouteTest, testException = java.lang.IllegalArgumentException: There are no outputs which matches: my-internal-route-id in the route: Route(my-test-file-route)[From[direct:start] -> [Log[--> got file ${header.CamelFileName}], To[file:{{output-directory-param-id}}], Log[--> done]]], mergedContextConfiguration = [WebMergedContextConfiguration@3a62c01e testClass = RouteTest, locations = '{}', classes = '{class com.jvh.TestApp}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1187c9e8, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@16aa8654, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@61eaec38, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@31920ade, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@16b2bb0c, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@5ae50ce6], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true, 'org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false, 'org.springframework.test.context.support.DependencyInjectionTestExecutionListener.reinjectDependencies' -> true]].
<snip...>
1

There are 1 answers

2
TacheDeChoco On

WeaveByID is weaving nodes (ie endpoints) within the context of a specific route.

Given the route:

from("direct:start")
        .id("my-route")
        .to("mock:bar").id("bar")

You can do:

AdviceWith.adviceWith(camelContext, "my-route", ...
           ...
                weaveById("bar").replace().to("log:demo");

In your example, you are weaving "my-internal-route-id" which is a route, not an endpoint