Type Converter
It is very common when routing messages from one endpoint to another to need to convert the body payloads from one type to another such as to convert to and from the following common types:
-
File
-
String
-
byte[] and ByteBuffer
-
InputStream and OutputStream
-
Reader and Writer
-
Document and Source
-
…
The Message interface defines a helper method to allow conversions to be done via the getBody(Class) method.
So in an endpoint you can convert a body to another type via:
Message message = exchange.getIn();
Document document = message.getBody(Document.class);
How Type Conversion works
The type conversion strategy is defined by the TypeConverter interface that can be customized on a CamelContext.
Camel provides a default type converter registry. From Camel 3 onwards there type converter registry implementation uses source code generation via the Camel Maven Package Plugin to generate source code, that allows Camel at runtime to load and invoke these type converters via quick java method invocations. The older implementation in Camel 2 uses a annotation based discover to load and register type converters and invokes the type converters via Java method call reflections.
TypeConverterRegistry
Exposed the TypeConverterRegistry from CamelContext so end users more easily will be able to add type converters at runtime. This is also usable in situations where the default discovering of type converters fails on platforms with classloading issues.
To access the registry, you get it from the CamelContext
:
CamelContext context = ...
context.getTypeConverterRegistry()
TypeConverterRegistry utilization statistics
Camel can gather utilization statistics of the runtime usage of type
converters. These stats are available in JMX, and as well as from the
getStatistics()
method from TypeConverterRegistry
.
These statistics are turned off by default as there is some performance overhead under very high concurrent load. To enable the statistics in Java, do the following:
CamelContext context = ...
context.setTypeConverterStatisticsEnabled(true);
Or in the XML DSL with:
<camelContext xmlns="http://camel.apache.org/schema/spring" typeConverterStatisticsEnabled="true">
...
</camelContext>
Add type converter classes at runtime
You can add new type converters at runtime, by having your classes
implement org.apache.camel.TypeConverters
which is an marker
interface. Then for each type converter you want use
the @Converter
annotation.
private class MyOrderTypeConverters implements TypeConverters {
@Converter
public MyOrder toMyOrder(String orderId) {
MyOrder order = new MyOrder();
order.setId(Integer.parseInt(orderId));
return order;
}
}
Then you can add these converters to the registry using:
MyOrderTypeConverters myClass = ...
context.getTypeConverterRegistry().addTypeConverters(myClass);
If you are using Spring or Blueprint, then you can just declare a <bean>
then CamelContext
will automatic discover and add the converters.
<bean id="myOrderTypeConverters" class="..."/>
<camelContext ...>
...
</camelContext>
You can declare multiple `<bean>`s if you have more classes.
Discovering Type Converters
Camel will automatic discover and load type converters from all the JARs in the classpath on startup.
Camel will search the classpath for a file called
META-INF/services/org/apache/camel/TypeConverterLoader
, which lists
all type converter loader classes (they are automatic source code generated by the Camel Maven Package Plugin).
These loader classes will load the type converters into the Camel type converter registry
and invoking these type converters is done in a fast way using standard java method calls.
Discovering Camel 2.x based type converters (not loader)
Camel will not perform additional package scanning for type conveters which has no source code generated loader classes. However this can be enabled by setting:
camelContext.setLoadTypeConverters(true);
And in XML:
<camelContext loadTypeConverters="true">
...
</camelContext>
And in Spring Boot application.properties
:
camel.loadTypeConverters=true
Then Camel will discover discover Camel 2.x compatible type converters by
search the classpath for a file called META-INF/services/org/apache/camel/TypeConverter
,
which lists all the type converter classes. These classes will then automatic
be registered in the type converter registry. However invoking these type converters
does not happen in a fast way but uses Java method call reflection. It is therefore
recommended to migrate your type converters to use the faster way. See further below.
You can turn off the fallback of discovering Camel 2.x compatible type converters by
setting the loadTypeConverters option to false on CamelContext .
|
The converter classes must be annotated on the top class level with the @Converter
annotation,
and each converter method as well.
E.g. the following shows how to register a converter from File
to
InputStream
:
@Converter
public class IOConverter {
@Converter
public static InputStream toInputStream(File file) throws FileNotFoundException {
return new BufferedInputStream(new FileInputStream(file));
}
}
Discovering Type Converters in the fast way
To enable the fast type converter way, you should enable loader = true
on the class level annotation as shown:
@Converter(loader = true)
public class IOConverter {
@Converter
public static InputStream toInputStream(File file) throws FileNotFoundException {
return new BufferedInputStream(new FileInputStream(file));
}
}
And then you should have the Camel Maven Package Plugin in as build plugin when compiling the project.
Also add the build helper plugin which ensures the generated source code in src/generated
will be included in the source path.
When using Maven you add:
<plugin>
<groupId>org.apache.camel</groupId>
<artifactId>camel-package-maven-plugin</artifactId>
<version>${camel-version}</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate-component</goal>
</goals>
<phase>process-classes</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>add-source</goal>
<goal>add-resource</goal>
</goals>
<configuration>
<sources>
<source>src/generated/java</source>
</sources>
<resources>
<resource>
<directory>src/generated/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
Returning null values
By default when using a method in a POJO annotation with @Converter returning null is not a valid response. If null is returned, then Camel will regard that type converter as a miss, and prevent from using it in the future. If null should be allowed as a valid response, then from Camel 2.11.2/2.12 onwards you can specify this in the annotation as shown:
@Converter(allowNull = true)
public static InputStream toInputStream(File file) throws IOException {
if (file.exist()) {
return new BufferedInputStream(new FileInputStream(file));
} else {
return null;
}
}
Discovering Fallback Type Converters
Available in Camel 2.0
The
AnnotationTypeConverterLoader
has been enhanced to also look for methods defined with a
@FallbackConverter
annotation, and register it as a fallback type
converter.
Fallback type converters are used as a last resort for converting a given value to another type. Its used when the regular type converters give up. The fallback converters is also meant for a broader scope, so its method signature is a bit different:
@FallbackConverter
public static <T> T convertTo(Class<T> type, Exchange exchange, Object value, TypeConverterRegistry registry)
Or you can use the non generic signature.
@FallbackConverter
public static Object convertTo(Class type, Exchange exchange, Object value, TypeConverterRegistry registry)
And the method name can be anything (convertTo
is not required as a
name), so it can be named convertMySpecialTypes
if you like.
The Exchange
parameter is optional, just as its with the regular
@Converter
methods.
The purpose with this broad scope method signature is allowing you to
control if you can convert the given type or not. The type
parameter
holds the type we want the value
converted to. Its used internally in
Camel for wrapper objects so we can delegate the type convertions to the
body that is wrapped.
For instance in the method below we will handle all type conversions
that is based on the wrapper class GenericFile
and we let Camel do the
type conversions on its body instead.
@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;
}
Writing your own Type Converters
You are welcome to write your own converters. Remember to use the
@Converter
annotations on the classes and methods you wish to use.
And on the top-level class add Converter(loader = true)
to support the fast way
of using type converters.
-
static methods are encouraged to reduce caching, but instance methods are fine, particularly if you want to allow optional dependency injection to customize the converter
-
converter methods should be thread safe and reentrant
Exchange parameter
The type converter accepts the Exchange
as an optional 2nd parameter.
This is usable if the type converter for instance needs information from
the current exchange. For instance combined with the encoding support
its possible for type converters to convert with the configured
encoding. An example from camel-core for the byte[]
→ String
converter:
@Converter
public static String toString(byte[] data, Exchange exchange) {
String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class);
if (charsetName != null) {
try {
return new String(data, charsetName);
} catch (UnsupportedEncodingException e) {
// ignore
}
return new String(data);
}
}