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:

<?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.

Advertisement

About Pascal Alma

Pascal is a senior IT consultant and has been working in IT since 1997. He is monitoring the latest development in new technologies (Mobile, Cloud, Big Data) closely and particularly interested in Java open source tool stacks, cloud related technologies like AWS and mobile development like building iOS apps with Swift. Specialties: Java/JEE/Spring Amazon AWS API/REST Big Data Continuous Delivery Swift/iOS
This entry was posted in Mule, XML/ XSD/ XSLT and tagged , , . Bookmark the permalink.