Extending Camel DSL - apache-camel

I have a route with a custom aggregator, it looks like this:
I would like to wrap the details so that developers can just add a single line to their routes to get the functionality. Can I wrap those lines so that I have a class that extends ProcessorDefinition, and then add that ProcessorDefinition to routes that need it, so that it looks like an extension of the DSL? If yes, is the addOutput() method the way to do this?
Something like this:
from("file:" + FILE_PATH + "?noop=true")
.log("Detected file")
.split().tokenize("\n")
.streaming()
.unmarshal(bindy)
.addProcessorDefinition(new MyCustomAggregation())
.to("direct:handleAggregatedRecords");
Where
MyCustomerAggegation extends ProcessorDefinition

I have a similar use-case and I don't think Camel has anything to offer out of the box to extend routes at a certain point with route fragments. However, you can break apart one route into multiple fragments
public abstract class ExtensibleRouterBuilder extends RouteBuilder {
#Override
public void configure() {
ProcessorDefinition<?> routeFragmentToExtend= from("file:" + FILE_PATH + "?noop=true")
.log("Detected file")
.split().tokenize("\n")
.streaming()
.unmarshal(bindy);
configure(routeFragmentToExtend);
routeFragmentToExtend.to("direct:handleAggregatedRecords");
}
//"Users" of your API can implement and extend your route
public abstract void configure(ProcessorDefinition<?> from);
}
But this can get messy, especially if you have several choice() calls, so I wish Camel enhances it's API one day.

To get a similar outcome, I use Lombok #ExtensionMethod in the following way:
#ExtensionMethod(MyRouteBuilder.Extensions.class)
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() {
from("direct:in")
.debugLog("Incoming message ${body}");
}
#UtilityClass
public static class Extensions {
public static ProcessorDefinition<?> debugLog(
#NonNull ProcessorDefinition<?> route, String logMessage) {
return route
.log(LoggingLevel.DEBUG, "debug-logger", logMessage);
}
}
}

Related

Camel-Cdi not injecting CamelContext with Registery

I am using camel-cdi and it is injecting the CamelContext, detecting all the routes in project.
But I want a CamelContext with a registry because I have some components that I use in camel routes like shown below.
SimpleRegistry registry = new SimpleRegistry();
registry.put("actionProcessor", actionProcessor);
registry.put("jpa", jpaComponent);
registry.put("jtaTransactionManager", platformTransactionManager);
CamelContext camelContext = new DefaultCamelContext(registry);
When I inject CamelContext the components like actionProcess, jpa are not recognized. when in my Route I have
.to("bean:actionProcessor?method=myMethod(${body})")
but my bean does not get executed.
I documentation I read use # before components name which are in registry but still it is not working.
Please suggest how can I achieve this using camel-cdi.
Did you already try with creating a CdiCamelContext (a subclass of DefaultCamelContext) ?
Otherwise, a more elegant would be to annotate your various classes, eg:
#Named("actionProcessor")
public class MyActionProcessor{
...
}
We have been using this for years without any problem
public class ContextFactory {
#Produces
#ApplicationScoped
#ContextName("Demo")
static final CamelContext createContext() {
CdiCamelContext context = new CdiCamelContext();
context.setStreamCaching(true);
context.disableJMX();
return context;
}
}
#ContextName("Demo")
public class MyRouteBuilder extends RouteBuilder {
from("...")
.to("bean:actionProcessor?method=myMethod")
}
#Named("actionProcessor")
public class MyActionProcessor{
public void myMethod(#Body String body) {}
}
Of course, in order to work, you need to activate the JEE bean discovery (=add a "beans.xml" file in META-INF or WEB-INF) !

How to pass parameters to a Camel route?

It is possible to pass parameters to a Camel route?, for instance, in the next code snippet:
public class MyRoute extends RouteBuilder {
public void configure() throws Exception {
from("direct:start")
.to("cxf:bean:inventoryEndpoint?dataFormat=PAYLOAD");
}
}
The value for dataFormat is in hard code, but, what if I want set it dynamically?, passing a value from the code where route is called. I know this is possible adding a constructor and passing parameters in it, like this:
public class MyRoute extends RouteBuilder {
private String type;
public MyRoute(String type){
this.type = type;
}
public void configure() throws Exception {
from("direct:start")
.to("cxf:bean:inventoryEndpoint?dataFormat=" + type);
}
}
There is another way?
Thanks so much!
As you mentioned, you can use a constructor (or setters or any other Java/Framework instruments) if the parameters are static from a Camel point of view.
The parameters are configurable in the application, but after the application is started they do no more change. So, every message processed by the Camel route uses the same value.
In contrast, when the parameters are dynamic - i.e. they can change for every processed message, you can use the dynamic endpoint toD() of Camel. These endpoint addresses can contain expressions that are computed on runtime. For example the route
from("direct:start")
.toD("${header.foo}");
sends messages to a dynamic endpoint and takes the value from the message header named foo.
Or to use your example
.toD("cxf:bean:inventoryEndpoint?dataFormat=${header.dataFormat}");
This way you can set the dataformat for every message individually through a header.
You can find more about dynamic endpoints on this Camel documentation page

Issue with Static resources when extending Spring Boot WebMvcConfigurationSupport

I extended WebMvcConfigurationSupport to implement an api versioning scheme - i.e.
#Configuration
public class ApiVersionConfiguration extends WebMvcConfigurationSupport {
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new ApiVersionRequestMappingHandlerMapping(readDateToVersionMap());
}}
This uses a custom handler mapping to version the api and works quite nicely.
However it also seems to disable the #EnableAutoConfiguration bean so that now static resources aren't served (as mentioned in this question Is it possible to extend WebMvcConfigurationSupport and use WebMvcAutoConfiguration?).
Ok, I thought, let's just add a resource handler to the class above - i.e.
#Configuration
public class ApiVersionConfiguration extends WebMvcConfigurationSupport {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("classpath:/public/").addResourceLocations("/");
}
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
return new ApiVersionRequestMappingHandlerMapping(readDateToVersionMap());
}}
However.. this isn't working..? I get this error when I browse to /index.html:
No mapping found for HTTP request with URI [/index.html] in DispatcherServlet with name 'dispatcherServlet'
..If I disable this class then these resources are served just fine by #EnableAutoConfiguration magic.
I've been playing with various options to serve static content having extended the WebMvcConfigurationSupport and thus far no success.
Any ideas?
I was facing the same problem and came up with a solution that just works for me. If you just want to get the resources working without worrying of repetition you can do:
#Configuration
public class StaticResourcesConfig extends WebMvcAutoConfigurationAdapter {
}
and then
#Configuration
#EnableWebMvc
#Import(StaticResourcesConfig.class)
public class WebConfig extends WebMvcConfigurationSupport {
...
}
This successfully uses the Spring Boot defaults for serving static resources, as long as you don't map /** in your controllers.

