Anyone who has designed RESTfull API appreciate API Kit Router available in Mule. It not only generates flows based on API definition but also route and validate messages. Our flows looks more concise and easy to read. This feature is available for both Community and Enterprise editions. For SOAP Web Services SOAP Router is available. However this time this utility works only for Enterprise Edition. Some time ago I developed a couple of services on Mule Community Edition. All services have WSDL contracts and I must say that when I now think about the implementation I would appreciate such router. So I have decided to write something similar that would work for Mule CE.
In example simple weather.wsdl file is used. WSDL file is from project https://github.com/raquel-ucl/SOAPpy-example/blob/master/wsdl/weather.wsdl.
Mule SOAP Router
APIKit for SOAP and SOAP Router have following features:
- Generating private flow based on WSDL file
- Routing SOAP messages
- Extracting headers to soap.* variables
- Well integrated with Anypoint Studio
Reference documentation is available here.
You can create new project from scratch or import wsdl file for existing one. In Anypoint Studio create new mule project. In section APIkit Settings browse for WSDL file. GUI will promptly display Service and Port drop downs. You can pick there, which ones to use. Please do not be mistaken, but you can fill this form even if you have selected Mule Server CE. When you try to run it you will receive following message:
The plugin APIKit SOAP requires an Enterprise License. Switch to a Mule-EE runtime to enable it. com.mulesource.licm.LicenseManagementFactory
After project generation you should see single mule configuration file. Within it you should see three flows. Like in the picture below:
We have one main flow with router and two private flows. By default SOAP Faults are generated for each operation. If you run this project you should be able to call two operations. Here is example response:
<br /> <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><br /> <soap:Body><br /> <soap:Fault xmlns:soap="http://www.w3.org/2003/05/soap-envelope"><br /> <faultcode>soap:Server</faultcode><br /> <faultstring>Operation [GetCitiesByCountry:/GlobalWeather/GlobalWeatherSoap/api-config] not implemented</faultstring><br /> </soap:Fault><br /> </soap:Body><br /> </soap:Envelope><br />
What I have noticed is lack of XML validation. I was able to sent malformed SOAP message. Now let’s see what we can achieve in Community Edition.
SOAP Router CE Workarounds
In CE Edition implementing SOAP routing is much more cumbersome. I will walk through each step briefly.
After receiving a SOAP request we need to forward message to flow implementing operation. This can be achieved by Choice component. It will route messages based on the SOAPAction header. In each block flow reference will connect to proper flow. It can become very complex and large element when we have a lot of operations published. Moreover we are hard coding SOAPAction values. When we change one of them in WSDL file we are obliged to do the same change in Choice component as well. In this approach we have a full control what flow or subflow we would like to invoke.
Implementation flow/subflow can have any name you like, as Choice Router does not include any naming convention. In flow logic we need to perform explicit validation of both incoming and outgoing message. However this may be a difficult to perform. We may extract body and validate against schema or we may create extra XSD schema for SOAP envelope and validate message against it. Either way this is difficult and we need to add it explicitly in each operation. We might as well opt out from validation, but it is generally a good idea to check messages against contract.
Custom SOAP Router
In the diagram below you can see all steps that SOAP Router will perform. For simplicity error handling is not depicted, although on each activity error may occur.
First we validate such simple thing like SOAPAction header. Based on this information, operation is retrieved from WSDL contract. Next, Router checks if flow for this operation exists in Mule or not. After that, Router validates incoming SOAP Body against XSD files. In case of any failure appropriate message with detailed error is thrown. For positive validation private flow is invoked and router passes SOAP Envelope as a payload. Mule ESB processes the message and returns outcome to SOAP Router for further validation. Response is validated against XSD files and if everything is OK we pass through response to the caller.
SOAP related exceptions are wrapped in SoapException class providing some meaningful description of what went wrong. Based on this you can implement exception handling strategy.
Below I have described configuration and usage of custom SOAP Router. In a couple of days this project will be available in central maven repository as well. SOAPRouter implementation is available at GitHub.
In order to configure SOAP Router you need to attach maven dependency to the pom.file:
<br /> <dependency><br /> <groupId>pl.profit-online</groupId><br /> <artifactId>soap-router</artifactId><br /> <version>1.0.0</version><br /> </dependency><br />
After that we may start configuring Mule configuration file. We need to define two beans SoapRouterConfiguration and SoapRouter.
SoapRouterConfiguration expects following properties:
- wsdlFile: location to wsdl file
- port: port of the web service
- service: service name
- schemaFiles: list of locations to XSD files
Here is the sample configuration for weather service:
<br /> <spring:bean id="SoapRouterConfiguration" name="SoapRouterConfiguration" class="pl.profitonline.soap.router.SoapRouterConfiguration" scope="singleton"><br /> <spring:property name="wsdlFile" value="wsdl/weather.wsdl"/><br /> <spring:property name="port" value="GlobalWeatherSoap" /><br /> <spring:property name="service" value="GlobalWeather"></spring:property><br /> <spring:property name="schemaFiles"><br /> <spring:list><br /> <spring:value>wsdl/schema/weather.xsd</spring:value><br /> </spring:list><br /> </spring:property><br /> </spring:bean><br />
Port and service name are picked from weather.wsdl file. SOAP Router implementation expects to have schema defined outside of the WSDL file to efficiently perform validation.
Bean SoapRouter is straightforward in configuration, you only need to supply created earlier configuration like below:
<br /> <spring:bean id="SoapRouter" name="SoapRouter" class="pl.profitonline.soap.router.SoapRouter" scope="singleton"><br /> <spring:constructor-arg index="0" ref="SoapRouterConfiguration"/><br /> </spring:bean><br />
After configuration you may drag Java Component and bind SoapRouter bean. Unfortunately in comparison to built in APIKit private flows won’t be generated for us. In other words we need to create them manually. There are some constraints that need to be met in order to work correctly with SOAP Router. Here are the rules:
- it must be a private flow (without source specified)
- name must match following pattern /[Operation_Name]/[Service_Name]/[Port_Name]/api
As you can see in the below screenshot we have a main flow soap-routerFlow that publish web service and pass incoming messages to SOAP Router component. Based on operation it may be routed to private flow /GetCitiesByCountry/GlobalWeather/GlobalWeatherSoap/api where operation logic is implemented.
In case of any error Mule can use Choice Exception Strategy to react on SoapExceptions and everything else. Sample code is here:
<br /> <choice-exception-strategy name="soap-router-exception-strategy"><br /> <catch-exception-strategy when="#[exception.causedBy(pl.profitonline.soap.exception.SoapException)]" doc:name="Catch Exception Strategy"><br /> <set-payload value="&lt;soapenv:Envelope xmlns:soapenv=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;<br /> &lt;soapenv:Body&gt;<br /> &lt;soapenv:Fault&gt;<br /> &lt;faultcode&gt;SOAP-500&lt;/faultcode&gt;<br /> &lt;faultstring&gt;#[exception.cause.message]&lt;/faultstring&gt;<br /> &lt;detail&gt;#[exception.message]&lt;/detail&gt;<br /> &lt;/soapenv:Fault&gt;<br /> &lt;/soapenv:Body&gt;<br /> &lt;/soapenv:Envelope&gt;" doc:name="Set Payload"/><br /> <set-property propertyName="Content-Type" value="text/xml" doc:name="Content-Type Property"/><br /> </catch-exception-strategy><br /> <catch-exception-strategy doc:name="Catch Exception Strategy"><br /> <set-payload value="&lt;soapenv:Envelope xmlns:soapenv=&quot;http://schemas.xmlsoap.org/soap/envelope/&quot;&gt;<br /> &lt;soapenv:Body&gt;<br /> &lt;soapenv:Fault&gt;<br /> &lt;faultcode&gt;SOAP-500&lt;/faultcode&gt;<br /> &lt;faultstring&gt;Internal Exception&lt;/faultstring&gt;<br /> &lt;detail&gt;<br /> Internal Exception occured. Please contact system administrator for further informations.<br /> &lt;/detail&gt;<br /> &lt;/soapenv:Fault&gt;<br /> &lt;/soapenv:Body&gt;<br /> &lt;/soapenv:Envelope&gt;" doc:name="Set Payload"/><br /> <set-property propertyName="Content-Type" value="text/xml" doc:name="Content-Type Property"/><br /> </catch-exception-strategy><br /> </choice-exception-strategy><br />
So what does this code do? Simply it detects type of the exception (SoapException or else) and returns SOAP Fault message. That is all.
I have just introduced custom SOAP Router. It may be useful for developing SOAP services on Mule ESB CE edition. It has some advantages like concise code, input/output validation, consistent naming convention. Opposite to APIKit for SOAP (EE) my solution does not offer autogenerting flows. Most noteworthy is lack of validation functionality and therefore we need to perform this on our own. If you have any thoughts or improvements please share and I will improve the code further and make it almost perfect :).