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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:tns="http://www.redstream.nl/message/v01_0" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.redstream.nl/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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?xml version="1.0" encoding="UTF-8"?> <tns:message xsi:schemaLocation="http://www.redstream.nl/message/v01_0 message_v01_0.xsd" xmlns:tns="http://www.redstream.nl/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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | package nl.redstream.evaluator; import nl.redstream.cdm.util.JaxbUtil; import java.util.List; import javax.xml.bind.JAXBException; import nl.redstream.message.v01_0.Message; import nl.redstream.message.v01_0.PropertySetType; import nl.redstream.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:
[code]
object.1=nl.redstream.evaluator.CdmMessageEvaluator
[/code]
And that’s it. To make use of this evaluator I have in my Mule config:
1 | <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.
Pingback: Dynamic Routing in Mule | Redstream Blog