Sunday, October 20, 2013

Custom mediator for WSO2 ESB

There are some situations in our deployment scenarios, which it is required to do a custom transformation of incoming messages. This custom tasks are sometimes can not be achieved with the inbuilt mediators in WSO2 Enterprise Service Bus. So , in those kind of scenarios , we ll have to write a custom mediator to achieve this. With this post , I am explaining how to write a custom mediator for WSO2 ESB.

Writing a custom mediator for ESB , means we are writing a class meditor for ESB which contains the required logic in the mediate() method of the class. When writing a class mediator, there is  a basic step follow to be compatible to deploy in WSO2 ESB.  We need to extend our mediator from the abstract class "org.apache.synapse.mediators.AbstractMediator". In order to do that , you will have to add a dependency to your project for  synapse-core as follows;



    <dependencies>
        <dependency>
            <groupId>org.apache.synapse</groupId>
            <artifactId>synapse-core</artifactId>
            <version>${synpase.core.version}</version>
        </dependency>
    </dependencies>


Before digging in to the code it self, let me explain , what i am going to achieve with this custom mediator.

I have following incoming message ;


         <dat:addJobWorkLocation xmlns:dat="http://ws.wso2.org/dataservice">
            <dat:job_id>2037826</dat:job_id>
            <dat:preferred_city_code>,CMB,GM,KND,</dat:preferred_city_code>
            <dat:country_code>SL</dat:country_code>
         </dat:addJobWorkLocation>

 

From this incoming message , i need to generate messages with the same type by splitting the comma separated string passed with the local name "preferred city code". After generating , it should be like follows,




         <dat:addJobWorkLocation xmlns:dat="http://ws.wso2.org/dataservice">
            <dat:job_id>2037826</dat:job_id>
            <dat:preferred_city_code>CMB</dat:preferred_city_code>
            <dat:country_code>SL</dat:country_code>
         </dat:addJobWorkLocation>



         <dat:addJobWorkLocation xmlns:dat="http://ws.wso2.org/dataservice">
            <dat:job_id>2037826</dat:job_id>
            <dat:preferred_city_code>GM</dat:preferred_city_code>
            <dat:country_code>SL</dat:country_code>
         </dat:addJobWorkLocation>



         <dat:addJobWorkLocation xmlns:dat="http://ws.wso2.org/dataservice">
            <dat:job_id>2037826</dat:job_id>
            <dat:preferred_city_code>KND</dat:preferred_city_code>
            <dat:country_code>SL</dat:country_code>
         </dat:addJobWorkLocation>






Since we do not have an inbuilt mediator to fulfill this task, i am writing a custom mediator achieve that.
Name of my custom mediator is "SmartSplitMediator" and i am extending it from the AbstractMediator as follows.

public class SmartSplitMediator extends AbstractMediator {
...
}


Once i extended my class with AbstractMediator,  it is essential to implement the methods in the AbstractMediator class. So , i am implementing the method mediate in this class as follows.


public class SmartSplitMediator extends AbstractMediator {
...


   public boolean mediate(MessageContext messageContext) {
   ...
  }


...
}


Then i need to write my logic inside this mediate method. To achieve my target , i need to pass some variables in this class. So, for that task , i need to define those variables as class variables and add getters and setters for those variables. For this particular case, i need following variables.

  • parentLocalName
  • parentNamespace
  • parentNamespacePrefix
  • IteratingElementLocalName
  • variableStringLocalName
  • constantStringsLocalNames


I am adding these variables to my class as follows;


public class SmartSplitMediator extends AbstractMediator {

    private String parentLocalName;
    private String parentNamespace;
    private String parentNamespacePrefix;
    private String IteratingElementLocalName;
    private String variableStringLocalName;
    private String constantStringsLocalNames;
  
    public boolean mediate(MessageContext messageContext) {
        ...
    }

    public String getParentLocalName() {
        return parentLocalName;
    }

    public void setParentLocalName(String parentLocalName) {
        this.parentLocalName = parentLocalName;
    }

    public String getParentNamespace() {
        return parentNamespace;
    }

    public void setParentNamespace(String parentNamespace) {
        this.parentNamespace = parentNamespace;
    }

    public String getParentNamespacePrefix() {
        return parentNamespacePrefix;
    }

    public void setParentNamespacePrefix(String parentNamespacePrefix) {
        this.parentNamespacePrefix = parentNamespacePrefix;
    }

