Last Updated on 03/05/2021 by Patryk Bandurski
Anyone who has designed RESTfull API appreciates the API Kit Router available in Mule. It not only generates flows based on API definition but also routes and validates messages. Our flows look 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 had WSDL contracts and I must say that when I now think about the implementation I would appreciate such a router. So I have decided to write something similar that would work for Mule CE.
In the example I use simple weather.wsdl file.
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 a new project from scratch or import a WSDL file for the existing one. In Anypoint Studio create a 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 the 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 the 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 an example response:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <soap:Fault xmlns:soap="http://www.w3.org/2003/05/soap-envelope"> <faultcode>soap:Server</faultcode> <faultstring>Operation [GetCitiesByCountry:/GlobalWeather/GlobalWeatherSoap/api-config] not implemented</faultstring> </soap:Fault> </soap:Body> </soap:Envelope>
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 Workaround
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 the message to the flow implementing the operation. The Choice component can achieve this. It will route messages based on the SOAPAction header. In each block, flow reference will connect to proper flow. It can become a very complex and large element when we have a lot of operations published. Moreover, we are hardcoding SOAPAction values. When we change one of them in the WSDL file, we are obliged to do the same change in the Choice component. In this approach, we have full control over 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 the flow logic, we need to perform explicit validation of both incoming and outgoing messages. However, this may not be easy to perform. We may extract the body and validate against a schema, or we may create an extra XSD schema for the SOAP envelope and validate the message against it. Either way, this isn’t easy, and we need to add it explicitly in each operation. We might opt out from bvalidation, but it is generally good to check messages against the 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 things as the SOAPAction header. Based on this information, the operation is retrieved from the WSDL contract. Next, the Router checks if the flow for this operation exists in Mule or not. After that, the Router validates the incoming SOAP Body against XSD files. In case of any failure appropriate message with a detailed error is thrown. For positive validation, private flow is invoked, and the router passes SOAP Envelope as a payload. Mule ESB processes the message and returns the outcome to SOAP Router for further validation. The response is validated against XSD files, and if everything is OK, we pass through the 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 an exception handling strategy.
Below I have described the configuration and usage of the custom SOAP Router. SOAPRouter implementation is available at GitHub.
In order to configure SOAP Router you need to attach maven dependency to the pom.file:
<dependency> <groupId>pl.profit-online</groupId> <artifactId>soap-router</artifactId> <version>1.0.0</version> </dependency>
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:
<spring:bean id="SoapRouterConfiguration" name="SoapRouterConfiguration" class="pl.profitonline.soap.router.SoapRouterConfiguration" scope="singleton"> <spring:property name="wsdlFile" value="wsdl/weather.wsdl"/> <spring:property name="port" value="GlobalWeatherSoap"/> <spring:property name="service" value="GlobalWeather"/> <spring:property name="schemaFiles"> <spring:list> <spring:value>wsdl/schema/weather.xsd</spring:value> </spring:list> </spring:property> </spring:bean>
Port and service names are picked from the weather.wsdl file. SOAP Router implementation expects to have the 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:
<spring:bean id="SoapRouter" name="SoapRouter" class="pl.profitonline.soap.router.SoapRouter" scope="singleton"> <spring:constructor-arg index="0" ref="SoapRouterConfiguration"/> </spring:bean>
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. Some constraints need to be met 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 on the screenshot below, we have a main flow soap-routerFlow that publishes web service and pass incoming messages to the SOAP Router component. Based on the 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:
<choice-exception-strategy name="soap-router-exception-strategy"> <catch-exception-strategy when="#[exception.causedBy(pl.profitonline.soap.exception.SoapException)]" doc:name="Catch Exception Strategy"> <set-payload value="<soapenv:Envelope xmlns:soapenv=&amp;quot;http://schemas.xmlsoap.org/soap/envelope/&amp;quot;> <soapenv:Body> <soapenv:Fault> <faultcode>SOAP-500</faultcode> <faultstring>#[exception.cause.message]</faultstring> <detail>#[exception.message]</detail> </soapenv:Fault> </soapenv:Body> </soapenv:Envelope>" doc:name="Set Payload"/> <set-property propertyName="Content-Type" value="text/xml" doc:name="Content-Type Property"/> </catch-exception-strategy> <catch-exception-strategy doc:name="Catch Exception Strategy"> <set-payload value="<soapenv:Envelope xmlns:soapenv=&amp;quot;http://schemas.xmlsoap.org/soap/envelope/&amp;quot;> <soapenv:Body> <soapenv:Fault> <faultcode>SOAP-500</faultcode> <faultstring>Internal Exception</faultstring> <detail> Internal Exception occured. Please contact system administrator for further informations. </detail> </soapenv:Fault> </soapenv:Body> </soapenv:Envelope>" doc:name="Set Payload"/> <set-property propertyName="Content-Type" value="text/xml" doc:name="Content-Type Property"/> </catch-exception-strategy> </choice-exception-strategy>
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 a custom SOAP Router. It may be useful for developing SOAP services on the 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 autogenerating flows. Most noteworthy is the 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 :).