Book Image

Apache Camel Developer's Cookbook

Book Image

Apache Camel Developer's Cookbook

Overview of this book

Apache Camel is a de-facto standard for developing integrations in Java, and is based on well-understood Enterprise Integration Patterns. It is used within many commercial and open source integration products. Camel makes common integration tasks easy while still providing the developer with the means to customize the framework when the situation demands it. Tasks such as protocol mediation, message routing and transformation, and auditing are common usages of Camel. Apache Camel Developer's Cookbook provides hundreds of best practice tips for using Apache Camel in a format that helps you build your Camel projects. Each tip or recipe provides you with the most important steps to perform along with a summary of how it works, with references to further reading if you need more information. This book is intended to be a reliable information source that is quicker to use than an Internet search. Apache Camel Developer's Cookbook is a quick lookup guide that can also be read from cover to cover if you want to get a sense of the full power of Apache Camel. This book provides coverage of the full lifecycle of creating Apache Camel-based integration projects, including the structure of your Camel code and using the most common Enterprise Integration patterns. Patterns like Split/Join and Aggregation are covered in depth in this book. Throughout this book, you will be learning steps to transform your data. You will also learn how to perform unit and integration testing of your code using Camel's extensive testing framework, and also strategies for debugging and monitoring your code. Advanced topics like Error Handling, Parallel Processing, Transactions, and Security will also be covered in this book. This book provides you with practical tips on using Apache Camel based on years of hands-on experience from hundreds of integration projects.
Table of Contents (20 chapters)
Apache Camel Developer's Cookbook
Credits
About the Authors
Acknowledgments
About the Reviewers
www.PacktPub.com
Preface
Index

Reusing routing logic through template routes


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:

  • Consumes bulk order data from CSV files in an input directory

  • Splits it into individual orders

  • Extracts the date of each order, formatted specific to the country, and converts it to a universal one

  • Places an order confirmation into another directory

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.

Getting ready

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("\n"))
        .process(orderFileNameProcessor)
        .log("Writing file: ${header.CamelFileName}")
        .to(outputUri)
      .end();
  }
}

Note

Note that the URI variables are defined as package scoped. This will help us to test the class later.

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.

How to do it...

Use property setters on your RouteBuilder implementation to instantiate multiple instances of the route with different property values injected.

  1. Add setters for the properties:

    public void setInputDirectory(String inputDirectory) {
      inputUri = "file://" + inputDirectory;
    }
    public void setOutputDirectory(String outputDirectory) {
      outputUri = "file://" + outputDirectory;
    }
    public void setOrderFileNameProcessor(
        OrderFileNameProcessor orderFileNameProcessor) {
      this.orderFileNameProcessor = orderFileNameProcessor;
    }

    Tip

    A useful trick is to construct the endpoint URIs within the setters. This way, when instantiating the RouteBuilder, you only have to worry about which directories to use, not about the various additional attributes that you want to repeat for the file component each time.

  2. Validate that the mandatory bean properties were set. The following code uses the 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");
    }

    Tip

    If you are using the RouteBuilder from Spring, add a @PostConstruct method to check that all of the properties have been set. This way, if all of the fields have not been initialized correctly, the application will refuse to start up.

  3. To complete the integration we need to add a 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.

  4. In your Spring XML file, you can now create multiple instances of this class:

    <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>
  5. We now have all of the pieces in place to perform the same integration for a number of countries that use different input and output directories, and date formats, within their order files. We can now go ahead and instantiate the RouteBuilders 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>

How it works...

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.

See also