2007/12/03
3 Dec, 2007

[Article] Writing a Mediator in WSO2 ESB - Part I

  • Upul G
  • - WSO2

Introduction

Writing a Mediator in ESBWSO2 ESB is a fast, light-weight and versatile Enterprise Service Bus product released under the Apache License v2.0. Using WSO2 ESB you can filter, transform, route and manipulate SOAP, binary, plain XML, and text messages that pass through your business systems by HTTP, HTTPS, JMS, mail etc.

WSO2 ESB comes with an assortment of useful mediators to filter, transform, route and manipulate messages. See Samples Guide in WSO2 ESB documentation to see different mediator usage. When the existing mediators come short, you can write your own mediators to implement your specific business requirements. Your custom mediators can be plugged in to WSO2 ESB quite easily. After adding them to ESB they function as first class citizens the same as core mediators which come with the product. The custom mediators can be distributed in packaged form which can be installed in another WSO2 ESB instance.

Surcharge Stock Quote Mediator

In this tutorial we will create a basic mediator and put it into use in an ESB configuration. We are going to change the sample 150 in the Samples Guide in the WSO2 ESB documentation to use our new mediator . Sample 150 ESB configuration exposes SimpleStockQuoteService as a proxy service named StockQuoteProxy. This means that WSO2 ESB is hosting a virtual web service called StockQuoteProxy with the given WSDL file. When requests come to this proxy service, the inSequence (if it exists) will call its child mediators one by one. Then it will send it to the given target endpoint. This endpoint can be an endpoint reference address, selected port on a given WSDL etc. The response message will go through each mediator in the outSequence mediator, if that exists.

Our mediator will add a surcharge amount to the stock price as the message passes through the ESB. The sample SimpleStockQuoteService sends the stock quote information for a given stock symbol. We are going to put our surchargeStockQuote mediator into the outSequence of the proxy service. surchargeStockQuote mediator will read the symbol in the message and add a surcharge percentage according to the symbol. The mediator will keep a map of symbol and related surcharge percentage. If the symbol is not found in the map, a default surcharge percentage will be added to the outgoing message.

To write a custom mediator we simply have to understand the Mediator interface. This allows us to write a class mediator. After that, we have an option to extend that class mediator with its own domain specific XML configuration. But that is a further step that you only need to do if your Mediator needs its own XML configuration model. This approach will be covered in part 2 of this tutorial, which is coming soon.

Writing the Mediator Class

The custom mediator class should extend the Mediator interface. There is a convenience class AbstractMediator which implements Mediator interface that you can use instead. Then you have to implement only the mediate() method from the interface. When a message passes through each mediator in the configuration, the mediator's mediate() method will be called. In the mediate() method you can manipulate the message headers and the message contents as your requirements dictate. The argument org.apache.synapse.MessageContext represents a message that is passing through the ESB at the moment. The boolean return value determines whether the message mediation should continue or not in the ESB configuration. If you return false in the mediate() method then the message will be dropped and no further processing will occur after that for that particular message.

Our custom mediator class is shown below.

package org.wso2.esb.tutorial.mediators;

import java.util.HashMap;
import java.util.Map;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.xpath.AXIOMXPath;
import org.apache.axiom.soap.SOAPBody;
import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseLog;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.mediators.AbstractMediator;
import org.jaxen.JaxenException;


/**
 * custom mediator to read the stock price and add up surcharge
 */
public class SurchargeStockQuoteMediator extends AbstractMediator implements
        ManagedLifecycle {

    // symbol -> surcharge percentage
    private Map surcharges = new HashMap();
    // default surcharge percentage to use if symbol not found in surcharges map
    private Double defaultPercentage = 0d;

    public boolean mediate(MessageContext synCtx) {

        // establish log levels
        SynapseLog logger = getLog(synCtx);
        boolean traceOn = logger.isTraceEnabled();
        boolean traceOrDebugOn = logger.isTraceOrDebugEnabled();

        // write log messages
        if (traceOrDebugOn) {
            logger.traceOrDebug("Start : SurchargeStockQuote mediator");

            if (traceOn && trace.isTraceEnabled()) {
                trace.trace("Message : " + synCtx.getEnvelope());
            }
        }

        // get symbol, last elements of SOAP envelope
        SOAPBody body = synCtx.getEnvelope().getBody();
        OMElement firstElement = body.getFirstElement();

        OMElement symbolElement = null;
        try {
            AXIOMXPath xPathSymbol = new AXIOMXPath("//ns:symbol");
            xPathSymbol.addNamespace("ns", "https://services.samples/xsd");
            symbolElement = (OMElement) xPathSymbol.selectSingleNode(firstElement);

        } catch (JaxenException e) {
            handleException("element symbol error", e, synCtx);
        }

        if (symbolElement == null) {
            handleException("element symbol not found", synCtx);
        }

        OMElement lastElement = null;
        try {
            AXIOMXPath xPathLast = new AXIOMXPath("//ns:last");
            xPathLast.addNamespace("ns", "https://services.samples/xsd");
            lastElement = (OMElement) xPathLast.selectSingleNode(firstElement);

        } catch (JaxenException e) {
            handleException("element last error", e, synCtx);
        }

        if (lastElement == null) {
            handleException("element last not found", synCtx);
        }

        // lookup surcharge percentage from surcharges map
        // if not found apply default surcharge percentage
        Double surchargePercentage = getDefaultPercentage();
        String symbol = symbolElement.getText();
        Double givenSurchargePercentage = surcharges.get(symbol);
        if (givenSurchargePercentage != null) {
            surchargePercentage = givenSurchargePercentage;
        }

        String text = lastElement.getText();
        Double price = Double.valueOf(text);

        Double newPrice = price.doubleValue()
                + (price.doubleValue() * surchargePercentage.doubleValue() / 100);

        // write back new stock price
        lastElement.setText(String.valueOf(newPrice));

        // print log message
        log.info("symbol:" + symbol + " original price:" + price + " surcharge:"
                + surchargePercentage + "% new price:" + newPrice);

        // write log messages
        if (traceOrDebugOn) {
            logger.traceOrDebug("End : SurchargeStockQuote mediator");
        }

        // proceed with next mediator
        return true;
    }

    public void init(SynapseEnvironment synapseEnvironment) {
        // initializing  surcharges map with some symbols
        surcharges.put("IBM", 15d);
        surcharges.put("MSFT", 20d);
        surcharges.put("SUN", 25d);
    }

    public void destroy() {
        // clearing the surcharges contents
        surcharges.clear();
    }

    public double getDefaultPercentage() {
        return defaultPercentage;
    }

    public void setDefaultPercentage(double defaultPercentage) {
        this.defaultPercentage = defaultPercentage;
    }
}

