Chapter 34. Understanding Message Formats
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 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 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: 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: 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: 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: 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 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 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[] and java.nio.ByteBuffer java.io.InputStream and java.io.OutputStream java.io.Reader and java.io.Writer java.io.BufferedReader and java.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. Note 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 from byte[] , String , java.io.File , and java.io.InputStream . org.w3c.dom.Node javax.xml.transform.dom.DOMSource - convertible from String . javax.xml.transform.Source - convertible from byte[] and String . 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: Because the enum type converter is case-insensitive, any of the following alternatives would also work: 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 at 1 . The underlying implementation uses the java.util.concurrent.atomic.AtomicLong type, so that it is thread-safe. org.apache.camel.impl.JavaUuidGenerator - implements an ID based on the java.util.UUID type. Because java.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 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: Note 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: [1] If there is no active method the returned value will be null .
// 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); // 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); from(" SourceURL ") .filter(header("foo") .isEqualTo("bar")) .to(" TargetURL "); from(" SourceURL ").convertBodyTo(String.class).to(" TargetURL "); from(" SourceURL ").setBody(bodyAs(String.class).append("My Special Signature")).to(" TargetURL "); from(" SourceURL ").setBody(body().append("My Special Signature")).to(" TargetURL "); from("file://tmp/appfiles/serialized") .unmarshal() .serialization() . <FurtherProcessing> .to(" TargetURL "); org.apache.camel.TypeConverter tc = exchange.getContext().getTypeConverter(); String str_value = tc.convertTo(String.class, value); <to uri="log:foo?level=INFO"/> <to uri="log:foo?level=info"/> <to uri="log:foo?level=INfo"/> <to uri="log:foo?level=InFo"/> // Java package org.apache.camel.spi; /** * Generator to generate UUID strings. */ public interface UuidGenerator { String generateUuid(); } // Java getContext().setUuidGenerator(new org.apache.camel.impl.SimpleUuidGenerator()); <beans ...> <bean id="simpleUuidGenerator" class="org.apache.camel.impl.SimpleUuidGenerator" /> <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring"> </camelContext> </beans>