Create your own Expression Evaluator in Mule

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.

tags: , ,

About Pascal Alma

Pascal started as an Oracle Developer in 1997 and developed numerous applications with Oracle Designer/Developer and PL/SQL. Since 2001 Pascal becomes more and more active with the development of software at the Java/J2EE platform. Nowadays Pascal is a senior JEE Developer/ Architect and has a lot of experience with several open source initiatives/ frameworks especially within the Enterprise Integration area. Besides these technical skills Pascal is a big Scrum enthusiastic.

One Response to Create your own Expression Evaluator in Mule

  1. Pingback: Dynamic Routing in Mule | Redstream Blog