[Article] Extending the Functionality of WSO2 Enterprise Service Bus - Part 1

  • By Isuru Udana Loku Narangoda
  • 1 Jun, 2016

Table of contents


Applies to

WSO2 Enterprise Service Bus Version 4.9 and above

Introduction

WSO2 Enterprise Service Bus (WSO2 ESB) is a lightweight integration solution that is recognized as a high performing, user friendly and feature rich enterprise service bus. It has first class support for almost all the enterprise integration patterns. Even though it provides features required for enterprise integration, there can be situations where the it’s not sufficient to build an integration solution. In such scenarios we may need to extend its functionality.

This is the first of a series of articles that will discuss this topic. In this first part, we will explore the different extension points available in WSO2 ESB and discuss two most widely used extension points in detail.


Connect anything to anything

Figure 1

Figure 1 above shows that the WSO2 Enterprise Service Bus (WSO2 ESB) acts as the central hub when integrating heterogeneous systems. WSO2 ESB is connected to systems that communicate in standard protocols like SOAP and REST. It’s also connected to various proprietary systems like SAP and integrated with cloud based solutions like Salesforce, Twitter and Google Docs. Additionally it can be used to connect to different unknown legacy systems as well. In short, WSO2 ESB is capable of connecting anything to anything.

WSO2 ESB has first class support for most standard protocols. It also has various other connectors and adaptors that connect with proprietary systems other cloud based systems. These built-in features cover integration with most systems. However, if the built-in functionality is not sufficient, we can simply extend it to fulfill your requirements.


Extension points

There are several extension points we can use to extend WSO2 ESB. These include

  • Script mediator
  • Custom/Class mediators
  • API handlers
  • Connectors
  • Custom tasks
  • Custom inbound endpoints
  • Custom message stores/processors
  • Custom XPath functions
  • Synapse handlers
  • Axis2 handlers
  • Axis2 modules
  • Message builders and formatters
  • Transport listeners and senders

Choosing the correct extension point to implement your requirement is very important. Figure 2 below shows the different extension points available in WSO2 ESB’s architecture [1].

Figure 2

Script mediator

If some functionality is not available in the WSO2 ESB, first thing we need to check is whether we can achieve it using a script. The script mediator can be used to write your mediation logic using a scripting language like Javascript. It internally uses the Apache Bean Scripting Framework [2]. The following languages are mainly supported:

  • Javascript
  • Groovy
  • Ruby

Example usage of script mediator

Let’s first look at an example use case of the script mediator.


Example scenario 1: Generate a random number

There can be situations where you need to generate a random number or a UUID (universally unique identifier) and use it within the message flow. These type of simple requirements can be easily achieved by using the script mediator. Following is a sample sequence that contains a script mediator. Here, the script is placed inline and is written in Javascript.

<sequence name="GenerateUUID" xmlns="http://ws.apache.org/ns/synapse"> 
 <script language="js">var result, i, j; 
                 result = ''; 
                 for(j=0; j<32; j++) 
                 { 
                   if( j == 8 || j == 12|| j == 16|| j == 20) 
                     result = result + '-'; 
                     i = Math.floor(Math.random()*16).toString(16).toUpperCase(); 
                     result = result + i; 
                 } 
                 mc.setProperty("CONTEXT_ID", result);
  </script> 
</sequence>

As you can see above, the randomly generated number is assigned to a property called ‘CONTEXT_ID’.


Example scenario 2: Sleeping mediation flow

For troubleshooting and debugging purposes sometimes we need to introduce some latency at the WSO2 ESB message flow. To do that we can use a similar script to the one shown below. It’s a sequence that contains a script mediator with a script to change the status of the thread to sleep for five seconds.

<sequence name="SleepThread" xmlns="http://ws.apache.org/ns/synapse"> 
 <script language="js">
              java.lang.Thread.sleep(5000);
  </script> 
</sequence>

Using script mediator

As you know, the script mediator needs a script to define the logic. There are two ways of specifying the script:

  • As an embedded script in the script mediator configuration
  • By storing the script as a separate file and referring to it from the script mediator

When you embed the script into the synapse configuration, you have to define the logic in-line. This is suitable for very small scripts. When you have complex scripts, the better approach is to store it in a file and refer to it from the configuration. When the script is stored as an external file, it needs to be written as a function. You can then simply call that function from the script mediator. If the same function of the script needs to called from multiple places, you have to store it in a separate file to avoid duplication. There are two ways to store the script file:

  • In the registry
  • In local entries

When it’s saved in the registry it gets saved in the database whereas when it’s stores in the local entries it gets saved as a synapse configuration entry in the file system.


APIs and functions

WSO2 ESB provides a set of built-in functions that you can call from the script you’re writing. You get access to the predefined variable “mc”, which is a reference to the MessageContext in the mediation flow. More precisely, you get access to the ScriptMessageContext, which is an implementation of the MessageContext. You can call the built-in functions and manipulate the mediation flow with the “mc” variable. The table below shows some important functions:

