Although there are a lot of evaluators available in Mule CE it is very easy to add your own evaluator. In my case we have a self defined message format that holds some properties in the header of a message (similar to JMS Message, MuleMessage, etc.). To get access to these properties in the Mule config I created a custom evaluator that made this possible. Although there will be other solutions available for this situation, I found this a nice (pragmatic) way to solve it. It also provides a base to start from in case of possible changes in the future.
The XML schema that describes our message looks like:
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:tns="http://www.pascalalma.net/message/v01_0" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.pascalalma.net/message/v01_0" elementFormDefault="qualified"> <xs:element name="message" type="tns:MessageType"/> <xs:complexType name="MessageHeaderType"> <xs:annotation> <xs:documentation>The header for a message. Contains all metadata about the message.</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="messageVersion" type="tns:versionNumberType" minOccurs="1" maxOccurs="1"/> <xs:element name="PropertySet" type="tns:PropertySetType" minOccurs="0" maxOccurs="1"/> </xs:sequence> </xs:complexType> <xs:complexType name="MessageType"> <xs:sequence> <xs:element name="MessageHeader" type="tns:MessageHeaderType" minOccurs="1" maxOccurs="1"/> <xs:element name="MessageBody" type="tns:MessageBodyType" minOccurs="0" maxOccurs="1"/> </xs:sequence> </xs:complexType> <xs:complexType name="MessageBodyType"> <xs:sequence> <xs:any minOccurs="0" maxOccurs="1"/> </xs:sequence> </xs:complexType> <xs:complexType name="PropertyType"> <xs:attribute name="key" type="xs:string" use="required"/> <xs:attribute name="value" type="xs:string"/> </xs:complexType> <xs:complexType name="PropertySetType"> <xs:annotation> <xs:documentation>Set of properties</xs:documentation> </xs:annotation> <xs:sequence> <xs:element name="Property" type="tns:PropertyType" minOccurs="0" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:simpleType name="versionNumberType"> <xs:annotation> <xs:documentation>Type used to define a Message versionNumber</xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> <xs:pattern value="v[0-9]{2}_[0-9]{1}"/> <xs:length value="5"/> </xs:restriction> </xs:simpleType> </xs:schema>
And example message looks like this:
<?xml version="1.0" encoding="UTF-8"?> <tns:message xsi:schemaLocation="http://www.pascalalma.net/message/v01_0 message_v01_0.xsd" xmlns:tns="http://www.pascalalma.net/message/v01_0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <tns:MessageHeader> <tns:messageVersion>v01_0</tns:messageVersion> <tns:PropertySet> <tns:Property key="SOURCE_SYSTEM" value="CRM" /> <tns:Property key="ENTITY" value="Order" /> <tns:Property key="VERSION" value="v01_0" /> </tns:PropertySet> </tns:MessageHeader> <tns:MessageBody> <payload-root>Blablabla</payload-root> </tns:MessageBody> </tns:message>
To get access to the ‘Property’ elements in the message-header via an ‘evaluator expression’ I created the following Java class based on the info given here:
package net.pascalalma.evaluator; import net.pascalalma.cdm.util.JaxbUtil; import java.util.List; import javax.xml.bind.JAXBException; import net.pascalalma.message.v01_0.Message; import net.pascalalma.message.v01_0.PropertySetType; import net.pascalalma.message.v01_0.PropertyType; import org.apache.log4j.Logger; import org.mule.api.MuleMessage; import org.mule.api.expression.ExpressionEvaluator; import org.mule.api.expression.RequiredValueException; import org.mule.config.i18n.CoreMessages; /** * * @author pascal */ public class CdmMessageEvaluator implements ExpressionEvaluator { public static final String NAME = "cdm-msg-property"; protected static Logger logger = Logger.getLogger(CdmMessageEvaluator.class); public Object evaluate(String expression, MuleMessage msg) { Object result = null; boolean required; //Is the header optional? the '*' denotes optional if (expression.endsWith("*")) { expression = expression.substring(expression.length() - 1); required = false; } else { required = true; } Message cdmMsg = null; //Look up the property on the message if (msg.getPayload() instanceof Message) { cdmMsg = (Message) msg.getPayload(); } else if (msg.getPayload() instanceof String) { try { // Apparantly we are getting the message as an XML String // So use jaxb to create a Object of it cdmMsg = JaxbUtil.unmarshal(Message.class, msg.getPayload().toString()); } catch (JAXBException ex) { throw new UnsupportedOperationException("Unexpected String input received (not a Ship Message)"); } } else { // It's not an expected payload. Fail this operation. throw new UnsupportedOperationException("Unexpected inputtype received: " + msg.getPayload().getClass().getName()); } result = getProperty(cdmMsg.getMessageHeader().getPropertySet(),expression.toUpperCase()); if (result == null && required) { throw new RequiredValueException(CoreMessages.expressionEvaluatorReturnedNull(NAME, expression)); } return result; } public String getName() { return NAME; } public void setName(String name) { throw new UnsupportedOperationException("setName"); } private String getProperty(PropertySetType propertySet, String property) { List<propertyType> props = propertySet.getProperties(); String value = null; for (PropertyType prop: props) { if (prop.getKey().toUpperCase().equals(property)) { value = prop.getValue(); break; } } return value; } }
I think the code is fairly straightforward. In the method ‘evaluate’ I receive the payload of the MuleMessage and I check to see if the payload is a JAXBObject of the type Message. If it is a String I assume I receive the XML message as payload and transform the String to the expected JAXB object myself (see this post for more details about this subject).
When I have access to the JAXBObject I simply question the properties of the message for the property name that is supplied with the expression. If a value is found this is returned by the expression.
To make use of this evaluator I ‘bootstrapped’ it with the Mule application. This can simply be done by adding the file ‘registry-bootstrap.properties’ to your classpath in the folder ‘META-INF/services/org/mule/config’ (as explained here). In this file I put:
object.1=net.pascalalma.evaluator.CdmMessageEvaluator
And that’s it. To make use of this evaluator I have in my Mule config:
<expression-recipient-list-router evaluator="custom" custom-evaluator="cdm-msg-property" expression="ADDR_LIST" transformer-refs="JaxbObjectToCdmXml" >
This will take the property ‘ADDR_LIST’ out of my Message and use it as outbound Endpoint, but I will explain that in another post.