Normalizing messages into a common XML format

This recipe will show you a strategy for normalizing input that arrives in many different data formats (CSV, JSON, and XML) into a single canonical XML format. The strategy shown here also allows you to handle unknown input formats.

This strategy uses the Normalizer EIP, which is a Content Based Router EIP (see the Content Based Routing recipe in Chapter 2, Message Routing) combined with Data Format translators. It detects the incoming message format, and performs one or more steps needed to transform the message to the canonical format. After the Content Based Router, all processing steps can now operate assuming the message is in canonical format.

Normalizing messages into a common XML format

Note

It the following example, a subtle point to note is that we are transforming the message into an object graph of JAXB objects—a parsed version of the XML message. See the Transforming from Java to XML with JAXB recipe for more details.

Getting ready

The Java code for this recipe is located in the org.camelcookbook.transformation.normalizer package. Spring XML files are located under src/main/resources/META-INF/spring and prefixed with normalizer.

How to do it...

The main idea behind normalizing messages is to use a Content Based Router (see the Content Based Routing recipe in Chapter 2, Message Routing) whose when statements identify and transform each of the different formats to be normalized:

  1. Lay out the Content Based Router, followed by processing steps that expect the canonical message format.

    In the XML DSL, this is written as:

    <route>
      <from uri="direct:start"/>
      <choice>
        <!-- normalize each input format here -->
      </choice>
      <to uri="mock:normalized"/>
    </route>

    In the Java DSL, the same thing is expressed as:

    public void configure() throws Exception {
      // setup Data Formats here
    
      from("direct:start")
        .choice()
          // normalize each input format here
        .end()
        .to("mock:normalized");
    }

    The mock:normalized endpoint should only receive messages as a graph of JAXB objects regardless of the format of the input message to this route.

  2. Handle CSV input messages, and convert them to our canonical model. See the Parsing comma-separated values (CSV) recipe for more details on the Bindy Data Format.

    In the XML DSL, this routing logic is written as:

    <choice>
      <when>
        <simple>
          ${header.CamelFileName} regex '.*.csv$'
        </simple>
        <unmarshal>
          <bindy type="Csv" 
                 packages="org.camelcookbook.transformation.csv.model"/>
        </unmarshal>
        <bean ref="myNormalizer" method="bookModelToJaxb"/>
        <to uri="mock:csv"/>
      </when>
      <!-- normalize other formats -->
    </choice>

    In that Java DSL, the same thing is written as follows:

    .choice()
      .when(header(Exchange.FILE_NAME).endsWith(".csv"))
        .unmarshal(bindy)
        .bean(MyNormalizer.class, "bookModelToJaxb")
        .to("mock:csv")
      // normalize other formats
    .end()

    The MyNormalizer Java call-out is to code that translates from the CSV Java model to JAXB.

    Note

    Note that the XML DSL uses the Simple Expression Language's regular expression operator (regex) to detect the filename extension, while the Java DSL uses the Header Expression with a value builder predicate. It felt more natural in the Java DSL to use a Java-like expression (endsWith) versus a regular expression. There is no "best" approach in situations like this; use whichever you feel best matches your needs.

  3. Handle JSON input messages, and convert them to our canonical model. See the Transforming from Java to JSON recipe for more details on the JSON Data Format.

    In the XML DSL, this is written as:

    <choice>
      <when>
        <simple>
          ${header.CamelFileName} regex '.*.json$'
        </simple>
        <unmarshal>
          <xmljson rootName="bookstore"
                   elementName="book"
                   expandableProperties="author author"/>
        </unmarshal>
        <to uri="mock:json"/>
      </when>
      <!-- normalize other formats -->
    </choice>

    In that Java DSL, this is written as follows:

    .choice()
      .when(header(Exchange.FILE_NAME).endsWith(".json"))
        .unmarshal(xmlJsonFormat)
        .to("mock:json")
      // normalize other formats
    .end()
  4. Handle XML input messages, and convert them to our canonical model. In this example, this means parsing the XML into Java using JAXB. See the Transforming from Java to XML with JAXB recipe for more details on the JAXB Data Format.

    In the XML DSL, this logic is written as:

    <choice>
      <when>
        <simple>
          ${header.CamelFileName} regex '.*.xml$'
        </simple>
        <unmarshal>
          <jaxb contextPath="org.camelcookbook.transformation.myschema"/>
        </unmarshal>
        <to uri="mock:xml"/>
      </when>
      <!-- normalize other formats -->
    </choice>

    In that Java DSL, this is written as follows:

    .choice()
      .when(header(Exchange.FILE_NAME).endsWith(".xml"))
        .unmarshal(jaxb)
        .to("mock:xml")
      // normalize other formats
    .end()
  5. Handle other unknown input messages by forwarding the message to some endpoint, such as a dead letter queue, and then using the stop statement to discontinue processing of this message within this route.

    In the XML DSL, this step is written as:

    <choice>
      <!-- normalize input formats -->
      <otherwise>
        <to uri="mock:unknown"/>
        <stop/>
      </otherwise>
    </choice>

    In that Java DSL, the same thing is expressed as:

    .choice()
      // normalize input formats
      .otherwise()
        .to("mock:unknown")
        .stop()
    .end()

How it works...

The main part of this recipe uses the Content Based Routing EIP to detect each incoming input message format, and performs the steps required to normalize that message to the desired canonical format.

A number of different types of expressions can be used to detect the incoming message format. The preceding example uses the CamelFileName header that is set by the File and FTP Components, to detect the input format based on the suffix of the filename. For a scenario with different XML inputs, using an XPath Expression and inspecting Namespaces might be a better way to normalize different versions of XML requests, followed by translations using XSLT or XQuery into the common XML format.

The otherwise portion of the Content Based Router is used in the example to catch other unknown input message formats that are not handled by the when statement's expressions. It is common in such cases to log or send the message to some external route or processor for later handling by a person or automated error-handling portion of your application. The route could also throw an exception, such as an IllegalArgumentException, if you want the caller to handle the issue. The Camel stop statement in this example tells Camel to stop processing, that is, to execute no more processing steps with this message within this route.

There's more...

In this example, within the processor steps for normalizing incoming CSV format messages, we see the usage of a Java callout to translate one Java object model to another. If this Java model conversation is common across your Camel routes, you could create and register a Camel Data Type Converter to allow Camel to automatically do the type conversation for you when needed. In this example, the Java callout to MyNormalizer could be replaced with the use of the Camel directive convertBodyTo(org.camelcookbook.transformation.myschema.Bookstore), which arguably is clearer as to what's happening within the route. See the Writing a custom data type converter recipe in Chapter 3, Routing to Your Code for more details.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.186.167