Method Name Description
getPayloadXML() This gets a XML representation of SOAP body payload.
getEnvelopeXML() This gets the XML representation of the complete SOAP envelope.
getProperty(name) This gets a property from the current message context.
setPayloadXML(payload) This sets the SOAP body payload from XML.
setProperty(key, value) This is used to set a property in the current message context. The previously set property values are replaced by this method.

For more functions you can refer to WSO2 ESB’s documentation [3].


Executing Java code from script mediator

The script mediator uses Mozilla Rhino [4] as the scripting engine for Javascript. Rhino provides the functionality to write Java code within Javascript. Scripting in Java has many use cases. It allows you to use different Java libraries to compose powerful scripts.

Sample script:

<script language="js">
              java.lang.Thread.sleep(5000);
</script>

We need to define the fully qualified class name to access a Java class. But if the script contains access to lot of different classes of a package, it’s not suitable to use the fully qualified class name all the time. In that case we can import the entire package to the script. There is a top level function called ‘importPackage’ which is equivalent to the import function in java.


Custom/Class mediators

Mediators are the fundamental components of an ESB’s message flow. If WSO2 ESB’s built-in mediators are not sufficient to build your integration solution, you can write your own mediator in Java and engage it into the message flow. These custom mediators are called class mediators.


Writing a class mediator

There are two ways you can create a class mediator:

  1. Implement the mediator interface
  2. Extend AbstractMediator class

It is recommended to take the second approach as AbstractMediator provides all the common functionalities you need. To create a class mediator with the required project structure you can use WSO2 Developer Studio.

A mediator project can be created using WSO2 Developer Studio as shown in figure 3 and 4 below:

Figure 3

Figure 4

Following is a sample class mediator code:

package org.wso2.custom;

import org.apache.synapse.MessageContext; 
import org.apache.synapse.mediators.AbstractMediator;

public class SampleClassMediator extends AbstractMediator { 

	public boolean mediate(MessageContext synCtx) { 
		// TODO Implement your mediation logic here 
		return true;
	}
}

Here we have extended the AbstractMediator class. The only method we need to implement here is the mediate method, which is invoked when the mediator is executed at the mediation flow. The return statement of the mediate method decides whether the mediation flow should continue further or not.


Deploying the mediator

A mediator can be deployed into WSO2 ESB in several ways.

  1. Deploy as a server extension by copying the .jar file to the file system.

    You can export the above created mediator project as a .jar file and then copy it into the directory where we usually drop third party libraries in a carbon server.

    <CARBON_HOME>/repository/components/lib

    If it is an OSGi bundle you can copy it into

    <CARBON_HOME>/repository/components/dropins

    After copying the file a restart is required to install the new jar.

    When we copy the jar as a server extension, this mediator can be accessed from anywhere (even in the tenant space).


  2. Pack and export as a composite application

    From the WSO2 Developer Studio, we can pack the mediator project within a composite application and export it as a CAR file. Then we can deploy the CAR file into the WSO2 ESB server and get the changes to work immediately even without a restart. When we ship the mediator through a CAR file, the mediator is accessible only to the artifacts (sequences, proxy services, APIs) available in the same CAR file. The mediator is not available globally. If you want to access the mediator from an artifact which is deployed from another CAR or from any other way, you can still do that in the following manner:

    • Write a sequence which engages the class mediator
    • Pack the sequence from the same CAR file which contains the class mediator
    • Call the sequence from other artifacts

    In this way the mediator cannot be shared among different tenants.


Engaging the mediator to the message flow

To engage the custom class mediator into the message flow, we can use the built-in mediator called Class Mediator. Following is a sample configuration:

<class name="org.wso2.custom.SimpleClassMediator" />

Here, the name should be the full qualified class name of the mediator class.


Programming abstractions

MessageContext

MessageContext is the representation for a message within the ESB message flow. In the mediate method we get access to the message context. From the message context we can access message payload, message headers, properties, ESB configurations, etc. If you need to perform any manipulations to the message, it has to be performed on the message context. Following are some of the useful functions you can access from the message context.

Method Description
SOAPEnvelope getEnvelope() Get the SOAP Envelope. We can use this to get the message payload.
void setEnvelope(SOAPEnvelope envelope) Set the SOAP Envelope to the message context.
Object getProperty(String key) Get a property value from message context.
void setProperty(String key, Object value) Set a property to the message context.
SynapseConfiguration getConfiguration() Get the synapse configuration.
((Axis2MessageContext)synCtx).getAxis2MessageContext()

(synCtx: Synapse Message Context available in the mediate() method)
Get the Axis2 message context related to the synapse message context. Axis2 message context can be used to do more advanced things related to Axis2 engine and transports.

Axiom API

Apache Axiom is the library which is used as the XML Infoset compliant object model implementation in WSO2 ESB. Since Axiom is heavily used, we need to have a fair understanding of Axiom API in order to do advanced actions with the class mediators. For example, the SOAPEnvelope which we used to represent the message payload belongs to the Axiom. So to perform message manipulation we need to have some understanding of Axiom API.


SynpaseConfiguration

The SynapseConfiguration holds the global configuration for a Synapse instance. Configurations like sequences, endpoints and proxy services are available in this SynapseConfiguration class.


