Deploying a Service API in WSO2 API Manager.
When using WSO2 API Manager, you might have come across scenarios where you wanted to expose a service API which is accessible to any external entity without a valid token. This article will walk you through the implementation of a custom service API for WSO2 API Manager.
This will basically include adding a custom class mediator which is configured to invoke by a synapse API.
Creating Maven Bundle Project
(A detailed tutorial about creating OSGI Bundle is explained here)
First, we need to implement the mediation logic of the class mediator and bundle it as a OSGI bundle.
In order to properly bundle the resources, we need the maven bundle plugin configured in the project’s pom.xml file.
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>org.wso2.mediators.*</Export-Package>
<Import-Package>
</Import-Package>
<DynamicImport-Package>*</DynamicImport-Package>
<Embed-Dependency>
org.sample.dependency.one;scope=compile|runtime,
org.sample.dependency.two;scope=compile|runtime,
org.sample.dependency.three;scope=compile|runtime
</Embed-Dependency>
<Fragment-Host>synapse-core</Fragment-Host>
</instructions>
</configuration>
</plugin>
If you are using 3rd party dependencies in your class mediator, you need to add them within the <Embed-Dependency> tags, specifying the relevant scope.
You will also need to add the maven compiler plugin to compile the sources.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
The following dependencies should also be there to support the basic synapse level functionalities within the class mediator.
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-api</artifactId>
<version>1.2.22</version>
</dependency>
<dependency>
<groupId>org.apache.synapse</groupId>
<artifactId>synapse-core</artifactId>
<version>${synapse.version}</version>
</dependency>
<dependency>
<groupId>org.apache.synapse</groupId>
<artifactId>synapse-commons</artifactId>
<version>${synapse.version}</version>
</dependency>
The complete pom.xml file would looks like this,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>bundle</packaging>
<groupId>org.wso2.custom</groupId>
<artifactId>SampleClassMediator</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<synapse.version>2.1.7-wso2v19</synapse.version>
<carbon.apimgt.version>6.6.163</carbon.apimgt.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.ws.commons.axiom</groupId>
<artifactId>axiom-api</artifactId>
<version>1.2.22</version>
</dependency>
<dependency>
<groupId>org.apache.synapse</groupId>
<artifactId>synapse-core</artifactId>
<version>${synapse.version}</version>
</dependency>
<dependency>
<groupId>org.apache.synapse</groupId>
<artifactId>synapse-commons</artifactId>
<version>${synapse.version}</version>
</dependency>
<dependency>
<groupId>org.wso2.carbon.apimgt</groupId>
<artifactId>org.wso2.carbon.apimgt.impl</artifactId>
<version>${carbon.apimgt.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<encoding>UTF-8</encoding>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
<Export-Package>samples.mediators.*</Export-Package>
<Import-Package>
</Import-Package>
<DynamicImport-Package>*</DynamicImport-Package>
<Embed-Dependency>
hapi-fhir-base;scope=compile|runtime,
org.hl7.fhir.r4;scope=compile|runtime,
org.hl7.fhir.r5;scope=compile|runtime,
hapi-fhir-server;scope=compile|runtime,
</Embed-Dependency>
<Fragment-Host>synapse-core</Fragment-Host>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
Implementing Mediation Logic
(Official documentation about Creating a class mediator in WSO2 API Manager)
When creating a class mediator, we have to write a Java class that extends the org.apache.synapse.mediators.AbstractMediator class and implement the necessary methods.
One of the key methods that we need to implement is the mediate() method. In that method, we can refer to the synapse message context and access the properties of the message context and use them as variables and perform operations.
package samples.mediators;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.util.AXIOMUtil;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.synapse.MessageContext;
import org.apache.synapse.commons.json.JsonUtil;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.mediators.AbstractMediator;
import java.util.Map;
import javax.xml.stream.XMLStreamException;
public class SampleClassMediator extends AbstractMediator {
private static final Log log = LogFactory.getLog(SimpleClassMediator.class);
public SampleClassMediator() {
}
public boolean mediate(MessageContext mc) {
log.info("Class Mediator Started");
PayloadObject resource = null;
String tenant = mc.getProperty("tenant.info.domain").toString();
resource = createPayloadObject(tenant);
if (mc instanceof Axis2MessageContext) {
org.apache.axis2.context.MessageContext axisMsgCtx =
((Axis2MessageContext) mc).getAxis2MessageContext();
Object headers = axisMsgCtx.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
String targetContentType = "application/json";
if (headers instanceof Map) {
Map headersMap = (Map) headers;
targetContentType = (String) headersMap.get("Accept");
} else {
log.debug("No HTTP Accept header found");
}
if ("application/xml".equalsIgnoreCase(targetContentType) ||
"text/xml".equalsIgnoreCase(targetContentType)) {
try {
JsonUtil.removeJsonPayload(axisMsgCtx);
String payload;
payload = serializeToXML(resource);
OMElement omXML = AXIOMUtil.stringToOM(payload);
axisMsgCtx.getEnvelope().getBody().addChild(omXML);
axisMsgCtx.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/xml");
} catch (XMLStreamException e) {
throw new MediatorException(
"Error occurred while populating XML payload in the message context", e);
}
} else {
String payload;
payload = serializeToJSON(resource);
try {
JsonUtil.getNewJsonPayload(axisMsgCtx, payload, true, true);
axisMsgCtx.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/json");
} catch (AxisFault axisFault) {
throw new MediatorException("Error occurred while populating JSON payload in message context",
axisFault);
}
}
axisMsgCtx.removeProperty("NO_ENTITY_BODY");
}
return true;
}
}
Packaging and Deploying
Since the class mediator sources are bundled as an OSGI bundle, we can refer to the packages which are going to be available in the APIM runtime; in this case, you don’t need to embed the carbon.apimgt dependency to the package.
Note: If you have a distributed deployment, you need to pay extra attention to the availability of the relevant APIM Admin Service.
Build the project by executing mvn clean install in the project’s root directory and copy the bundled jar file to <APIM_HOME>/repository/components/dropins folder.
Then you need to create a synapse API which acts as the initiator of the class mediator. Content of a sample synapse API would looks as follows,
<api xmlns=”http://ws.apache.org/ns/synapse" name=”_WSO2AMMetadataAPI_” context=”/metadata”>
<resource methods=”GET” url-mapping=”/*” faultSequence=”_token_fault_”>
<inSequence>
<log level=”full”></log>
<class name=”org.wso2.mediators.SampleClassMediator”>
</class>
<respond />
</inSequence>
<outSequence></outSequence>
</resource>
<handlers>
<handler class=”org.wso2.carbon.apimgt.gateway.handlers.common.SynapsePropertiesHandler” />
</handlers>
</api>
When you create an xml file containing above configurations and copied it into <APIM_HOME>/repository/deployment/server/synapse-configs/default/api folder,
You’ll be able to observe in the startup logs and verify that the API is properly deployed. Also you can to execute the mediation logic simply by calling the specified context.
(When you invoke the <gateway_host:port>/metadata url, the deployed SampleClassMediator class’ mediate method will execute.)