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

CategoryMediatorsPurpose
CoreLog, Property, Call, Send, RespondBasic message handling
TransformPayloadFactory, Enrich, XSLT, DataMapper, ScriptMessage transformation
FilterFilter, Switch, ValidateConditional processing
FlowClone, Iterate, Aggregate, ForEachMessage splitting/aggregation
DatabaseDBLookup, DBReportDatabase operations
ExtensionClass, Script, BeanCustom logic

Core Mediators in Depth

Property Mediator

Set, read, and remove properties in the message context.

Scopes:

ScopeDescriptionLifetime
defaultSynapse message contextCurrent message flow
axis2Axis2 message contextCurrent message
transportHTTP headersCurrent transport
operationOperation scopeAcross in/out sequences
registryRegistry entriesPersistent

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

MediatorBlockingResponseUse Case
CallNoContinues in same sequenceDefault choice for service calls
SendNoResponse goes to Out SequenceLegacy; use at end of In Sequence
CalloutYesBlocks until response receivedWhen 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 call for non-blocking service invocations (preferred over send)
  • 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.