Chapter 8: Message Mediation and Transformation
Overview
Mediation is the core of WSO2's integration capabilities. It intercepts, transforms, routes, and enriches messages as they flow between clients and backend services. This chapter covers mediation sequences, the full mediator catalog, and real transformation patterns.
Mediation Architecture
Client Request
│
▼
┌─────────────────────────────┐
│ In Sequence │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Log │→│Xform│→│Route│ │
│ └─────┘ └─────┘ └─────┘ │
└────────────┬────────────────┘
▼
Endpoint
│
▼
┌─────────────────────────────┐
│ Out Sequence │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Log │→│Xform│→│Send │ │
│ └─────┘ └─────┘ └─────┘ │
└────────────┬────────────────┘
▼
Client Response
Key Concepts:
- In Sequence: Processes the incoming request
- Out Sequence: Processes the backend response
- Fault Sequence: Handles errors at any stage
- Mediators: Individual processing units within sequences
Mediator Categories
| Category | Mediators | Purpose |
|---|---|---|
| Core | Log, Property, Call, Send, Respond | Basic message handling |
| Transform | PayloadFactory, Enrich, XSLT, DataMapper, Script | Message transformation |
| Filter | Filter, Switch, Validate | Conditional processing |
| Flow | Clone, Iterate, Aggregate, ForEach | Message splitting/aggregation |
| Database | DBLookup, DBReport | Database operations |
| Extension | Class, Script, Bean | Custom logic |
Core Mediators in Depth
Property Mediator
Set, read, and remove properties in the message context.
Scopes:
| Scope | Description | Lifetime |
|---|---|---|
| default | Synapse message context | Current message flow |
| axis2 | Axis2 message context | Current message |
| transport | HTTP headers | Current transport |
| operation | Operation scope | Across in/out sequences |
| registry | Registry entries | Persistent |
Examples:
<!-- Extract from JSON body -->
<property name="userId" expression="json-eval($.user.id)" scope="default"/>
<!-- Extract from URL parameter -->
<property name="orderId" expression="get-property('uri.var.orderId')"/>
<!-- Set HTTP status code -->
<property name="HTTP_SC" value="201" scope="axis2"/>
<!-- Set response content type -->
<property name="messageType" value="application/json" scope="axis2"/>
<!-- Set a transport header -->
<property name="X-Request-ID" expression="get-property('MESSAGE_ID')" scope="transport"/>
<!-- Remove a header -->
<property name="Server" scope="transport" action="remove"/>
<!-- Read system properties -->
<property name="timestamp" expression="get-property('SYSTEM_TIME')"/>
<property name="msgId" expression="get-property('MESSAGE_ID')"/>
Call vs Send vs CallOut
| Mediator | Blocking | Response | Use Case |
|---|---|---|---|
| Call | No | Continues in same sequence | Default choice for service calls |
| Send | No | Response goes to Out Sequence | Legacy; use at end of In Sequence |
| Callout | Yes | Blocks until response received | When you must wait for result |
Call Mediator (recommended):
<call>
<endpoint>
<http method="get" uri-template="http://backend/api/users/{userId}"/>
</endpoint>
</call>
<!-- Response replaces current message; continue processing -->
<property name="userName" expression="json-eval($.name)"/>
Callout Mediator (blocking):
<callout serviceURL="http://backend/api/lookup"
action="urn:lookup">
<source type="envelope"/>
<target key="lookupResult"/>
</callout>
<!-- lookupResult property contains the response -->
Respond Mediator
Returns the current message to the client immediately. No further processing occurs.
<!-- Build response and return -->
<payloadFactory media-type="json">
<format>{"status": "accepted", "id": "$1"}</format>
<args>
<arg expression="$ctx:requestId"/>
</args>
</payloadFactory>
<property name="HTTP_SC" value="202" scope="axis2"/>
<respond/>
Transformation Mediators
PayloadFactory
Build new message payloads from scratch.
JSON Output:
<payloadFactory media-type="json">
<format>
{
"employee": {
"id": "$1",
"fullName": "$2 $3",
"email": "$4",
"active": true
}
}
</format>
<args>
<arg expression="json-eval($.id)"/>
<arg expression="json-eval($.firstName)"/>
<arg expression="json-eval($.lastName)"/>
<arg expression="json-eval($.emailAddress)"/>
</args>
</payloadFactory>
XML Output:
<payloadFactory media-type="xml">
<format>
<order xmlns="http://example.com/orders">
<orderId>$1</orderId>
<customer>$2</customer>
<items>$3</items>
</order>
</format>
<args>
<arg expression="json-eval($.id)"/>
<arg expression="json-eval($.customer.name)"/>
<arg expression="json-eval($.items)"/>
</args>
</payloadFactory>
FreeMarker Template (complex transformations):
<payloadFactory media-type="json" template-type="freemarker">
<format>
<![CDATA[
{
"orders": [
<#list payload.items as item>
{
"sku": "${item.sku}",
"quantity": ${item.qty},
"total": ${item.price * item.qty}
}<#if item?has_next>,</#if>
</#list>
],
"grandTotal": ${payload.items?map(i -> i.price * i.qty)?reduce(0, (a, b) -> a + b)}
}
]]>
</format>
<args/>
</payloadFactory>
Enrich Mediator
Add, replace, or modify parts of the message.
<!-- Add a new field to JSON body -->
<enrich>
<source type="inline" clone="true">
<metadata>
<processedAt/>
</metadata>
</source>
<target type="body" action="child"/>
</enrich>
<!-- Copy response body to a property -->
<enrich>
<source type="body" clone="true"/>
<target type="property" property="originalResponse"/>
</enrich>
<!-- Replace body from a property -->
<enrich>
<source type="property" property="savedPayload"/>
<target type="body"/>
</enrich>
<!-- Copy a specific element -->
<enrich>
<source clone="true" xpath="//customer/name"/>
<target xpath="//order/customerName" action="replace"/>
</enrich>
XSLT Mediator
Apply XSLT stylesheets for complex XML transformations.
<xslt key="gov:xslt/employee-transform.xsl">
<property name="department" expression="$ctx:department"/>
</xslt>
XSLT Stylesheet (employee-transform.xsl):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:emp="http://example.com/employee">
<xsl:param name="department"/>
<xsl:template match="/">
<employees>
<xsl:for-each select="//emp:employee[emp:department=$department]">
<employee>
<name><xsl:value-of select="emp:name"/></name>
<email><xsl:value-of select="emp:email"/></email>
</employee>
</xsl:for-each>
</employees>
</xsl:template>
</xsl:stylesheet>
Script Mediator
Use JavaScript or Groovy for transformations that are awkward in XML.
JavaScript:
<script language="js">
<![CDATA[
var payload = mc.getPayloadJSON();
// Flatten nested structure
var result = {
id: payload.user.id,
name: payload.user.profile.firstName + ' ' + payload.user.profile.lastName,
email: payload.user.contact.email,
roles: payload.user.roles.join(',')
};
mc.setPayloadJSON(result);
]]>
</script>
Accessing properties from script:
<script language="js">
<![CDATA[
var userId = mc.getProperty('userId');
var payload = mc.getPayloadJSON();
payload.processedBy = 'integration-layer';
payload.correlationId = mc.getProperty('MESSAGE_ID');
mc.setPayloadJSON(payload);
mc.setProperty('itemCount', String(payload.items.length));
]]>
</script>
Flow Control Mediators
Clone (Scatter)
Send the same message to multiple endpoints in parallel.
<clone>
<target>
<sequence>
<call>
<endpoint>
<http method="post" uri-template="http://audit-service/log"/>
</endpoint>
</call>
</sequence>
</target>
<target>
<sequence>
<call>
<endpoint>
<http method="post" uri-template="http://analytics-service/event"/>
</endpoint>
</call>
</sequence>
</target>
</clone>
Iterate
Split an array and process each element individually.
<!-- Input: {"orders": [{"id": 1}, {"id": 2}, {"id": 3}]} -->
<iterate expression="json-eval($.orders)" sequential="false">
<target>
<sequence>
<call>
<endpoint>
<http method="post" uri-template="http://order-service/process"/>
</endpoint>
</call>
</sequence>
</target>
</iterate>
ForEach
Process each element of an array but keep the message intact.
<foreach expression="json-eval($.items)">
<sequence>
<!-- Transform each item -->
<payloadFactory media-type="json">
<format>{"sku": "$1", "price": "$2"}</format>
<args>
<arg expression="json-eval($.itemCode)"/>
<arg expression="json-eval($.unitPrice)"/>
</args>
</payloadFactory>
</sequence>
</foreach>
<!-- Message now contains transformed items array -->
Aggregate
Collect results from Clone or Iterate back into a single message.
<clone>
<target>
<sequence>
<call>
<endpoint key="ServiceA"/>
</call>
</sequence>
</target>
<target>
<sequence>
<call>
<endpoint key="ServiceB"/>
</call>
</sequence>
</target>
</clone>
<aggregate>
<completeCondition timeout="30">
<messageCount min="2" max="2"/>
</completeCondition>
<onComplete expression="json-eval($)">
<respond/>
</onComplete>
</aggregate>
Database Mediators
DBLookup
Query a database and store results in properties.
<dblookup>
<connection>
<pool>
<driver>com.mysql.cj.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/appdb</url>
<user>app_user</user>
<password>app_pass</password>
</pool>
</connection>
<statement>
<sql><![CDATA[SELECT name, email FROM customers WHERE id = ?]]></sql>
<parameter expression="json-eval($.customerId)" type="VARCHAR"/>
<result name="customerName" column="name"/>
<result name="customerEmail" column="email"/>
</statement>
</dblookup>
<!-- Use the results -->
<log level="custom">
<property name="customer" expression="$ctx:customerName"/>
</log>
DBReport
Write data to a database.
<dbreport>
<connection>
<pool>
<driver>com.mysql.cj.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/appdb</url>
<user>app_user</user>
<password>app_pass</password>
</pool>
</connection>
<statement>
<sql><![CDATA[INSERT INTO audit_log (request_id, action, timestamp) VALUES (?, ?, ?)]]></sql>
<parameter expression="get-property('MESSAGE_ID')" type="VARCHAR"/>
<parameter value="API_CALL" type="VARCHAR"/>
<parameter expression="get-property('SYSTEM_TIME')" type="VARCHAR"/>
</statement>
</dbreport>
Common Transformation Patterns
JSON-to-JSON Mapping
Transform one JSON structure to another.
<!-- Input:
{
"first_name": "John",
"last_name": "Doe",
"date_of_birth": "1985-03-15",
"contact": { "phone": "+1234567890", "email": "john@example.com" }
}
-->
<payloadFactory media-type="json">
<format>
{
"name": "$1 $2",
"dob": "$3",
"phone": "$4",
"email": "$5"
}
</format>
<args>
<arg expression="json-eval($.first_name)"/>
<arg expression="json-eval($.last_name)"/>
<arg expression="json-eval($.date_of_birth)"/>
<arg expression="json-eval($.contact.phone)"/>
<arg expression="json-eval($.contact.email)"/>
</args>
</payloadFactory>
JSON to XML
<!-- Extract from JSON, build XML -->
<property name="orderId" expression="json-eval($.orderId)"/>
<property name="amount" expression="json-eval($.total)"/>
<payloadFactory media-type="xml">
<format>
<PurchaseOrder xmlns="http://example.com/po">
<OrderID>$1</OrderID>
<Amount currency="USD">$2</Amount>
</PurchaseOrder>
</format>
<args>
<arg expression="$ctx:orderId"/>
<arg expression="$ctx:amount"/>
</args>
</payloadFactory>
<property name="messageType" value="application/xml" scope="axis2"/>
XML to JSON
<!-- Input is XML, output must be JSON -->
<property name="id" expression="//order/id" scope="default"/>
<property name="customer" expression="//order/customer/name" scope="default"/>
<payloadFactory media-type="json">
<format>{"orderId": "$1", "customerName": "$2"}</format>
<args>
<arg expression="$ctx:id"/>
<arg expression="$ctx:customer"/>
</args>
</payloadFactory>
Array Aggregation from Multiple Services
<!-- Call two services, merge results into one array -->
<call>
<endpoint key="InternalUsersEndpoint"/>
</call>
<enrich>
<source type="body" clone="true"/>
<target type="property" property="internalUsers"/>
</enrich>
<call>
<endpoint key="ExternalUsersEndpoint"/>
</call>
<enrich>
<source type="body" clone="true"/>
<target type="property" property="externalUsers"/>
</enrich>
<payloadFactory media-type="json">
<format>
{
"internal": $1,
"external": $2
}
</format>
<args>
<arg expression="$ctx:internalUsers"/>
<arg expression="$ctx:externalUsers"/>
</args>
</payloadFactory>
<respond/>
Named Sequences
Create reusable mediation logic.
Definition:
<sequence name="addSecurityHeaders" xmlns="http://ws.apache.org/ns/synapse">
<property name="X-Request-ID" expression="get-property('MESSAGE_ID')" scope="transport"/>
<property name="X-Timestamp" expression="get-property('SYSTEM_TIME')" scope="transport"/>
<property name="X-Source" value="integration-layer" scope="transport"/>
</sequence>
Usage:
<api name="MyAPI" context="/api">
<resource methods="GET" uri-template="/data">
<inSequence>
<sequence key="addSecurityHeaders"/>
<call>
<endpoint key="BackendEndpoint"/>
</call>
<respond/>
</inSequence>
</resource>
</api>
Key Takeaways
- In Sequence handles requests; Out Sequence handles responses; Fault Sequence handles errors
- Use
callfor non-blocking service invocations (preferred oversend) - PayloadFactory is the workhorse for JSON/XML transformations
- Clone + Aggregate implements scatter-gather pattern
- Iterate splits arrays for individual processing
- DBLookup/DBReport provide direct database access from mediation
- Named sequences promote reusability across APIs and proxy services
- FreeMarker templates handle complex transformation logic
Next Steps
Continue to Chapter 9: Security Configuration to learn about securing APIs and integration flows.