이 콘텐츠는 선택한 언어로 제공되지 않습니다.
Chapter 34. Understanding Message Formats
Abstract
Before you can begin programming with Apache Camel, you should have a clear understanding of how messages and message exchanges are modelled. Because Apache Camel can process many message formats, the basic message type is designed to have an abstract format. Apache Camel provides the APIs needed to access and transform the data formats that underly message bodies and message headers.
34.1. Exchanges
Overview
An exchange object is a wrapper that encapsulates a received message and stores its associated metadata (including the exchange properties). In addition, if the current message is dispatched to a producer endpoint, the exchange provides a temporary slot to hold the reply (the Out message).
An important feature of exchanges in Apache Camel is that they support lazy creation of messages. This can provide a significant optimization in the case of routes that do not require explicit access to messages.
Figure 34.1. Exchange Object Passing through a Route
Figure 34.1, “Exchange Object Passing through a Route” shows an exchange object passing through a route. In the context of a route, an exchange object gets passed as the argument of the Processor.process()
method. This means that the exchange object is directly accessible to the source endpoint, the target endpoint, and all of the processors in between.
The Exchange interface
The org.apache.camel.Exchange interface defines methods to access In and Out messages, as shown in Example 34.1, “Exchange Methods”.
Example 34.1. Exchange Methods
// Access the In message Message getIn(); void setIn(Message in); // Access the Out message (if any) Message getOut(); void setOut(Message out); boolean hasOut(); // Access the exchange ID String getExchangeId(); void setExchangeId(String id);
For a complete description of the methods in the Exchange interface, see Section 43.1, “The Exchange Interface”.
Lazy creation of messages
Apache Camel supports lazy creation of In, Out, and Fault messages. This means that message instances are not created until you try to access them (for example, by calling getIn()
or getOut()
). The lazy message creation semantics are implemented by the org.apache.camel.impl.DefaultExchange
class.
If you call one of the no-argument accessors (getIn()
or getOut()
), or if you call an accessor with the boolean argument equal to true
(that is, getIn(true)
or getOut(true)
), the default method implementation creates a new message instance, if one does not already exist.
If you call an accessor with the boolean argument equal to false
(that is, getIn(false)
or getOut(false)
), the default method implementation returns the current message value.[1]
Lazy creation of exchange IDs
Apache Camel supports lazy creation of exchange IDs. You can call getExchangeId()
on any exchange to obtain a unique ID for that exchange instance, but the ID is generated only when you actually call the method. The DefaultExchange.getExchangeId()
implementation of this method delegates ID generation to the UUID generator that is registered with the CamelContext
.
For details of how to register UUID generators with the CamelContext
, see Section 34.4, “Built-In UUID Generators”.
34.2. Messages
Overview
Message objects represent messages using the following abstract model:
- Message body
- Message headers
- Message attachments
The message body and the message headers can be of arbitrary type (they are declared as type Object
) and the message attachments are declared to be of type javax.activation.DataHandler
, which can contain arbitrary MIME types. If you need to obtain a concrete representation of the message contents, you can convert the body and headers to another type using the type converter mechanism and, possibly, using the marshalling and unmarshalling mechanism.
One important feature of Apache Camel messages is that they support lazy creation of message bodies and headers. In some cases, this means that a message can pass through a route without needing to be parsed at all.
The Message interface
The org.apache.camel.Message interface defines methods to access the message body, message headers and message attachments, as shown in Example 34.2, “Message Interface”.
Example 34.2. Message Interface
// Access the message body Object getBody(); <T> T getBody(Class<T> type); void setBody(Object body); <T> void setBody(Object body, Class<T> type); // Access message headers Object getHeader(String name); <T> T getHeader(String name, Class<T> type); void setHeader(String name, Object value); Object removeHeader(String name); Map<String, Object> getHeaders(); void setHeaders(Map<String, Object> headers); // Access message attachments javax.activation.DataHandler getAttachment(String id); java.util.Map<String, javax.activation.DataHandler> getAttachments(); java.util.Set<String> getAttachmentNames(); void addAttachment(String id, javax.activation.DataHandler content) // Access the message ID String getMessageId(); void setMessageId(String messageId);
For a complete description of the methods in the Message interface, see Section 44.1, “The Message Interface”.
Lazy creation of bodies, headers, and attachments
Apache Camel supports lazy creation of bodies, headers, and attachments. This means that the objects that represent a message body, a message header, or a message attachment are not created until they are needed.
For example, consider the following route that accesses the foo
message header from the In message:
from("SourceURL") .filter(header("foo") .isEqualTo("bar")) .to("TargetURL");
In this route, if we assume that the component referenced by SourceURL supports lazy creation, the In message headers are not actually parsed until the header("foo")
call is executed. At that point, the underlying message implementation parses the headers and populates the header map. The message body is not parsed until you reach the end of the route, at the to("TargetURL")
call. At that point, the body is converted into the format required for writing it to the target endpoint, TargetURL.
By waiting until the last possible moment before populating the bodies, headers, and attachments, you can ensure that unnecessary type conversions are avoided. In some cases, you can completely avoid parsing. For example, if a route contains no explicit references to message headers, a message could traverse the route without ever parsing the headers.
Whether or not lazy creation is implemented in practice depends on the underlying component implementation. In general, lazy creation is valuable for those cases where creating a message body, a message header, or a message attachment is expensive. For details about implementing a message type that supports lazy creation, see Section 44.2, “Implementing the Message Interface”.
Lazy creation of message IDs
Apache Camel supports lazy creation of message IDs. That is, a message ID is generated only when you actually call the getMessageId()
method. The DefaultExchange.getExchangeId()
implementation of this method delegates ID generation to the UUID generator that is registered with the CamelContext
.
Some endpoint implementations would call the getMessageId()
method implicitly, if the endpoint implements a protocol that requires a unique message ID. In particular, JMS messages normally include a header containing unique message ID, so the JMS component automatically calls getMessageId()
to obtain the message ID (this is controlled by the messageIdEnabled
option on the JMS endpoint).
For details of how to register UUID generators with the CamelContext
, see Section 34.4, “Built-In UUID Generators”.
Initial message format
The initial format of an In message is determined by the source endpoint, and the initial format of an Out message is determined by the target endpoint. If lazy creation is supported by the underlying component, the message remains unparsed until it is accessed explicitly by the application. Most Apache Camel components create the message body in a relatively raw form — for example, representing it using types such as byte[]
, ByteBuffer
, InputStream
, or OutputStream
. This ensures that the overhead required for creating the initial message is minimal. Where more elaborate message formats are required components usually rely on type converters or marshalling processors.
Type converters
It does not matter what the initial format of the message is, because you can easily convert a message from one format to another using the built-in type converters (see Section 34.3, “Built-In Type Converters”). There are various methods in the Apache Camel API that expose type conversion functionality. For example, the convertBodyTo(Class type)
method can be inserted into a route to convert the body of an In message, as follows:
from("SourceURL").convertBodyTo(String.class).to("TargetURL");
Where the body of the In message is converted to a java.lang.String
. The following example shows how to append a string to the end of the In message body:
from("SourceURL").setBody(bodyAs(String.class).append("My Special Signature")).to("TargetURL");
Where the message body is converted to a string format before appending a string to the end. It is not necessary to convert the message body explicitly in this example. You can also use:
from("SourceURL").setBody(body().append("My Special Signature")).to("TargetURL");
Where the append()
method automatically converts the message body to a string before appending its argument.
Type conversion methods in Message
The org.apache.camel.Message interface exposes some methods that perform type conversion explicitly:
-
getBody(Class<T> type)
— Returns the message body as type,T
. -
getHeader(String name, Class<T> type)
— Returns the named header value as type,T
.
For the complete list of supported conversion types, see Section 34.3, “Built-In Type Converters”.
Converting to XML
In addition to supporting conversion between simple types (such as byte[]
, ByteBuffer
, String
, and so on), the built-in type converter also supports conversion to XML formats. For example, you can convert a message body to the org.w3c.dom.Document
type. This conversion is more expensive than the simple conversions, because it involves parsing the entire message and then creating a tree of nodes to represent the XML document structure. You can convert to the following XML document types:
-
org.w3c.dom.Document
-
javax.xml.transform.sax.SAXSource
XML type conversions have narrower applicability than the simpler conversions. Because not every message body conforms to an XML structure, you have to remember that this type conversion might fail. On the other hand, there are many scenarios where a router deals exclusively with XML message types.
Marshalling and unmarshalling
Marshalling involves converting a high-level format to a low-level format, and unmarshalling involves converting a low-level format to a high-level format. The following two processors are used to perform marshalling or unmarshalling in a route:
-
marshal()
-
unmarshal()
For example, to read a serialized Java object from a file and unmarshal it into a Java object, you could use the route definition shown in Example 34.3, “Unmarshalling a Java Object”.
Example 34.3. Unmarshalling a Java Object
from("file://tmp/appfiles/serialized") .unmarshal() .serialization() .<FurtherProcessing> .to("TargetURL");
Final message format
When an In message reaches the end of a route, the target endpoint must be able to convert the message body into a format that can be written to the physical endpoint. The same rule applies to Out messages that arrive back at the source endpoint. This conversion is usually performed implicitly, using the Apache Camel type converter. Typically, this involves converting from a low-level format to another low-level format, such as converting from a byte[]
array to an InputStream
type.
34.3. Built-In Type Converters
Overview
This section describes the conversions supported by the master type converter. These conversions are built into the Apache Camel core.
Usually, the type converter is called through convenience functions, such as Message.getBody(Class<T> type)
or Message.getHeader(String name, Class<T> type)
. It is also possible to invoke the master type converter directly. For example, if you have an exchange object, exchange
, you could convert a given value to a String
as shown in Example 34.4, “Converting a Value to a String”.
Example 34.4. Converting a Value to a String
org.apache.camel.TypeConverter tc = exchange.getContext().getTypeConverter(); String str_value = tc.convertTo(String.class, value);
Basic type converters
Apache Camel provides built-in type converters that perform conversions to and from the following basic types:
-
java.io.File
-
String
-
byte[]
andjava.nio.ByteBuffer
-
java.io.InputStream
andjava.io.OutputStream
-
java.io.Reader
andjava.io.Writer
-
java.io.BufferedReader
andjava.io.BufferedWriter
-
java.io.StringReader
However, not all of these types are inter-convertible. The built-in converter is mainly focused on providing conversions from the File
and String
types. The File
type can be converted to any of the preceding types, except Reader
, Writer
, and StringReader
. The String
type can be converted to File
, byte[]
, ByteBuffer
, InputStream
, or StringReader
. The conversion from String
to File
works by interpreting the string as a file name. The trio of String
, byte[]
, and ByteBuffer
are completely inter-convertible.
You can explicitly specify which character encoding to use for conversion from byte[]
to String
and from String
to byte[]
by setting the Exchange.CHARSET_NAME
exchange property in the current exchange. For example, to perform conversions using the UTF-8 character encoding, call exchange.setProperty("Exchange.CHARSET_NAME", "UTF-8")
. The supported character sets are described in the java.nio.charset.Charset
class.
Collection type converters
Apache Camel provides built-in type converters that perform conversions to and from the following collection types:
-
Object[]
-
java.util.Set
-
java.util.List
All permutations of conversions between the preceding collection types are supported.
Map type converters
Apache Camel provides built-in type converters that perform conversions to and from the following map types:
-
java.util.Map
-
java.util.HashMap
-
java.util.Hashtable
-
java.util.Properties
The preceding map types can also be converted into a set, of java.util.Set
type, where the set elements are of the MapEntry<K,V>
type.
DOM type converters
You can perform type conversions to the following Document Object Model (DOM) types:
-
org.w3c.dom.Document
— convertible frombyte[]
,String
,java.io.File
, andjava.io.InputStream
. -
org.w3c.dom.Node
-
javax.xml.transform.dom.DOMSource
— convertible fromString
. -
javax.xml.transform.Source
— convertible frombyte[]
andString
.
All permutations of conversions between the preceding DOM types are supported.
SAX type converters
You can also perform conversions to the javax.xml.transform.sax.SAXSource
type, which supports the SAX event-driven XML parser (see the SAX Web site for details). You can convert to SAXSource
from the following types:
-
String
-
InputStream
-
Source
-
StreamSource
-
DOMSource
enum type converter
Camel provides a type converter for performing String
to enum
type conversions, where the string value is converted to the matching enum
constant from the specified enumeration class (the matching is case-insensitive). This type converter is rarely needed for converting message bodies, but it is frequently used internally by Apache Camel to select particular options.
For example, when setting the logging level option, the following value, INFO
, is converted into an enum
constant:
<to uri="log:foo?level=INFO"/>
Because the enum
type converter is case-insensitive, any of the following alternatives would also work:
<to uri="log:foo?level=info"/> <to uri="log:foo?level=INfo"/> <to uri="log:foo?level=InFo"/>
Custom type converters
Apache Camel also enables you to implement your own custom type converters. For details on how to implement a custom type converter, see Chapter 36, Type Converters.
34.4. Built-In UUID Generators
Overview
Apache Camel enables you to register a UUID generator in the CamelContext
. This UUID generator is then used whenever Apache Camel needs to generate a unique ID — in particular, the registered UUID generator is called to generate the IDs returned by the Exchange.getExchangeId()
and the Message.getMessageId()
methods.
For example, you might prefer to replace the default UUID generator, if part of your application does not support IDs with a length of 36 characters (like Websphere MQ). Also, it can be convenient to generate IDs using a simple counter (see the SimpleUuidGenerator
) for testing purposes.
Provided UUID generators
You can configure Apache Camel to use one of the following UUID generators, which are provided in the core:
-
org.apache.camel.impl.ActiveMQUuidGenerator
— (Default) generates the same style of ID as is used by Apache ActiveMQ. This implementation might not be suitable for all applications, because it uses some JDK APIs that are forbidden in the context of cloud computing (such as the Google App Engine). -
org.apache.camel.impl.SimpleUuidGenerator
— implements a simple counter ID, starting at1
. The underlying implementation uses thejava.util.concurrent.atomic.AtomicLong
type, so that it is thread-safe. -
org.apache.camel.impl.JavaUuidGenerator
— implements an ID based on thejava.util.UUID
type. Becausejava.util.UUID
is synchronized, this might affect performance on some highly concurrent systems.
Custom UUID generator
To implement a custom UUID generator, implement the org.apache.camel.spi.UuidGenerator
interface, which is shown in Example 34.5, “UuidGenerator Interface”. The generateUuid()
must be implemented to return a unique ID string.
Example 34.5. UuidGenerator Interface
// Java package org.apache.camel.spi; /** * Generator to generate UUID strings. */ public interface UuidGenerator { String generateUuid(); }
Specifying the UUID generator using Java
To replace the default UUID generator using Java, call the setUuidGenerator()
method on the current CamelContext
object. For example, you can register a SimpleUuidGenerator
instance with the current CamelContext
, as follows:
// Java getContext().setUuidGenerator(new org.apache.camel.impl.SimpleUuidGenerator());
The setUuidGenerator()
method should be called during startup, before any routes are activated.
Specifying the UUID generator using Spring
To replace the default UUID generator using Spring, all you need to do is to create an instance of a UUID generator using the Spring bean
element. When a camelContext
instance is created, it automatically looks up the Spring registry, searching for a bean that implements org.apache.camel.spi.UuidGenerator
. For example, you can register a SimpleUuidGenerator
instance with the CamelContext
as follows:
<beans ...> <bean id="simpleUuidGenerator" class="org.apache.camel.impl.SimpleUuidGenerator" /> <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> ... </camelContext> ... </beans>