Setting Parameters for Mediators

As shown previously we can engage the mediator into the message flow with the class mediator by specifying the full qualified class name as the class name. In that case it just simply invokes the mediator from the message flow. Similar to the built-in mediators, we may need to give the user an option to configure the mediator behaviour from the synapse configuration. For that we can pass properties form the configuration. Following is a sample configuration:

<class name="org.wso2.custom.SimpleClassMediator">
         <property name="foo" value="10"/>
         <property name="bar" value="5"/>
 </class>

In the mediator code, we need to define variables for these properties and have getters and setters for those variables. Following is a sample mediator implementation.

package org.wso2.custom;

import org.apache.synapse.MessageContext; 
import org.apache.synapse.mediators.AbstractMediator;

public class SampleClassMediator extends AbstractMediator {
	
	private String foo, bar; 
	
	public boolean mediate(MessageContext context) { 
		System.out.println("Using Foo :  " + foo + " Bar : " + bar);
		return true;
	}

	public String getFoo() {
		return foo;
	}

	public void setFoo(String foo) {
		this.foo = foo;
	}

	public String getBar() {
		return bar;
	}

	public void setBar(String bar) {
		this.bar = bar;
	}
	
}

Handling exceptions

Exception handling is a key aspect in programming. In synapse configuration language, fault sequence is used to handle errors. When we write a class mediator, we need to handle exceptions carefully in a way that the fault sequence gets triggered for the exceptions occurring at the class mediator level.

To invoke fault sequence for the errors, we need to throw a SynapseException from the class mediator whenever required. Also, to make things easier when it comes to debugging and identifying issues, we need to set the following properties which describes the occurred error.

ERROR_CODE: Error code for the last encountered exception
ERROR_MESSAGE: Error message for the last encountered exception
ERROR_DETAIL: Exception for the last encountered exception

Note: As a best practice we usually log these properties at the fault sequence.


Content-awareness

Content-awareness is a key aspect when it comes to message mediation. When mediating messages, some of the operations can be executed without touching the message payload whereas some of the operations need access to the message payload to perform the operation. Mediators wthat need access to the content of the message are called content-aware mediators.

To access the message payload, you need to build the message. Building the message is a costly operation. If your mediator does not touch the message payload (e.g. mediator that's just manipulating transport headers) it is better mark the mediator as a content-unaware mediator to avoid unnecessary message building.

To make the mediator content-unaware, we need to override the method isContentAware in the AbstractMediator as follows.

@Override
public boolean isContentAware() {
        return false;
}

Initializing a mediator

The mediate() method gets invoked when it receives a request. There can be situations where we need to execute some tasks (like initialize parameters, establish connections with systems, etc.) prior to receiving the requests. For that we can engage a lifecycle to the mediator. Basically we need to implement the ManagedLifecycle Interface. Following is a sample configuration.

package org.wso2.custom;

import org.apache.synapse.ManagedLifecycle;
import org.apache.synapse.MessageContext; 
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.mediators.AbstractMediator;

public class SampleClassMediator extends AbstractMediator implements ManagedLifecycle {
		
	public boolean mediate(MessageContext context) { 
		return true;
	}

	@Override
	public void init(SynapseEnvironment arg0) {
		// TODO Write your initialization logic here
		
	}

	@Override
	public void destroy() {
		// TODO Write your destroy logic here
		
	}
	
}

Here we can write our initialization logic int the init() method.


Thread safety of mediators

WSO2 ESB is capable of handling thousands of connections simultaneously. To handle a large amount of concurrent requests, built-in mediators and other features of WSO2 ESB is designed carefully following the best practices in concurrent programming. So when we develop a custom mediator, it is required to write the code without breaking the fundamental principles.

  • When we use the mediator in different artifacts, an instance of the mediator gets created for each occurrence in the configuration.
  • An instance gets created only for each occurrence in the configuration, but not per message.
  • You can define and use a global variable in the mediator class if and only if those variables are shared to all the messages in that particular message flow.
  • Updating the global variables within the mediate() method is not recommended.
  • Use of class variables is not recommended.
  • If the variables are specific for a particular message (value is dependant on the message), use method local variables within the mediate method.

A custom mediator with a serializer and factory

The custom mediator type that we discussed above, is engaged to the message flow through the built-in mediator called class mediator and we can only pass configuration parameters using properties. In this method the language syntax is not that rich. But there is a way that you can implement your mediator with your own syntax. In order to do that you need to write a mediator with a serializer and a factory. However because of the maintainability concerns and lack of support in WSO2 Developer Studio, this way of implementing a mediator is not recommended.

For further information about this approach you can refer to [5].


Conclusion

Extending the functionality of WSO2 ESB can be vital when it comes to building a complete integration solution for your enterprise. In this article we discussed how we can extend the functionality of WSO2 ESB using different extension points that are available. We specifically discusses class and script mediators in detail. In the next article we will discuss the rest of the extension points.


References

About Author

  • Isuru Udana Loku Narangoda
  • Senior Technical Lead
  • WSO2