Wednesday, January 14, 2015

Implementing a custom XPath function to used in WSO2 ESB sequences


Today I am going to discuss about a extension point of WSO2 ESB,  implementing custom XPath function which can be used in ESB sequence. If you didn't understand what I meant by custom XPath functions, let me take an example from WSO2 ESB. 
Most of the users use get-property('propertyName') in sequences to retrieve properties, which is not a XPath function provided by jaxen (ESB uses jaxen library underneath) but we used it as a XPath function. So that is a good example of custom XPath function. 
Now you know what is custom XPath function is. So lets start creating a new XPath function. 


Here say we are going to add a XPath function to retrieve a upper case string of a given string. Lets say the function signature to-uppercase('string') which returns STRING.
In order to implement this we need to implement two interfaces.
  1. org.jaxen.Function
  2. org.apache.synapse.util.xpath.ext.SynapseXpathFunctionContextProvider
 First let me show how I implement the Function interface.
 
package org.wso2.carbon.samples.xpath;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jaxen.Context;
import org.jaxen.Function;
import org.jaxen.FunctionCallException;
import org.jaxen.function.StringFunction;
import org.apache.synapse.MessageContext;
import java.util.List;


public class UpperCaseFunction implements Function {
    private Log log = LogFactory.getLog(UpperCaseFunction.class);
    private MessageContext synCtx;
    private String NULL_STRING = "";

    public UpperCaseFunction(MessageContext synCtx) {
        this.synCtx = synCtx;
    }

    @Override
    public Object call(Context context, List list) throws FunctionCallException {
        if (list == null || list.size() == 0) {
            if (log.isDebugEnabled()) {
                log.debug("String not specified");
            }
            return NULL_STRING;
        }
        String str = StringFunction.evaluate(list.get(0), context.getNavigator());   
        return str.toUpperCase();
    }
}

Here you can see I have parse the MessageContext in construct but I didn't use. Here I tried to show you how to access MessageContext inside the function implementation.

Now let me show how I implement SynapseXpathFunctionContextProvider.

 
package org.wso2.carbon.samples.xpath;

import org.apache.synapse.MessageContext;
import org.apache.synapse.util.xpath.ext.SynapseXpathFunctionContextProvider;
import javax.xml.namespace.QName;
import org.jaxen.Function;

public class UppercaseXPathFunctionProvider implements SynapseXpathFunctionContextProvider {

    private static final String NAME_SPACE_PREFIX = "wso2";
    private static final String CACHE_LOOKUP = "to-uppercase";

    public Function getInitializedExtFunction(MessageContext messageContext) {
        UpperCaseFunction toUppercaseFunction = new UpperCaseFunction(messageContext);
        return toUppercaseFunction;
    }

    public QName getResolvingQName() {
        return new QName(null, CACHE_LOOKUP, NAME_SPACE_PREFIX);
    }
}

That's all coding. Now make a OSGi bundle out of this and drop it into $ESB_HOME/repository/component/dropins or make a jar out of this and drop it into $ESB_HOME/repository/component/lib. 

Now we need to register this and that can be done by adding the following entry to $ESB_HOME/repository/conf/synapse.properties file.

synapse.xpath.func.extensions=org.wso2.carbon.samples.xpath.UppercaseXPathFunctionProvider

You can try this out by creating a custom log as follows which will print the upper case of "wso2", "WSO2"
 

        

Hope this helps.