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
The type conversion mechanism proceeds as follows:
-
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 callingCamelContext.getTypeConverter()
. -
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 thetoType
argument. -
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’sconvertTo()
method and returns the result. - 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.
-
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”. -
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 thetoType
type. -
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.
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.