Red Hat Training

A Red Hat training course is available for Red Hat Fuse

Chapter 36. Type Converters

Abstract

Apache Camel has a built-in type conversion mechanism, which is used to convert message bodies and message headers to different types. This chapter explains how to extend the type conversion mechanism by adding your own custom converter methods.

36.1. Type Converter Architecture

Overview

This section describes the overall architecture of the type converter mechanism, which you must understand, if you want to write custom type converters. If you only need to use the built-in type converters, see Chapter 34, Understanding Message Formats.

Type converter interface

Example 36.1, “TypeConverter Interface” shows the definition of the org.apache.camel.TypeConverter interface, which all type converters must implement.

Example 36.1. TypeConverter Interface

package org.apache.camel;

public interface TypeConverter {
    <T> T convertTo(Class<T> type, Object value);
}

Master type converter

The Apache Camel type converter mechanism follows a master/slave pattern. There are many slave type converters, which are each capable of performing a limited number of type conversions, and a single master type converter, which aggregates the type conversions performed by the slaves. The master type converter acts as a front-end for the slave type converters. When you request the master to perform a type conversion, it selects the appropriate slave and delegates the conversion task to that slave.

For users of the type conversion mechanism, the master type converter is the most important because it provides the entry point for accessing the conversion mechanism. During start up, Apache Camel automatically associates a master type converter instance with the CamelContext object. To obtain a reference to the master type converter, you call the CamelContext.getTypeConverter() method. For example, if you have an exchange object, exchange, you can obtain a reference to the master type converter as shown in Example 36.2, “Getting a Master Type Converter”.

Example 36.2. Getting a Master Type Converter

org.apache.camel.TypeConverter tc = exchange.getContext().getTypeConverter();

Type converter loader

The master type converter uses a type converter loader to populate the registry of slave type converters. A type converter loader is any class that implements the TypeConverterLoader interface. Apache Camel currently uses only one kind of type converter loader — the annotation type converter loader (of AnnotationTypeConverterLoader type).

Type conversion process

Figure 36.1, “Type Conversion Process” gives an overview of the type conversion process, showing the steps involved in converting a given data value, value, to a specified type, toType.

Figure 36.1. Type Conversion Process

Type conversion process

The type conversion mechanism proceeds as follows:

  1. The CamelContext object holds a reference to the master TypeConverter instance. The first step in the conversion process is to retrieve the master type converter by calling CamelContext.getTypeConverter().
  2. Type conversion is initiated by calling the convertTo() method on the master type converter. This method instructs the type converter to convert the data object, value, from its original type to the type specified by the toType argument.
  3. Because the master type converter is a front end for many different slave type converters, it looks up the appropriate slave type converter by checking a registry of type mappings The registry of type converters is keyed by a type mapping pair (toType, fromType). If a suitable type converter is found in the registry, the master type converter calls the slave’s convertTo() method and returns the result.
  4. If a suitable type converter cannot be found in the registry, the master type converter loads a new type converter, using the type converter loader.
  5. The type converter loader searches the available JAR libraries on the classpath to find a suitable type converter. Currently, the loader strategy that is used is implemented by the annotation type converter loader, which attempts to load a class annotated by the org.apache.camel.Converter annotation. See the section called “Create a TypeConverter file”.
  6. If the type converter loader is successful, a new slave type converter is loaded and entered into the type converter registry. This type converter is then used to convert the value argument to the toType type.
  7. If the data is successfully converted, the converted data value is returned. If the conversion does not succeed, null is returned.

36.2. Handling Duplicate Type Converters

You can configure what must happen if a duplicate type converter is added.

In the TypeConverterRegistry (See Section 36.3, “Implementing Type Converter Using Annotations”) you can set the action to Override, Ignore or Fail using the following code:

typeconverterregistry = camelContext.getTypeConverter()
// Define the behaviour if the TypeConverter already exists
typeconverterregistry.setTypeConverterExists(TypeConverterExists.Override);

Override in this code can be replaced by Ignore or Fail, depending on your requirements.

TypeConverterExists Class

The TypeConverterExists class consists of the following commands:

package org.apache.camel;

import javax.xml.bind.annotation.XmlEnum;

/**
 * What to do if attempting to add a duplicate type converter
 *
 * @version
 */
@XmlEnum
public enum TypeConverterExists {

    Override, Ignore, Fail

}

36.3. Implementing Type Converter Using Annotations

Overview

The type conversion mechanism can easily be customized by adding a new slave type converter. This section describes how to implement a slave type converter and how to integrate it with Apache Camel, so that it is automatically loaded by the annotation type converter loader.

How to implement a type converter

To implement a custom type converter, perform the following steps:

Implement an annotated converter class

You can implement a custom type converter class using the @Converter annotation. You must annotate the class itself and each of the static methods intended to perform type conversion. Each converter method takes an argument that defines the from type, optionally takes a second Exchange argument, and has a non-void return value that defines the to type. The type converter loader uses Java reflection to find the annotated methods and integrate them into the type converter mechanism. Example 36.3, “Example of an Annotated Converter Class” shows an example of an annotated converter class that defines a converter method for converting from java.io.File to java.io.InputStream and another converter method (with an Exchange argument) for converting from byte[] to String.

Example 36.3. Example of an Annotated Converter Class

package com.YourDomain.YourPackageName;

import org.apache.camel.Converter;

import java.io.*;

@Converter
public class IOConverter {
    private IOConverter() {
    }

    @Converter
    public static InputStream toInputStream(File file) throws FileNotFoundException {
        return new BufferedInputStream(new FileInputStream(file));
    }

