Skip to Main Content

How to create a custom mediator and associate it with Tag in WSO2

WSO2 offers multiple tools to carry out our APIs, be it via API Manager or the Enterprise Integrator. In addition, it allows us to carry out mediation flows or sequences in their input or output. In these sequences, WSO2 also offers multiple mediators which allow us to perform message operations.

However, at times these mediators don’t do everything we need them to. This can involve generating a HMAC key based on specific data, reading the attached file’s content, or treating the transport headers in a specific way. For such actions we have the custom mediators, another tool offered by WSO2 to extend its functionality and message processing.

Creating a Custom Mediator: Example

In order to see how they work, we are going to create an example based on the following premise:

One of the best practices when calling an external system from a sequence is to remove transport headers and set the new ones necessary for the new system yourself. By doing so, we avoid sending redundant or erroneous information.

We could do this via the property mediator in the following way. However, we want to go a little further:

<property name="TRANSPORT_HEADERS" action="remove" scope="axis2"/>

The set of transport headers might be 5 in total, but we know that there are two identical ones which we need and want to keep. Our mediator allows us to indicate the ones we want to keep and the ones we want to remove.

Abstract Mediator

We need to create a Java project with these two characteristics:

  • It needs to be named bundle project and will create a JAR once it is packaged. You can use Maven to help you do it.
  • It needs to include the library org.apache.synapse.synapse-core as a dependency.

In the Java project we will include all classes which will allow us to carry out the logic operation we want. We also need to take into consideration that the main class, that is to say, the one we will invoke from the sequence further down the line, needs to implement the AbstractMediator class.

public class RemoveTrpHeadersMediator extends AbstractMediator {
    private String exclude;
    public boolean mediate(final MessageContext context) {
        org.apache.axis2.context.MessageContext mc = ((org.apache.synapse.core.axis2.Axis2MessageContext) context).getAxis2MessageContext();
        Map<String, String> transportHeaders = (Map<String, String>) mc.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        if (StringUtils.isNotEmpty(exclude)) {
            List<String> headersToMaintain = Arrays.stream(exclude.split(",")).map(String::trim).collect(Collectors.toList());
            transportHeaders.keySet().retainAll(headersToMaintain);
        } else {
            transportHeaders.clear();
        }
        return true;
    }
    // getters and setters
}

The next step is to include the packaged JAR in the physical file lib of our WSO2 product and restart it. 

Class Mediator

Everything is now correctly set up. The only thing left to do is to use our custom mediator in the sequence. If we follow the example, we want to remove all transport headers except Content-Type and Accept. In order to do so, we need to carry out the following invocation:

<class name="com.chakray.mediator.trpHeadersMgmt.RemoveTrpHeadersMediator" >
<property name="exclude" value="Accept, Content-Type" />
</class>

As you can see, in order to use our custom mediator, we have to indicate the name and exact package. We have also had to define a specific logic to accept a value list associated to a specific attribute.

These two details can be solved through the following section, which focuses on creating our own mediator with a specific tag included.

AbstractMediatorFactory and AbstractMediatorSerializer

Through these utility classes we can implement classes that allow us to create a mediator and manage its settings via XML.

AbstractMediatorFactory allows us to create the mediator instance through the XML code which identifies it. We will indicate what will be our tag: removeTrpHeaders.

public class RemoveTrpHeadersMediatorFactory extends AbstractMediatorFactory {
    public static final QName RemoveTrpHeadersMediator_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "removeTrpHeaders");
    public static final QName EXCLUDE_Q = new QName(XMLConfigConstants.SYNAPSE_NAMESPACE, "exclude");
    public QName getTagQName() {
        return RemoveTrpHeadersMediator_Q;
    }
    protected Mediator createSpecificMediator(final OMElement elem, final Properties properties) {
        RemoveTrpHeadersMediator removeTrpHeadersMediator = new RemoveTrpHeadersMediator();
        processAuditStatus(removeTrpHeadersMediator, elem);
        Iterator<OMElement> iter = elem.getChildrenWithName(EXCLUDE_Q);
        while (iter.hasNext()) {
            OMElement excludeElem = iter.next();
            OMAttribute name = excludeElem.getAttribute(ATT_NAME);
            if (name == null) {
                String msg = "The 'name' attribute is required for 'exclude' definition";
                throw new SynapseException(msg);
            } 
            removeTrpHeadersMediator.getHeadersToExclude().add(name.getAttributeValue());
        }
        addAllCommentChildrenToList(elem, removeTrpHeadersMediator.getCommentsList());
        return removeTrpHeadersMediator;
    }
}

Through AbstractMediatorSerializer we will carry out the opposite step.

public class RemoveTrpHeadersMediatorSerializer extends AbstractMediatorSerializer {
    public String getMediatorClassName() {
        return RemoveTrpHeadersMediator.class.getName();
    }
    protected OMElement serializeSpecificMediator(final Mediator m) {
        if (!(m instanceof RemoveTrpHeadersMediator)) {
            handleException("Unsupported mediator passed in for serialization : " + m.getType());
        }
        RemoveTrpHeadersMediator mediator = (RemoveTrpHeadersMediator) m;
        OMElement removeTrpHeadersMed = fac.createOMElement(MediatorConstants.RemoveTrpHeadersMediator_Q);
        saveTracingState(removeTrpHeadersMed, mediator);
        for (String nameHeaderToExclude : mediator.getHeadersToExclude()) {
            OMElement excludeElem = fac.createOMElement(MediatorConstants.EXCLUDE_Q);
            if (nameHeaderToExclude != null) {
                excludeElem.addAttribute(fac.createOMAttribute("name", nullNS, nameHeaderToExclude));
            } else {
                handleException("Invalid header to exclude. Name required");
            }
            removeTrpHeadersMed.addChild(excludeElem);
        }
        serializeComments(removeTrpHeadersMed, mediator.getCommentsList());
        return removeTrpHeadersMed;
    }
}

In order for the mediators to be taken into account when they are deployed again in our WSO2 product (that has been restarted), we must carry out a new setup step. This step is based on a SPI feature included in Java, which allows us to load and dynamically use implementations of particular services in our apps – in this case the mediators.

In order to achieve this we need to create two files named org.apache.synapse.config.xml.MediatorFactory/MediatorSerializer. They will include the full names of the classes we created during this step and which implement each of these interfaces and will be stored in the META-INF file of our library.

Custom Mediator

Now, in order to indicate a header to be kept we will use the “exclude” tag. What’s more, this can be repeated it as many times as we want. Therefore, as a means of improving our class RemoveTrpHeadersMediator, we will be able to store the headers to be excluded from removal in a string list without the need to store them in a single attribute separated by commas, as we did in the previous step. 

Therefore, the invocation of our custom mediator after these modifications would be as follows:

<removeTrpHeaders >
<exclude name="Accept" />
<exclude name="Content-Type" />
</removeTrpHeaders>