One of the key advantages of using Java for defining routes is the ability to define the same, or similar, routing logic multiple times in your integrations, while changing key elements.
Consider the case of a route that:
Now consider that you may have orders from dozens of different countries, with different order and confirmation directories, and different date formats.
You could write similar routes dozens of times, but that is going to create a maintenance problem. Alternatively, using Camel's Java DSL, you can write the common routing logic once, and then use dependency injection to vary the values that are different when you instantiate the route.
This recipe will show you a strategy for creating Camel routes that can be created with different values at runtime, parameterizing your common routing logic.
Define your route within a RouteBuilder
as usual, only this time make the start and end URIs, as well as any beans involved in the processing, properties of the RouteBuilder
class:
public class OrderProcessingRouteBuilder extends RouteBuilder { String inputUri; String outputUri; private OrderFileNameProcessor orderFileNameProcessor; @Override public void configure() throws Exception { from(inputUri) // split into individual lines .split(body(String.class).tokenize(" ")) .process(orderFileNameProcessor) .log("Writing file: ${header.CamelFileName}") .to(outputUri) .end(); } }
The Java code for this recipe is located in the org.camelcookbook.structuringroutes.templating
package. The Spring XML files are located under src/main/resources/META-INF/spring
and prefixed with templating
.
Use property setters on your RouteBuilder
implementation to instantiate multiple instances of the route with different property values injected.
public void setInputDirectory(String inputDirectory) { inputUri = "file://" + inputDirectory; } public void setOutputDirectory(String outputDirectory) { outputUri = "file://" + outputDirectory; } public void setOrderFileNameProcessor( OrderFileNameProcessor orderFileNameProcessor) { this.orderFileNameProcessor = orderFileNameProcessor; }
org.apache.commons.lang.Validate
class to check for nulls and empty Strings:@PostConstruct public void checkMandatoryProperties() { Validate.notEmpty(inputUri, "inputUri is empty"); Validate.notEmpty(outputUri, "outputUri is empty"); Validate.notNull(orderFileNameProcessor, "orderFileNameProcessor is null"); }
Processor
that parses dates from a line of CSV text, changes the date to a universal format, and sets a header with the output filename. We encapsulate this logic in a class whose instances vary by a date format that is injected. The source for this class is available in the example code under: org.camelcookbook.structuringroutes.templating.OrderFileNameProcessor
.<bean id="dateFirstOrderFileNameProcessor" class="org.camelcookbook.structuringroutes.templating.OrderFileNameProcessor"> <property name="countryDateFormat" value="dd-MM-yyyy"/> </bean> <bean id="monthFirstOrderFileNameProcessor" class="org.camelcookbook.structuringroutes.templating.OrderFileNameProcessor"> <property name="countryDateFormat" value="MM-dd-yyyy"/> </bean>
RouteBuilder
s and inject them into the Camel context:<bean id="ukOrdersRouteBuilder" class="org.camelcookbook.structuringroutes.templating.OrderProcessingRouteBuilder"> <property name="inputDirectory" value="/orders/in/UK"/> <property name="outputDirectory" value="/orders/out/UK"/> <property name="orderFileNameProcessor" ref="dateFirstOrderFileNameProcessor"/> </bean> <bean id="usOrdersRouteBuilder" class="org.camelcookbook.structuringroutes.templating.OrderProcessingRouteBuilder"> <property name="inputDirectory" value="/orders/in/US"/> <property name="outputDirectory" value="/orders/out/US"/> <property name="orderFileNameProcessor" ref="monthFirstOrderFileNameProcessor"/> </bean> <camelContext xmlns="http://camel.apache.org/schema/spring"> <routeBuilder ref="ukOrdersRouteBuilder"/> <routeBuilder ref="usOrdersRouteBuilder"/> </camelContext>
By treating our RouteBuilder
implementation as just another bean to use in a Spring context, we were able to instantiate it multiple times, introducing varying behavior by changing the injected values. In the future, if we were to change the routing logic, perhaps by adding more logging, it would all be done in one place in the code.
When we defined our URI properties in the RouteBuilder
, we set them as package scoped. This is a handy strategy that allows us to inject endpoint types from within the same package that are not file:
endpoints, which are set when our public setter methods are used. Since test classes are typically co-located in the same package, this allows us to initialize our RouteBuilder
with more easily testable endpoints:
OrderFileNameProcessor processor = new OrderFileNameProcessor(); processor.setCountryDateFormat("dd-MM-yyyy"); OrderProcessingRouteBuilder routeBuilder = new OrderProcessingRouteBuilder(); routeBuilder.inputUri = "direct:in"; routeBuilder.outputUri = "mock:out"; routeBuilder.setOrderFileNameProcessor(processor);
See Chapter 9, Testing, for more details on testing.
18.227.102.159