You can see the mediate() method of our custom mediator above. First it checks the current logging levels and outputs the message details for logging purposes. Then it retrieves the SOAP Envelope of the current message and finds the symbol and the stock price elements using XPath expressions. Apache AXIOM is used for XML manipulation in ESB. AXIOM is an object model similar to DOM. To learn more about AXIOM, see the AXIOM tutorial. It looks up the symbol in the internal map and gets the surcharge percentage. If the symbol is not found, a default surcharge percentage is applied. The new stock price is written back to the message. The mediator returns true. So ESB will proceed with the next step as instructed in the configuration.

The mediator is also a JavaBean - it has a property called defatulPercentage. This property can be configured using dependency injection and

Implementing ManagedLifecycle for Initialization and Cleanup

There are times when a mediator needs to be aware when it is created or destroyed. Just like a servlet, Mediators can call initialization code, and then clean up afterwards. To do this, they must implement the ManagedLifecycle interface. When the mediator implements ManagedLifecyle interface, ESB will call init() method at the initialization of the mediator object and call destroy() when the mediator object model is destroyed.

public interface ManagedLifecycle {
public void init(SynapseEnvironment se);
public void destroy();
}

To demonstrate this, we are using the init() method in the surcharge stock quote mediator to initialize the surcharges map object with some default symbol and surcharge percentage pairs. We have done it in the init() method by implementing ManagedLifecycle interface. In real life we might be using this to create a connection pool to a database, or loading data from a cache.

Packaging the mediator

To package the mediator simply create a JAR file and add this to the ESB's classpath, and then restart the ESB.

Testting the Mediator

The following is the ESB configuration we are going to use. This is a the same configuration given in Sample 150 of ESB Samples Guide with a slight modification to add our custom mediator to the outSequence of the proxy service.

<definitions xmlns="http://ws.apache.org/ns/synapse">
<proxy name="StockQuoteProxy">
<target>
<endpoint>
<address uri="https://localhost:9000/soap/SimpleStockQuoteService"/>
</endpoint>
<outSequence>
<class name="org.wso2.esb.tutorial.mediators.SurchargeStockQuoteMediator">
<property name="defaultPercentage" value="10"/>
</class>
<send/>
</outSequence>
</target>
<publishWSDL uri="file:repository/conf/sample/resources/proxy/sample_proxy_1.wsdl"/>
</proxy>
</definitions>

Start WSO2 ESB with this configuration. To do that you can start WSO2 ESB server and log in to the ESB console. Go to the Configuration page and replace the existing configuration with the above configuration and click Update.

Deploy the SimpleStockQuoteService to sample Axis2 server. Start the sample Axis2 server. For further instructions, see ESB Samples Guide.

Run the client as given in Sample 150. Try giving differernt symbols as -Dsymbol=ABC etc.

ant stockquote -Daddurl=https://localhost:8080/soap/StockQuoteProxy -Dsymbol=IBM

 

You will see log entries similar to the following in the ESB console.

2007-11-22 10:03:02,232 [127.0.1.1-upul] [HttpClientWorker-3]  INFO SurchargeStockQuoteMediator symbol:IBM original price:152.11870457914713 surcharge:15.0% new price:174.9365102660192
2007-11-22 10:02:26,644 [127.0.1.1-upul] [HttpClientWorker-1] INFO SurchargeStockQuoteMediator symbol:MSFT original price:152.6138914900887 surcharge:20.0% new price:183.13666978810642
2007-11-22 10:03:17,262 [127.0.1.1-upul] [HttpClientWorker-4] INFO SurchargeStockQuoteMediator symbol:SUN original price:55.753095784294295 surcharge:25.0% new price:69.69136973036787
2007-11-22 10:02:46,933 [127.0.1.1-upul] [HttpClientWorker-2] INFO SurchargeStockQuoteMediator symbol:QWE original price:93.41435075068514 surcharge:10.0% new price:102.75578582575366

The surcharge stock quote mediator has added the surcharge to the stock price of the outgoing message. You can see the original price and the modified price on the console. The client will get the modified price.

Creating an XML Configuration model for this Mediator

A future tutorial will describe how you can create an XML configuration model for this mediator. That way - instead of using the generic <class/> configuration, you can have a cleaner XML syntax that is more "fit for purpose". This approach is known as Domain Specific Modelling. Keep your eyes posted for part 2!

Resources

For further details on writing mediators, see Extending Synapse in ESB documentation.

For part 2 of this tutorial, see Writing a Mediator in WSO2 ESB - Part 2

Source code: surcharge-stock-quote-mediator.zip

Author

Upul Godage, Senior Software Engineer, WSO2 Inc. upul AT wso2 DOT com

 

About Author

  • Upul G
  • IT