Writing a custom data marshaller

Camel supports a pluggable ability to translate messages to and from binary and text formats through a feature called Data Formats. Data Formats are an implementation of the Message Translator Enterprise Integration Pattern.

Writing a custom data marshaller

Camel's libraries include a number of Data Formats, such as JAXB, JSON, CSV, HL7, Base64, Crypto, Gzip, and so on. This recipe describes how to write your own Data Format for instances when you need more than what Camel provides out of the box.

This recipe will show you how to create your own Data Format implementation that can be easily shared across your Camel routes.

Getting ready

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

How to do it...

There are two main steps for creating and using a custom Data Format. First, you need to create a Java class that implements the DataFormat interface, and second, you need to provide a reference to it within your route.

  1. Creating a custom Data Format primarily consists of creating a Java class that implements the org.apache.camel.spi.DataFormat interface, which has two methods: marshal and unmarshal. Typically, you marshal from Java to a lower-level data representation, and unmarshal from that representation up to Java. This is true in most cases, but may vary depending on what the intent of your Data Format is, such as in this recipe's example where we are converting a String from one character set to a different character set.

    The following example converts between EBCDIC and Java Strings (UTF-8), which may be useful for performing Mainframe integrations. It marshals from a String to EBCDIC, and unmarshals from EBCDIC to a String.

    public class EbcdicDataFormat implements DataFormat {
      // US EBCDIC 037 code page 
      private String codepage = "CP037";
    
      public EbcdicDataFormat() {}
    
      public EbcdicDataFormat(String codepage) {
        this.codepage = codepage;
      }
    
      @Override
      public void marshal(Exchange exchange,
                          Object graph, OutputStream stream)
          throws Exception {
        final String str =
            ExchangeHelper.convertToMandatoryType(exchange,
                String.class, graph);
        stream.write(str.getBytes(codepage));
      }
    
      @Override
      public Object unmarshal(Exchange exchange, 
          InputStream stream) throws Exception {
        final byte[] bytes =
          ExchangeHelper.convertToMandatoryType(exchange,
              byte[].class, stream);
        return new String(bytes, codepage);
      }
    }
  2. You then need to reference your custom Data Format within your routes using the marshal and unmarshal statements.

    In the XML DSL, use the custom element within the marshal and unmarshal blocks to reference the bean ID for your custom Data Format:

    <beans xmlns="http://www.springframework.org/schema/beans" ...>
      <bean id="ebcdic"
            class="org.camelcookbook.extend.dataformat.EbcdicDataFormat">
        <constructor-arg value="CP037"/>
      </bean>
    
      <camelContext 
          xmlns="http://camel.apache.org/schema/spring">
        <route>
          <from uri="direct:marshal"/>
          <marshal>
            <custom ref="ebcdic"/>
          </marshal>
        </route>
        <route>
          <from uri="direct:unmarshal"/>
          <unmarshal>
            <custom ref="ebcdic"/>
          </unmarshal>
        </route>
      </camelContext>
    </beans>

    In the Java DSL, you refer to an instance of the data format:

    public class EbcdicDataFormatRouteBuilder 
        extends RouteBuilder {
      @Override
      public void configure() throws Exception {
        EbcdicDataFormat dataFormat =
            new EbcdicDataFormat("CP037");
    
        from("direct:marshal").marshal(dataFormat);
    
        from("direct:unmarshal").unmarshal(dataFormat);
      }
    }

How it works...

The Data Format's marshal and unmarshal methods will be called from the route with the current message body. Your implementation will be expected to convert the message to or from your format.

In your marshal implementation, the message body is passed in the second parameter Object graph, converted to your custom Data Format, and the results streamed into the third parameter OutputStream stream.

The ExchangeHelper utility class provides a number of helpful methods, such as convertToMandatoryType, which will use Camel's data type converters to convert the current message body to the data type you specify, and throws an exception if that fails. The mechanism for the actual data translation performed by your custom Data Format is up to you.

The unmarshal implementation is similar. You obtain the current message body from the provided InputStream, and either return the result of the translation from your method, or place it on the exchange through exchange.getIn().setBody().

Note

It is worth noting that the functionality of EbcdicDataFormat shown in the preceding example can be achieved using Camel's built-in StringDataFormat. You would instantiate this built-in format with the String encoding you want to translate Java Strings (UTF-8) to and from, such as "CP037", which indicates the US/Latin-1 EBCDIC Code Page 000037. See the StringDataFormat documentation for more details (http://camel.apache.org/string.html).

There's more...

Camel supports streaming data so that large data messages can flow through the integration route using less memory and greater speed. When possible, the data is streamed (incrementally pulled) from the consuming endpoint, through the processor steps defined in the route, and then streamed to a producer endpoint. If your data translation logic supports working on incremental parts or streams of data, you should apply it to your custom Data Format in order to work well with streaming.

For example, here is how the GZip Data Format implements streaming. Notice how in the marshal method the input message body is converted to an InputStream.

public class GzipDataFormat implements DataFormat {
  public void marshal(Exchange exchange, Object graph,
                      OutputStream stream) throws Exception {
    InputStream is =
        exchange.getContext().getTypeConverter()
          .mandatoryConvertTo(InputStream.class, 
                              exchange, graph);

    GZIPOutputStream zipOutput = new GZIPOutputStream(stream);
    try {
      IOHelper.copy(is, zipOutput);
    } finally {
      // must close all input streams
      IOHelper.close(is, zipOutput);
    }
  }
  //...
}
..................Content has been hidden....................

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