    public String getIteratingElementLocalName() {
        return IteratingElementLocalName;
    }

    public void setIteratingElementLocalName(String iteratingElementLocalName) {
        IteratingElementLocalName = iteratingElementLocalName;
    }

    public String getVariableStringLocalName() {
        return variableStringLocalName;
    }

    public void setVariableStringLocalName(String variableStringLocalName) {
        this.variableStringLocalName = variableStringLocalName;
    }

    public String getConstantStringsLocalNames() {
        return constantStringsLocalNames;
    }

    public void setConstantStringsLocalNames(String constantStringsLocalNames) {
        this.constantStringsLocalNames = constantStringsLocalNames;
    }
}



Then the remaining part is to write the logic inside the mediate method. With the above passed in variables i have the following logic inside my mediate method.




    public boolean mediate(MessageContext messageContext) {

        SOAPFactory fac = OMAbstractFactory.getSOAP11Factory();
        SOAPBody soapBody = messageContext.getEnvelope().getBody();
        OMElement parentElement = (OMElement) soapBody.getFirstElement();
        String variableString = parentElement.getFirstChildWithName(new QName(getParentNamespace(),getVariableStringLocalName(),getParentNamespacePrefix())).getText();
      
//Here we are tokenizing the incoming string from commas
  StringTokenizer variableTokenizer = new StringTokenizer(variableString,",");

        OMElement newParentElement = fac.createOMElement(new QName(getParentNamespace(), getParentLocalName(), getParentNamespacePrefix()));
        while (variableTokenizer.hasMoreTokens()) {
            String variableValue = variableTokenizer.nextToken();
            if (variableValue.length() > 0) {
                OMElement secondLevelElement = fac.createOMElement(new QName(getParentNamespace(), getIteratingElementLocalName(), getParentNamespacePrefix()));
                StringTokenizer tokenizer = new StringTokenizer(getConstantStringsLocalNames(), ",");
                while (tokenizer.hasMoreTokens()) {
                    String constantLocalName = tokenizer.nextToken();
                    OMElement jobIdElement;
                    if (constantLocalName.equalsIgnoreCase(getVariableStringLocalName())) {
                        jobIdElement = fac.createOMElement(new QName(getParentNamespace(), constantLocalName, getParentNamespacePrefix()));
                        jobIdElement.setText(variableValue);
                    } else {
                        jobIdElement = fac.createOMElement(new QName(getParentNamespace(), constantLocalName, getParentNamespacePrefix()));
                        jobIdElement.setText(parentElement.getFirstChildWithName(new QName(getParentNamespace(),constantLocalName,getParentNamespacePrefix())).getText());
                    }
                    secondLevelElement.addChild(jobIdElement);
                }
                newParentElement.addChild(secondLevelElement);
            }
        }
//Now we have completed the preparation of the new body, We need to detach the existing body and attach the new body. We are doing it here.
        SOAPBody body = messageContext.getEnvelope().getBody();
        if (body.getFirstElement() != null) {
            body.getFirstElement().detach();
        }
        body.addChild(newParentElement);

        return true;
    }




After completing the logic in the above method we are done in implementation. Now we need to compile this mediator and deploy it in the  wso2esb-4.x.x/repository components/lib directory to be used in any of the sequences.

Once you deploy it in the above directory , you can use it as follows in any of the sequence;


     <inSequence>
            <log level="full"/>
            <class name="com.js.mediator.split.SmartSplitMediator">
               <property name="variableStringLocalName" value="preferred_city_code"/>
               <property name="parentLocalName" value="WorkAuthorization"/>
               <property name="constantStringsLocalNames" value="preferred_city_code,
country_code"/>

               <property name="parentNamespacePrefix" value="dat"/>
               <property name="parentNamespace" value="http://ws.wso2.org/dataservice"/>
               <property name="IteratingElementLocalName" value="addJobWorkLocation"/>
            </class>
            <log level="full"/>
            <iterate xmlns:dat="http://ws.wso2.org/dataservice"
                     id="foo"
                     expression="//dat:WorkAuthorization/dat:addJobWorkLocation"
                     sequential="true">
               <target>
                  <sequence>
                     <log level="full"/>
                     <drop/>
                  </sequence>
               </target>
            </iterate>
         </inSequence>   

   

Here i am attaching following items with related to this blog post.