Camel: dynamically add routes

I am using Apache-Camel 2.15.2.

I am trying to add routes to CamelContext dynamically, but I ran into a problem that puzzled me.

As far as I can tell, I am adding routes to the correct CamelContext , and it seems that their configure() is called without exception. However, when I try to execute the main route, I get the runtime. The exception tells me that the route I added dynamically does not exist.

Here is a simplified version of my code:

 public class MainRouteBuilder extends RouteBuilder { public static CamelContext camelContext; public static boolean returnLabel = true; public static RouteBuilder nestedRouteBuilder; @Override public void configure() throws Exception { System.out.println("Building main route!"); System.out.println("Context: " + getContext()); camelContext = getContext(); from("direct:mainRoute") //3. I do not actually want to instantiate RouteContainer like this each time I call this route. //I would simply want to reuse a reference to an instance I created outside of configure()... .to(new RouteContainer().getMyRoute(2)) ; returnLabel = false; //configure direct:myRoute.2 includeRoutes(nestedRouteBuilder); } } public class RouteContainer extends RouteBuilder { public Route route; RouteContainer() { super(MainRouteBuilder.camelContext); } String getMyRoute(final int n) { if (MainRouteBuilder.returnLabel && route == null) { route = new Route() { @Override public void configure() { System.out.println("Building nested route!"); System.out.println("Context: " + getContext()); from("direct:myRoute." + n) .transform() .simple("number: " + n) .to("stream:out") .process(new Processor() { @Override public void process(Exchange exchange) throws Exception { Response response = new Response(); response.setStatus(Status.SUCCESS); exchange.getOut().setBody(response); } }); } }; } //1. works: MainRouteBuilder.nestedRouteBuilder = this; //2. does not work: // RouteContainer routeContainer = new RouteContainer(); // routeContainer.route = this.route; // MainRouteBuilder.nestedRouteBuilder = routeContainer; return "direct:myRoute." + n; } @Override public void configure() throws Exception { if (route != null) { route.configure(); } } public abstract static class Route { abstract public void configure(); } } 

Requests sent to direct:mainRoute . When Camel starts up, I see in the console:

 Building main route! Context: SpringCamelContext(camel-1) with spring id org.springframework.web.context.WebApplicationContext:/sample-route Building nested route! Context: SpringCamelContext(camel-1) with spring id org.springframework.web.context.WebApplicationContext:/sample-route 

and when I send a request to direct:mainRoute , the output is:

 {"status":"SUCCESS"} 

HOWEVER, if I comment (1) above and uncomment (2), Camel launches the same output to the console, but when I send a request to direct:mainRoute , route execution fails with an exception:

 org.apache.camel.component.direct.DirectConsumerNotAvailableException: No consumers available on endpoint: Endpoint[direct://myRoute.2]. 

To clarify: my problem is that I would really want NOT to not instantiate the RouteContainer every time I call its route, as in (3). This is why I create them at point (2) and insert a Route instance into it ...

So I would like MainRouteBuilder look like this:

 public class MainRouteBuilder extends RouteBuilder { public static CamelContext camelContext; public static boolean returnLabel = true; public static RouteBuilder nestedRouteBuilder; RouteContainer routeContainer = new RouteContainer(); @Override public void configure() throws Exception { System.out.println("Building main route!"); System.out.println("Context: " + getContext()); camelContext = getContext(); from("direct:mainRoute") .to(routeContainer.getMyRoute(2)) //I may want to call it again like this: //.to(routeContainer.getMyRoute(3)) ; returnLabel = false; //configure direct:myRoute.2 includeRoutes(nestedRouteBuilder); } } 

My guess is that perhaps a nested direct:myRoute.2 route was created in the wrong CamelContext , but the output to the console tells me that it is not.

Any idea what I'm doing wrong here?

+5
source share
1 answer

Route Configuration! = Route Execution

It seems that you are confusing route configuration with route execution. We were all there ,-)

When you configure RouteBuilder in MainRouteBuilder#configure() , the method only executes once the Camel application loads to configure the routing logic. DSL creates plumbing for the route (Processors, Interceptors, etc.) and what will be the runtime of the route.

Point to return home: DSL is not executed again and again with each Exchange.

In other words, Camel does not do what you specify in (3). It does not execute new RouteContainer().getMyRoute(2) for each individual Exchange. Think about it: the bytecode for configure() only runs when Camel is configured, and the bytecode creates an instance of an object of the RouteContainer class and calls getMyRoute with argument 2 . The resulting object is SendProcessor to SendProcessor , which generates DSL to() .

Analysis of your code

Now about why your code does not give the expected result.

You are having trouble maintaining the state of the RouteContainer . Each time you call getMyRoute , you overwrite the route instance variable. Therefore, it is impossible for your current code to call getMyRoute several times (with different n s), and then call includeRoutes only once at the end, because only the last created route will be added.

I also don't like to mask the Camel route class with my own class, just to act as a placeholder, but this brings up another discussion that you didn't ask for.

Simplified solution

Instead of the RouteContainer class, here is the RouteGenerator class that creates routes and returns the direct: endpoint to the caller. It tracks all routes in the internal Set .

 public class RouteGenerator { private Set<RouteBuilder> routeBuilders = new HashSet<>(); public String generateRoute(final int n) { routeBuilders.add(new RouteBuilder() { @Override public void configure() throws Exception { System.out.println("Building nested route!"); System.out.println("Context: " + getContext()); from("direct:myRoute." + n) .transform() .simple("number: " + n) .to("stream:out") .process(new Processor() { @Override public void process(Exchange exchange) throws Exception { Response response = new Response(); response.setStatus(Status.SUCCESS); exchange.getOut().setBody(response); } }); } }); return "direct:myRoute." + n; } public Set<RouteBuilder> getRouteBuilders() { return routeBuilders; } } 

And here is your MainRouteBuilder , which creates an instance of RouteGenerator only once and can generate as many routes as you like.

As soon as you finish configuring routes, you simply RouteBuilders over the accumulated RouteBuilders and turn them on:

 public class MainRouteBuilder extends RouteBuilder { public static CamelContext camelContext; public static RouteGenerator routeGenerator = new RouteGenerator(); @Override public void configure() throws Exception { System.out.println("Building main route!"); System.out.println("Context: " + getContext()); camelContext = getContext(); from("direct:mainRoute") .to(routeGenerator.generateRoute(2)); for (RouteBuilder routeBuilder : routeGenerator.getRouteBuilders()) { includeRoutes(routeBuilder); } } } 

EDIT: Why is your option (2) not working?

After debugging for some time, I realized why you see this effect.

Extracted from the Java tutorial :

As with instance methods and variables, an inner class is associated with an instance of its enclosing class and has direct access to this methods and object fields.

In (2), you create a route instance within the scope of the original RouteContainer object, acting as an external object. The route object saves the external RouteContainer as its external object. Therefore, from() and the following methods call this initial RouteContainer ( RouteBuilder ) object, not the new RouteContainer that you create later , which is the one you provide the top RouteBuilder (which is associated with CamelContext).

This is why your direct:myRoute.2 not added to the Camel Context because it is created in another route builder.

Also note that console output (2):

 Building main route! Context: CamelContext(camel-1) Building nested route! Context: CamelContext(camel-2) Added! 

The second route is added to another camel-2 context. Landl creates this new context lazily when he adds a route to an old RouteBuilder that has not yet been associated with any Camel loop.

Note that the Camel Context of the original RouteContainer (created by initializing the instance variable) is null , since you will later assign the MainRouteBuilder.camelContext property.

You can see how two different route builders are used by adding the following println statements:

Inside Route # configure:

 System.out.println("RouteContainer to which route is added: " + RouteContainer.this.hashCode()); 

Inside MainRouteBuilder # configure, before includeRoutes :

 System.out.println("RouteContainer loaded into Camel: " + nestedRouteBuilder.hashCode()); 

With (1) the hash code is the same as . With (2), the hash code is different , which clearly shows that the game has two different RouteBuilders players (one of which contains a route and one that is loaded into a context that does not include a route).


Source: I am a member of the Apache Camel PMC and committer.

+5
source

Source: https://habr.com/ru/post/1242409/


All Articles