    @Converter
    public static String toString(byte[] data, Exchange exchange) {
        if (exchange != null) {
            String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
            if (charsetName != null) {
                try {
                    return new String(data, charsetName);
                } catch (UnsupportedEncodingException e) {
                    LOG.warn("Can't convert the byte to String with the charset " + charsetName, e);
                }
            }
        }
        return new String(data);
    }
}

The toInputStream() method is responsible for performing the conversion from the File type to the InputStream type and the toString() method is responsible for performing the conversion from the byte[] type to the String type.

Note

The method name is unimportant, and can be anything you choose. What is important are the argument type, the return type, and the presence of the @Converter annotation.

Create a TypeConverter file

To enable the discovery mechanism (which is implemented by the annotation type converter loader) for your custom converter, create a TypeConverter file at the following location:

META-INF/services/org/apache/camel/TypeConverter

The TypeConverter file must contain a comma-separated list of Fully Qualified Names (FQN) of type converter classes. For example, if you want the type converter loader to search the YourPackageName.YourClassName package for annotated converter classes, the TypeConverter file would have the following contents:

com.PackageName.FooClass

An alternative method of enabling the discovery mechanism is to add just package names to the TypeConverter file. For example, the TypeConverter file would have the following contents:

com.PackageName

This would cause the package scanner to scan through the packages for the @Converter tag. Using the FQN method is faster and is the preferred method.

Package the type converter

The type converter is packaged as a JAR file containing the compiled classes of your custom type converters and the META-INF directory. Put this JAR file on your classpath to make it available to your Apache Camel application.

Fallback converter method

In addition to defining regular converter methods using the @Converter annotation, you can optionally define a fallback converter method using the @FallbackConverter annotation. The fallback converter method will only be tried, if the master type converter fails to find a regular converter method in the type registry.

The essential difference between a regular converter method and a fallback converter method is that whereas a regular converter is defined to perform conversion between a specific pair of types (for example, from byte[] to String), a fallback converter can potentially perform conversion between any pair of types. It is up to the code in the body of the fallback converter method to figure out which conversions it is able to perform. At run time, if a conversion cannot be performed by a regular converter, the master type converter iterates through every available fallback converter until it finds one that can perform the conversion.

The method signature of a fallback converter can have either of the following forms:

// 1. Non-generic form of signature
@FallbackConverter
public static Object MethodName(
    Class type,
    Exchange exchange,
    Object value,
    TypeConverterRegistry registry
)

// 2. Templating form of signature
@FallbackConverter
public static <T> T MethodName(
    Class<T> type,
    Exchange exchange,
    Object value,
    TypeConverterRegistry registry
)

Where MethodName is an arbitrary method name for the fallback converter.

For example, the following code extract (taken from the implementation of the File component) shows a fallback converter that can convert the body of a GenericFile object, exploiting the type converters already available in the type converter registry:

package org.apache.camel.component.file;

import org.apache.camel.Converter;
import org.apache.camel.FallbackConverter;
import org.apache.camel.Exchange;
import org.apache.camel.TypeConverter;
import org.apache.camel.spi.TypeConverterRegistry;

@Converter
public final class GenericFileConverter {

    private GenericFileConverter() {
        // Helper Class
    }

    @FallbackConverter
    public static <T> T convertTo(Class<T> type, Exchange exchange, Object value, TypeConverterRegistry registry) {
        // use a fallback type converter so we can convert the embedded body if the value is GenericFile
        if (GenericFile.class.isAssignableFrom(value.getClass())) {
            GenericFile file = (GenericFile) value;
            Class from = file.getBody().getClass();
            TypeConverter tc = registry.lookup(type, from);
            if (tc != null) {
                Object body = file.getBody();
                return tc.convertTo(type, exchange, body);
            }
        }

        return null;
    }
    ...
}

36.4. Implementing a Type Converter Directly

Overview

Generally, the recommended way to implement a type converter is to use an annotated class, as described in the previous section, Section 36.3, “Implementing Type Converter Using Annotations”. But if you want to have complete control over the registration of your type converter, you can implement a custom slave type converter and add it directly to the type converter registry, as described here.

Implement the TypeConverter interface

To implement your own type converter class, define a class that implements the TypeConverter interface. For example, the following MyOrderTypeConverter class converts an integer value to a MyOrder object, where the integer value is used to initialize the order ID in the MyOrder object.

import org.apache.camel.TypeConverter

private class MyOrderTypeConverter implements TypeConverter {

    public <T> T convertTo(Class<T> type, Object value) {
        // converter from value to the MyOrder bean
        MyOrder order = new MyOrder();
        order.setId(Integer.parseInt(value.toString()));
        return (T) order;
    }

    public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
        // this method with the Exchange parameter will be preferd by Camel to invoke
        // this allows you to fetch information from the exchange during convertions
        // such as an encoding parameter or the likes
        return convertTo(type, value);
    }

    public <T> T mandatoryConvertTo(Class<T> type, Object value) {
        return convertTo(type, value);
    }

    public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) {
        return convertTo(type, value);
    }
}

Add the type converter to the registry

You can add the custom type converter directly to the type converter registry using code like the following:

// Add the custom type converter to the type converter registry
context.getTypeConverterRegistry().addTypeConverter(MyOrder.class, String.class, new MyOrderTypeConverter());

Where context is the current org.apache.camel.CamelContext instance. The addTypeConverter() method registers the MyOrderTypeConverter class against the specific type conversion, from String.class to MyOrder.class.

You can add custom type converters to your Camel applications without having to use the META-INF file. If you are using Spring or Blueprint, then you can just declare a <bean>. CamelContext discovers the bean automatically and adds the converters.

<bean id="myOrderTypeConverters" class="..."/>
 <camelContext>
   ...
</camelContext>

You can declare multiple <bean>s if you have more classes.