Is there a way to override a processor during unit testing?

I'm trying to write a unit test for one of my camel routes. In the route there is a processor that I would like to replace with a stub. Is there a way I can do this? I'm thinking of using the intercept feature but I can't seem to nail down the best way.
Example:
from(start)
.process(myprocessor)
.to(end)
Thanks in advance.
Yes you can do that by using Camel Advicedwith weaveById functionality which is used to replace the node during testing.
you have to set the id for your processor in the route and using that id you can weave whatever you want. Below is the example,
#Before
protected void weaveMockPoints() throws Exception{
context.getRouteDefinition("Route_ID").adviceWith(context,new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveById("myprocessorId").replace().to(someEndpoint);
}
});
context().start();
}
Only thing is, you have to apply this to the route which is not started already. Better do the change whatever you want and then start your camelcontext as the above example.
IMHO, you need implementation of Detour EIP (http://camel.apache.org/detour.html).
from(start)
.when().method("controlBean", "isDetour")
.to("mock:detour")
.endChoice()
.otherwise()
.process(myprocessor)
.end()
.to(end)
First you need extends CamelTestSupport:
class MyTest extends CamelTestSupport {}
after in your test method:
context.getRouteDefinitions().get(0).adviceWith (context, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveById("myprocessorId")
.replace().to("mock:myprocessor");
}
}
And in your route:
from(start)
.process(myprocessor).id("myprocessorId")
.to(end)
regards

What are the pros/cons when defining an Endpoint as a URI over defining it programatically?

Consider the following:
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
FileEndpoint dropLocation = new FileEndpoint();
dropLocation.setCamelContext(getContext());
dropLocation.setFile(new File("/data"));
dropLocation.setRecursive(true);
dropLocation.setPreMove(".polled");
dropLocation.setNoop(true);
dropLocation.setMaxMessagesPerPoll(1);
from(dropLocation).to(...
versus
public class MyBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
from("file://data?recursive=true&preMove=.polled&noop=true&maxMessagesPerPoll=1").to(...
Programatically I get code completion and the like, whereas with the URI everything is in a single line. Are these the only pros/cons or are there others to consider?
Pretty much all the examples I see utilise the URI method - is there a strong reason for this?
generally you rely on the Component to create the Endpoint instance (via route definitions), but it can be done programmatically if there is a desire to integrate with legacy code, create Endpoints through class structures/instances, etc.
overall, a major benefit of Camel is to leverage it's concise DSL route capabilities to describe processes/interactions all in one place (a route). the more programmatic the route definitions are, the more verbose/spread out these definitions become...
overall, I prefer the URI approach because its more concise, easier to follow and nice to manipulate route parameters all in one place...otherwise, its entirely a preference/style decision.

Resources