[Article] Writing a Mediator in WSO2 ESB - Part I
- Upul G
- - WSO2
Introduction
WSO2 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 Mapsurcharges = 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