Writing custom transformers in Mule
- Get link
- X
- Other Apps
Hello Coder,
As soon as we start writing Mule flows we need to do the conversion of data from one form to another and that is how a message (request-response) flow in any application works. For example if we are writing endpoints for a mobile application, our server receives a request in some specified format say XML and it need to give response in other format say JSON. So what we will do in this case is to first parse the request XML to a String and then to a Java object or will use some 3rd party library which will do this transformation for us. Same applies to the response also. The work seems reasonable if the endpoints are less but what if we need to provide 1000 endpoints to a mobile application ? Are we going to do this repeated process of transformation ? NO. That's where Mule wins the race because it provides inbuilt transformers which will do the conversion. In this tutorial we will focus on Mule Custom Transformers. First we will understand what are transformers in Mule and why we need them. Then we will try to write our own custom transformer and add it in our existing Mule flow.
1) What is a Mule Transformer ?
A transformer is a joint point between two components in a Mule Flow. A Transformer enhances and or modifies the incoming mule message and prepares such a mule message which will be processed by the next available component in the Mule flow. Message transformation is done via payloads in the Mule Message. In other words a transformer is nothing but a simple java class which transforms the message from one format to another. There are many default transformers shipped with Mule ESB, some of them are Object-to-String, Object-to-Json, JAXB-Object-to-Xml, Object-to-xml etc. As their name suggests these transformers are used to transform the incoming message from one form to another. The incoming message comes as a payload to the transformer, the transformer applies the transformation to the message and sets the transformed message again in the payload.
2) Why are transformers needed in a Mule application ?
Transformers are the backbone of any ESB application because no ESB can live without transformers. One of the ESB's major role is to connect two systems without changing the source and destination system. There is very less possibility that both the systems produces/consumes the data in same format and also live as an independent system. Here the transformers come into picture in a Mule application flow. Transformers are needed when the next component in the flow expects the message in other format than the current component have. For example, the mule message that initiates the mule flow receives data in XML format, but a downstream message processor requires data in JSON format. In this case the transformers positioned between the message source and the message processor can achieve this translation. By default no XML-to-JSON transformer exists in Mule, hence in this particular tutorial we will write a custom transformer which converts a XML message to JSON message and pass the JSON to next component.
3) How to write a custom transformer
In the above example if the incoming message to a component is in XML format (as we used in our last post here) and suppose we want to pass this XML message to some other flow or an outbound endpoint (outbound endpoints are used to make http requests outside of Mule ESB) for further processing which expects the message in JSON format then we need to write custom transformer and do the transformation of message in the transformer. To write a custom transformer you need to write a custom java class which extends org.mule.transformer.AbstractMessageTransformer
from mule-core module. AbstractMessageTransformer
is an abstract class with an abstract method :
public abstract Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException;
From the documentation of AbstractMessageTransformer
class:
AbstractMessageTransformer
is a transformer that has a reference to the current message. This message can be used to obtain properties associated with the current message which are useful to the transform. Note that when part of a transform chain, the MuleMessage payload reflects the pre-transform message state, unless there is no current event for this thread, then the message will be a new DefaultMuleMessage with the src as its payload. Transformers should always work on the src object not the message payload.
In our custom transformer class we need to override the transformMessage
method from AbstractMessageTransformer
and provide the implementation of this method. The implementation is responsible to read the incoming message to the transformer, transform it to another format and pass it to next component or flow or outbound endpoint. The MuleMessage
parameter is the message which mule populates, wraps the request inside it and passes it to this transformer. In our example the XML message will be received in the MuleMessage
object. We need to use this message object to pull the requested xml object out of it and apply the desired conversion.
We will update the mule flow defined here to add 2 more components after the SOAP component. We will add two JAVA transformers by dragging and dropping the Java transformer component from right pane to the flow. Our flow will look like this after this (Ignore the "JSON request to xml response" transformer for now.):
Also in order to completely visualize the object transformation we will update the original wsdl to send and receive the custom objects instead of primitive types. We are doing this so that we can get a Java object in our transformer and we can typeCast it from MuleMessage
. Here is the updated wsdl for previous post :
<?xml version="1.0" encoding="UTF-8"?> <wsdl:definitions name="DoubleIt" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:di="http://www.example.org/schema/DoubleIt" xmlns:tns="http://www.example.org/contract/DoubleIt" targetNamespace="http://www.example.org/contract/DoubleIt"> <wsdl:types> <xsd:schema targetNamespace="http://www.example.org/schema/DoubleIt"> <xsd:element name="DoubleIt"> <xsd:complexType> <xsd:sequence> <xsd:element minOccurs="1" maxOccurs="1" name="requestItem" type="di:ItemsToProcess"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="ItemsToProcess"> <xsd:sequence> <xsd:element minOccurs="1" maxOccurs="1" name="numberToDouble" type="xsd:int"/> <xsd:element minOccurs="1" maxOccurs="1" name="numberToHalf" type="xsd:int"/> </xsd:sequence> </xsd:complexType> <xsd:element name="DoubleItResponse"> <xsd:complexType> <xsd:sequence> <xsd:element minOccurs="1" maxOccurs="1" name="responseItem" type="di:ItemsToResponse"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="ItemsToResponse"> <xsd:sequence> <xsd:element minOccurs="1" maxOccurs="1" name="numberToDouble" type="xsd:int"/> <xsd:element minOccurs="1" maxOccurs="1" name="numberToHalf" type="xsd:int"/> </xsd:sequence> </xsd:complexType> </xsd:schema> </wsdl:types> <wsdl:message name="DoubleItRequest"> <wsdl:part element="di:DoubleIt" name="parameters" /> </wsdl:message> <wsdl:message name="DoubleItResponse"> <wsdl:part element="di:DoubleItResponse" name="parameters" /> </wsdl:message> <wsdl:portType name="DoubleItPortType"> <wsdl:operation name="DoubleIt"> <wsdl:input message="tns:DoubleItRequest" /> <wsdl:output message="tns:DoubleItResponse" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="DoubleItBinding" type="tns:DoubleItPortType"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="DoubleIt"> <soap:operation soapAction=""/> <wsdl:input><soap:body use="literal"/></wsdl:input> <wsdl:output><soap:body use="literal"/></wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="DoubleItService"> <wsdl:port name="DoubleItPort" binding="tns:DoubleItBinding"> <!-- Default address for Tomcat deployment (CXF/Metro) --> <soap:address location="http://localhost:8090/mulesvc/sample/soapws/DoubleIt.svc"/> <!-- Default address for Karaf deployment (CXF only) --> <!--soap:address location="http://localhost:8181/cxf/doubleit"/--> </wsdl:port> </wsdl:service> </wsdl:definitions>
You need to generate the JAXB classes again from this updated wsdl. You can use the Anypoint Studio to generate the classes this time or else you need to again perform the Step 10 of this post.
Now, we will write two transformers in this example as the flow shows above. We are writing two transformers so that we can learn the transformer chaning also and view the transformed message from first transformer in second transformer :
1) XML to JSON Transformer and
2) JSON to XML Transformer
1) XML to JSON transformer
This transformer will receive the XML request (JAXB objects) from SOAP component as Message Payload. In this transformer we will transform the incoming XML request to next transformer's understandable JSON String format. Below is the full code for this transformer.package com.sample.mule.impl; import org.mule.api.MuleMessage; import org.mule.api.transformer.TransformerException; import org.mule.transformer.AbstractMessageTransformer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.sample.mule.ItemsToProcess; public class XmlToJsonTransformer extends AbstractMessageTransformer { private static final Logger LOGGER = LoggerFactory.getLogger(XmlToJsonTransformer.class.getName()); @Override public Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException { // Incoming request xml object ItemsToProcess request = (ItemsToProcess) message.getPayload(); JsonMessage transformed = new JsonMessage(); transformed.setNumberToDouble(String.valueOf(request.getNumberToDouble())); transformed.setNumberToHalf(String.valueOf(request.getNumberToHalf())); try { // Parse the java object to a json string String jsonString = getObjectMapper().writeValueAsString(transformed); // Set the json string for next component/outbound-endpoint/sub-flow message.setPayload(jsonString); } catch (JsonProcessingException e) { LOGGER.error("Cannot convert into json"); } return message; } private static ObjectMapper getObjectMapper() { final ObjectMapper result = new ObjectMapper(); result.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); result.configure(SerializationFeature.INDENT_OUTPUT, true); return result; } }
The return type of message.getPayload()
is Object
. This is so that it can be typecasted to the incoming Java object. Since in our case, as per updated wsdl file, the requested JAXB object would be ItemsToProcess
hence we can safely typecast the payload to ItemsToProcess
. Now we can create our custom JsonMessage
and set its fields from the ItemsToProcess
object. Next we will use the jackson library to convert the java object to a String json. We will leave the jacson details upto the reader until we write a separate post for Jackson.
Once we built the String JSON in the overridden transformMessage()
method we need to pass it to outside of this component. We can easily do it by setting it to the payload again so the original payload (XML) will be updated by new payload (JSON). Once the payload is updated we need to return it back to Mule so that it can take the updated payload and pass it to next transformer (Json To Xml Transformer). Mule will take care that the next transformer receives the updated payload.
2) JSON to XML Transformer
This is the transformer which receives the JSON request from its previous component. It transforms the incoming JSON message to an XML response and passes it further to to SOAP component. This is the same as defined in XML-to-JSON transformer, just with reversed process. Here is the full code of this transformer:package com.sample.mule.impl; import org.mule.api.MuleMessage; import org.mule.api.transformer.TransformerException; import org.mule.transformer.AbstractMessageTransformer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.sample.mule.ItemsToResponse; public class JsonToXmlTransformer extends AbstractMessageTransformer { private static final Logger LOGGER = LoggerFactory.getLogger(JsonToXmlTransformer.class.getName()); @Override public Object transformMessage(MuleMessage message, String outputEncoding) throws TransformerException { // Incoming request xml object try {
String incomingJson = message.getPayloadAsString(); JsonMessage request = getObjectMapper().readValue(incomingJson, JsonMessage.class); ItemsToResponse response = new ItemsToResponse(); response.setNumberToDouble(Integer.valueOf(request.getNumberToDouble())*2); response.setNumberToHalf(Integer.valueOf(request.getNumberToHalf())/2); message.setPayload(response); } catch (Exception e) { LOGGER.error("Error transforming JSON to XML"); } return message; } private static ObjectMapper getObjectMapper() { final ObjectMapper result = new ObjectMapper(); result.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); result.configure(SerializationFeature.INDENT_OUTPUT, true); return result; } }
This transformer receives the JSON sent by previous transformer in a String format. Since we know the previous transformer had written a String in the payload hence here we can safely use the getPayloadAsString()
method to get the incoming request. We will use jackson library again to convert the JSON String to Java object. After transformation to Java object we can apply the logic for our webservice, multiply by 2 and divide by 2, and create a ItemsToResponse
object.
Now we need to set this ItemsToResponse
object to payload so it will be returned to SOAP component and then it will be returned to the SOAP service caller via http endpoint.
To have a working flow you need to provide the fully qualified transformer class name to each of these component's properties section in the "Transformer class" tag. See the image from Anypoint studio below:
You need to do this for both the Transformers and save the mule flow xml. Now start the Mule application from Anypoint studio and send a SOAP request from a tool like SOAPUI. Below is the snapshot of this SOAP request-response from my machine.
Here is the full mule flow xml
<?xml version="1.0" encoding="UTF-8"?> <mule xmlns:cxf="http://www.mulesoft.org/schema/mule/cxf" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.6.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd http://www.mulesoft.org/schema/mule/cxf http://www.mulesoft.org/schema/mule/cxf/current/mule-cxf.xsd"> <http:listener-config name="Connector_Config" host="localhost" port="8090" doc:name="HTTP Listener Configuration" basePath="mulesvc"/> <flow name="soap-webservice-muleFlow"> <http:listener config-ref="Connector_Config" path="sample/soapws/DoubleIt.svc" doc:name="HTTP"/> <object-to-string-transformer doc:name="Object to String"/> <logger message="#[payload]" level="INFO" doc:name="Logger"/> <cxf:jaxws-service service="DoubleIt.svc" serviceClass="com.sample.mule.DoubleItPortType" doc:name="CXF" wsdlLocation="C:\Mule-workspace\workspace\soap-ws-mule\src\main\resources\example-soap-ws.wsdl"/> <!-- <component class="com.sample.mule.impl.DoubleItPortTypeImpl" doc:name="Java"/> --> <custom-transformer class="com.sample.mule.impl.XmlToJsonTransformer" doc:name="Xml Request To Json"/> <custom-transformer class="com.sample.mule.impl.JsonToXmlTransformer" doc:name="Json request to xml response"/> </flow> </mule>
Notice the highlighted lines in above flow xml which were added after we kept these two chaining Java transformers. These custom-transformer tags contains our fully qualified class names. So when, at runtime, mule flow execution reaches to these components, Mule will call transformMessage()
of each of them sequentially in the order they are defined in the flow XML.
Note: To keep the example simple we have used transformer chaining i.e joined two transformers. In more complex scenarios the message transformed from a transformer can be passed to a subflow or an entirely separate http out-bound endpoint.
I hope it will help you.
- Get link
- X
- Other Apps
Comments
Nice!
ReplyDeleteAre the source code available hosted somewhere publicly? Trying the XmlToJsonTransformer, I encounter missing dependencies like ItemsToProcess, JsonMessage, etc.
ReplyDeleteThese classes are autogenerated classes from the original wsdl. You need to autogenerate the java classes from wsdl using the JAXB xjc tool. For that you need to install JAXB on your machine and then run the following from command prompt:
ReplyDelete> xjc -wsdl [path to wsdl] [path to generated classes]
Whats the dependency used for JsonMessage
DeleteJsonMessage is not used from any dependency but an custom made object to pass the information from one transformer to another
DeleteThe article provided by you is very nice and it is very helpful to know the more information.keep update with your blogs .I found a article related to you..once you can check it out
ReplyDeleteMulesoft online course
That is very interesting; you are a very skilled blogger. I have shared your website in my social networks! A very nice guide. I will definitely follow these tips. Thank you for sharing such detailed article.
ReplyDeleteMulesoft online training bangalore
ReplyDeletethe blog is about writing Custom Transformers in mule for more updates on servicenow follow the link
mulesoft Online Training
For more info on other technologies go with below links
tableau online training hyderabad
ServiceNow Online Training
Python Online Training
the blog is good and Interactive it is about Mulesoft Anypoint Studio it is useful for students and Mulesoft Developers for more updates on Mulesoft mulesoft Online training india
ReplyDelete