MUnit testing a flow with collection-aggregator - unit-testing

I am trying to write MUnit tests for my Mule flows. I want to write a test for the below flow
<flow name="DownloadFTPFileIntoLocalFlow" processingStrategy="synchronous" tracking:enable-default-events="true">
<quartz:inbound-endpoint jobName="Source-File-Scheduler" cronExpression="${source.pollingfrequency}" startDelay="10000" responseTimeout="10000" doc:name="Quartz">
<quartz:endpoint-polling-job>
<quartz:job-endpoint ref="InputSFTPEndpoint"/>
</quartz:endpoint-polling-job>
</quartz:inbound-endpoint>
<logger message="DownloadFTPFileIntoLocalFlow #[payload.getClass().getName()]" level="INFO" doc:name="Logger"/>
<set-property propertyName="MULE_CORRELATION_GROUP_SIZE" value="#[java.lang.Integer.MAX_VALUE]" doc:name="GroupsizeForExceptionAggregator"/>
<set-property propertyName="MULE_CORRELATION_ID" value="#[java.util.UUID.randomUUID().toString()]" doc:name="corelationIdForExceptionAggregator"/>
<set-variable variableName="originalPayload" value="#[payload]" doc:name="originalPayload"/>
<byte-array-to-object-transformer doc:name="Byte Array to Object"/>
<flow-ref name="ProcessCSVFlow" doc:name="ProcessCSVFlow" />
<exception-strategy ref="Default_Exception_Strategy" doc:name="DownloadFTPFileIntoLocalFlow Strategy"/>
</flow>
<sub-flow name="ProcessCSVFlow" tracking:enable-default-events="true">
<transformer ref="enrichWithHeaderAndEndOfFileTransformer" doc:name="headerAndEOFEnricher" />
<set-variable variableName="outputfilename" value="#['Mercury'+server.dateTime.year+server.dateTime.month+server.dateTime.dayOfMonth+server.dateTime.hours+server.dateTime.minutes+server.dateTime.seconds+'.csv']" doc:name="outputfilename"/>
<!-- <set-variable variableName="outputfilename" value="#['Mercury'+server.dateTime.year+':'+server.dateTime.month+':'+server.dateTime.dayOfMonth+'::'+server.dateTime.hours+':'+server.dateTime.minutes+':'+server.dateTime.seconds+'.csv']" doc:name="outputfilename"/> -->
<sftp:outbound-endpoint exchange-pattern="one-way" connector-ref="DestinationSFTP" host="${destination.host}" port="22" responseTimeout="10000" doc:name="DestinationSFTP"
outputPattern="#[outputfilename]" path="${destination.path}" user="${destination.username}" password="${destination.password}"/>
<gzip-compress-transformer/>
<sftp:outbound-endpoint exchange-pattern="one-way" connector-ref="InputSFTP" host="${source.host}" port="22" responseTimeout="10000" doc:name="SourceArchiveSFTP"
outputPattern="#[outputfilename].gzip" path="Archive" user="${source.username}" password="${source.password}"/>
<component doc:name="Delete Read File">
<singleton-object class="component.DeleteProcessedFileComponent">
<property key="host" value="${source.host}"/>
<property key="username" value="${source.username}"/>
<property key="password" value="${source.password}"/>
<property key="workingDirectory" value="${source.path}"/>
</singleton-object>
</component>
<parse-template location="successmessagetemplate.txt" doc:name="Success Template"/>
<smtp:outbound-endpoint host="${smtp.host}" port="${smtp.port}" user="${smtp.from.address}" password="${smtp.from.password}"
to="${smtp.to.address}" from="${smtp.from.address}" subject="${mail.success.subject}" responseTimeout="10000"
doc:name="SuccessEmail" connector-ref="Gmail"/>
<logger message="Process completed successfully" level="INFO" doc:name="Logger"/>
</sub-flow>
Exception handling block
<catch-exception-strategy name="Default_Exception_Strategy">
<flow-ref name="ExceptionHandlingSubflow" doc:name="ExceptionHandlingSubflow"/>
</catch-exception-strategy>
<sub-flow name="ExceptionHandlingSubflow" tracking:enable-default-events="true">
<collection-aggregator timeout="60000" failOnTimeout="false" doc:name="Exception Aggregator"/>
<logger message="Exception has occured Payload is #[payload] and Message is #[message]" level="ERROR" doc:name="Logger"/>
<parse-template location="errormessagetemplate.txt" doc:name="Error Template"/>
<smtp:outbound-endpoint host="${smtp.host}" port="${smtp.port}" user="${smtp.from.address}" password="${smtp.from.password}"
to="${smtp.to.address}" from="${smtp.from.address}" subject="${mail.failure.subject}" responseTimeout="10000"
doc:name="ErrorEmail" connector-ref="Gmail"/>
</sub-flow>
The interesting bit is the exception sub-flow, especially the collection-aggregator
My unit test is
#Test
public void whenMultipleExceptionsOccurInFlow_itShouldSendOnlyOneFailureEmail() throws Exception {
whenMessageProcessor("collection-aggregator")
.withAttributes(attribute("name").ofNamespace("doc").withValue("Exception Aggregator")).thenReturnSameEvent();
destinationSFTP.thenThrow(new RuntimeException("Dummy Exception destinationSFTP"));
MuleEvent testEvent = PropertyEnricher.enrich(testEvent(IOUtils.toInputStream("hello,dummy,payload"))).get();
runFlow("DownloadFTPFileIntoLocalFlow", testEvent);
verifyCallOfMessageProcessor("outbound-endpoint").ofNamespace("smtp")
.withAttributes(attribute("name").ofNamespace("doc").withValue("ErrorEmail"))
.times(1);
}
Now if I do not mock the collection aggregator out my test does not pass, I can understand that this is tricky as the aggregator has a "pause" within it and hence is not an ideal candidate for a unit test, however from a technical standpoint I want to understand what is causing the unit test to fail (when collection-aggregator is not mocked).
My test fails when the collection-aggregator is not mocked.
junit.framework.AssertionFailedError: On smtp:outbound-endpoint.Expected 1 but got 0 calls
at junit.framework.Assert.fail(Assert.java:50)
at org.mule.munit.common.mocking.MunitVerifier.times(MunitVerifier.java:86)
at nz.co.mightyriver.ProcessCsvTest.whenMultipleExceptionsOccurInFlow_itShouldSendOnlyOneFailureEmail(ProcessCsvTest.java:100)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)

So I've been trying to reproduce this issue:
Production Code
<http:listener-config name="HTTP_Listener_Configuration" host="0.0.0.0" port="9090" doc:name="HTTP Listener Configuration"/>
<flow name="stack-munit-and-aggregationFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/" doc:name="HTTP"/>
<set-payload value="#['lalero_' + new java.util.Date().toString()]" doc:name="Set Payload"/>
<flow-ref name="stack-munit-and-aggregationSub_Flow" doc:name="stack-munit-and-aggregationSub_Flow"/>
<set-payload doc:name="Set Payload" value="#[payload.toString()]"/>
</flow>
<sub-flow name="stack-munit-and-aggregationSub_Flow">
<collection-aggregator failOnTimeout="true" doc:name="Collection Aggregator" timeout="10"/>
</sub-flow>
Test Code
package org.mule.munit;
import org.junit.Assert; import org.junit.Test; import org.mule.api.MuleEvent; import org.mule.api.MuleException; import org.mule.munit.runner.functional.FunctionalMunitSuite;
public class TheTest extends FunctionalMunitSuite {
#Test
public void aTest() throws MuleException, Exception { MuleEvent event = runFlow("stack-munit-and-aggregationFlow", testEvent(""));
String payload = (String) event.getMessage().getPayload();
Assert.assertTrue(payload.contains("lalero"));
} }
If you check this code you'll notice that I'm not mocking out the collection aggregator. After a few tests I wasn't able to reproduce your error.
I think the issue could somewhere else.
Could you please share you're code so I can investigate further?
World of warning though, due to an issue discovered in:
How to mock a Java component within Mule Flow using MUnit
You may find a problem if you try to directly test your sub-flow ExceptionHandlingSubFlow. But as you were not doing that in your example code I don't think those two are related.
Cheers!

Related

Add ws-security to flow in Mule

I am using mule without anypoint studio.
Exposed webservice in mule. Client sends a request, I transform the message (f.e. fill in some fields) and forward it to the external webservice (not managed by me).
Java classes for the external webservice were created using wsimport based on their wsdl
They require wss xml signature based on the keystore they gave me
Their service (and wsdl) is accessible via https
My goal: I want to add ws-security (just Signature action for now) in order to be able to make requests to their WS.
In conclusion: Client sends a request to WS on the mule I own (http), I transform it,
add ws-security and send it to the external webservice (https).
This is my WS:
#WebService
public interface LFlow {
#WebMethod
String exec(#WebParam(name = "pname") String pname,
#WebParam(name = "mname") String mname,
#WebParam(name = "parameters") String parameters);
}
And the request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:abc="http://a.b.c/">
<soapenv:Header/>
<soapenv:Body>
<abc:exec>
<pname>B</pname>
<mname>getC</mname>
<parameters>smth</parameters>
</abc:exec>
</soapenv:Body>
</soapenv:Envelope>
This is mule config I have for that:
<?xml version="1.0" encoding="UTF-8"?>
<mule <!--(...)--> >
<context:property-placeholder location="classpath:l.properties, classpath:wss.properties"
ignore-resource-not-found="true"/>
(...)
<flow name="lFlow">
<!-- Defined in other place: <http:connector name="domain-http-connector" /> -->
<http:inbound-endpoint connector-ref="domain-http-connector" address="${l.bind.address}" exchange-pattern="request-response"
doc:name="HTTP">
<cxf:jaxws-service serviceClass="a.b.c.LFlow" mtomEnabled="true">
<cxf:outFaultInterceptors>
<spring:bean class="a.b.c.interceptors.FaultSoapInterceptor"/>
</cxf:outFaultInterceptors>
</cxf:jaxws-service>
</http:inbound-endpoint>
<choice doc:name="Forward to proper process">
<!--(...)-->
<when expression="#[payload[0] == 'B']">
<flow-ref name="b" doc:name="b"/>
</when>
</choice>
</flow>
<!--(...)-->
<sub-flow name="b">
<choice doc:name="forward to proper method">
<!--(...)-->
<when expression="#[payload[1] == 'getC']">
<invoke object-ref="aHandler" method="getC" doc:name="Get C element"
methodArgumentTypes="java.lang.String" methodArguments="#[payload[2]]"/>
</when>
</choice>
</sub-flow>
</mule>
the wss.properties file is in the src/main/resources directory and looks like this:
org.apache.ws.security.crypto.provider=org.apache.ws.security.components.crypto.Merlin

> org.apache.ws.security.crypto.merlin.keystore.type=jks

> org.apache.ws.security.crypto.merlin.keystore.password=pass

> org.apache.ws.security.crypto.merlin.keystore.alias=alias
org.apache.ws.security.crypto.merlin.file=path-to-keystore
handler on my side that does some transformations (not in this exmaple for simplicity):
#Component
public class AHandler {
private final AService aService;
#Autowired
public AHandler(final AService aService) {
this.aService = aService;
}
public GetCResponse getC(final String jsonPayload) {
return aService.getC(mapToGetCRequest(jsonPayload));
}
}
then it is routed to the class that is supposed to send it to the external web-service:
#Service
public class AServiceImpl implements AService {
// (...)
#Override
public GetCResponse getC(final GetCRequest request) {
return getInstance().getC(request);
}
private BInterface getInstance() {
/**
I have generated *.jar for the external B service using wsimport from their wsdl-url
*/
final B service = new B();
final BInterface port = service.getBSOAP();
final BindingProvider bindingProvider = (BindingProvider) port;
final Map<String, Object> context = bindingProvider.getRequestContext();
context.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, /*wsdl-url*/);
context.put("thread.local.request.context", "true");
return port;
}
}
and this is part of the wsdl:
(...) <?xml name=B>
</wsdl:types>
<wsdl:message name="GetCRequest">
<wsdl:part element="b:GetCRequest" name="payload">
</wsdl:part>
</wsdl:message>
<wsdl:message name="GetCResponse">
<wsdl:part element="b:GetCResponse" name="payload">
</wsdl:part>
<wsdl:portType name="BInterface">
<wsdl:operation name="getC">
<wsdl:input message="b:GetCRequest">
</wsdl:input>
<wsdl:output message="b:GetCResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="BSOAPBinding" type="b:BInterface">
<soap:binding style="b" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getC">
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="B">
<wsdl:port binding="b:BSOAPBinding" name="BSOAP">
<soap:address location="https://b.local/cxf/ba/b"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Communication works in soapui. I added outgoing ws-security configuration, added "signature" entry, chosen previously added keystore, with alias and password, and there it works. But I can't make it work in mule, hence the question.
As far as I know, I should either:
create WSS4JOutInterceptor with configured: action, signatureUser, signaturePropFile, passwordCallbackRef and somehow connect it with mule outgoing messages or
add <jaxws-client> or <proxy-client> with wss config embedded:
<cxf:ws-security>
<cxf:ws-config>
<cxf:property key="action" value="Signature"/>
<cxf:property key="signaturePropFile" value="wss.properties"/>
<cxf:property key="passwordCallbackClass" value="com.mulesoft.mule.example.security.PasswordCallback"/>
</cxf:ws-config>
</cxf:ws-security>
But I just can't make it work. I would really appreciate any help
Edited (24.08.2021T12:03:00Z):
Mule sdk 3.8.0
To answer what happens when I apply my solutions is complicated. I am not sure where exactly I should put the wss config. Should I use <jaxws-client> or <proxy-client> ? should it be in the <subflow> or <flow> ? what "attributes" to these elements I should pass (bare minimum, required to test if it even works) ? or maybe I should use <cxf:inInterceptors>/<cxf:outInterceptors> ?
I tried different configurations but I am not sure if I did it the right way, so the errors I was getting were probably results of my improper use. So I didn't put them here, cause they might make it harder to read my question.
Edited (24.08.2021T12:54:00Z):
But according to the doc:
proxy-client provides raw SOAP and WS-* processing for outgoing XML
messages, allowing you to send outgoing messages in raw XML form and
apply things like WS-Security to them.
I should use <proxy-client> and I believe it should be in the "subflow":
<sub-flow name="b">
<cxf:proxy-client>
<cxf:ws-security>
<cxf:ws-config>
<cxf:property key="action" value="Signature"/>
<cxf:property key="signatureUser" value="keystore-alias"/>
<cxf:property key="signaturePropFile" value="wss.properties"/>
<cxf:property key="passwordCallbackClass" value="com.mulesoft.mule.example.security.PasswordCallback"/>
</cxf:ws-config>
</cxf:ws-security>
</cxf:proxy-client>
<choice doc:name="forward to proper method">
<!--(...)-->
<when expression="#[payload[1] == 'getC']">
<invoke object-ref="aHandler" method="getC" doc:name="Get C element"
methodArgumentTypes="java.lang.String" methodArguments="#[payload[2]]"/>
</when>
</choice>
</sub-flow>
Is that correct ? what attributes should I define for <cxf:proxy-client> ? or maybe instead of defining <cxf:ws-security> inside, I should define interceptors like this:
<bean id="clientWss4jOutInterceptor" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
<constructor-arg>
<map>
<entry key="action" value="Signature"/>
<entry key="signatureUser" value=""/>
<entry key="signaturePropFile" value=""/>
<entry key="passwordCallbackRef" value-ref=""/>
</map>
</constructor-arg>
</bean>
(...)
<sub-flow name="b">
<cxf:proxy-client doc:name="Proxy client">
<cxf:outInterceptors>
<spring:bean id="clientWss4jOutInterceptor">
</spring:bean>
</cxf:outInterceptors>
</cxf:proxy-client>
(...)
Edited (25.08.2021T11:28:00Z):
When try to use consumer like this (mule-config.xml):
<?xml version="1.0" encoding="UTF-8"?>
<mule
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tls="http://www.mulesoft.org/schema/mule/tls"
version="CE-3.8.0"
xmlns:ws="http://www.mulesoft.org/schema/mule/ws"
(...)
<tls:context name="tlsContext">
<tls:key-store
path="C:\\Users\\john\\Documents\\integrationB.keystore"
keyPassword="privatekey-password"
password="keystore-password"
alias="alias" />
</tls:context>
<!--service, port, wsdl-url, address - all taken from wsdl-->
<ws:consumer-config
name="WebServiceConsumer"
serviceAddress="https://b.local/cxf/ba/b"
wsdlLocation="https://b.local/cxf/ba/b?wsdl"
service="B"
port="BSOAP">
<ws:security>
<ws:wss-sign tlsContext-ref="tlsContext" />
</ws:security>
</ws:consumer-config>
<flow name="lFlow">
<http:inbound-endpoint
connector-ref="domain-http-connector"
address="${l.bind.address}"
exchange-pattern="request-response"
doc:name="HTTP">
<cxf:jaxws-service serviceClass="a.b.c.LFlow" mtomEnabled="true">
<cxf:outFaultInterceptors>
<spring:bean class="a.b.c.interceptors.FaultSoapInterceptor"/>
</cxf:outFaultInterceptors>
</cxf:jaxws-service>
</http:inbound-endpoint>
<http:listener config-ref="HTTP_Listener_Configuration" path="*" doc:name="HTTP">
<http:response-builder statusCode="200"/>
</http:listener>
<ws:consumer config-ref="WebServiceConsumer" operation="getC" doc:name="Get C element"/>
<choice doc:name="Forward to proper process">
<!--(...)-->
<when expression="#[payload[0] == 'B']">
<flow-ref name="b" doc:name="b"/>
</when>
</choice>
</flow>
<!--(...)-->
<sub-flow name="b">
<choice doc:name="forward to proper method">
<!--(...)-->
<when expression="#[payload[1] == 'getC']">
<invoke object-ref="aHandler" method="getC" doc:name="Get C element"
methodArgumentTypes="java.lang.String" methodArguments="#[payload[2]]"/>
</when>
</choice>
</sub-flow>
</mule>
I get during app-start:
Caused by:
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException:
Line 46 in XML document from URL [file:/(...)/mule-config.xml] is
invalid; nested exception is org.xml.sax.SAXParseException;
lineNumber: 46; columnNumber: 61; cvc-complex-type.2.4.a: Invalid
content was found starting with element 'ws:consumer-config'. One of
(...) is expected.
46:61 points to the last charatcer of: port="BSOAP">.
If possible I would avoid using CXF and try the Web Service Consumer that it is much easier to use:
<tls:context name="tlsContext">
<tls:key-store path="path" keyPassword="pass" password="pass" alias="keyalias" />
</tls:context>
<ws:consumer-config name="Web_Service_Consumerweather" serviceAddress="http://localhost/test" wsdlLocation="Test.wsdl"
service="TestService" port="TestPort">
<ws:security>
<ws:wss-sign tlsContext-ref="tlsContext" />
</ws:security>
</ws:consumer-config>
<flow name="listInventory" doc:name="listInventory">
<http:listener config-ref="HTTP_Listener_Configuration" path="inventory" doc:name="HTTP">
<http:response-builder statusCode="200"/>
</http:listener>
<ws:consumer config-ref="Web_Service_Consumer" operation="ListInventory" doc:name="List Inventory"/>
</flow>
Also note that Mule 3.8 has been replaced by Mule 3.9. The latest release is Mule 4.3 which is not compatible with Mule 3.x and doesn't support CXF.
Documentation: https://docs.mulesoft.com/web-service-consumer-connector/0.3.9/

How to pass a file as SOAP request to Mule SOAP client to consume service

I have a flow which is exposing a webservice :-
<flow name="ServiceFlow" doc:name="ServiceFlow">
<http:inbound-endpoint exchange-pattern="request-response" host="localhost" port="8082" path="mainData" doc:name="HTTP"/>
<cxf:jaxws-service serviceClass="com.test.services.schema.maindata.v1.MainData" doc:name="SOAP"/>
<component class="com.test.services.schema.maindata.v1.Impl.MainDataImpl" doc:name="JavaMain_ServiceImpl"/>
</flow>
This web service have a operation insertDataOperation which takes all the input from SOAP request and insert it in Database...
My SOAP request is as follow :-
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:v1="http://services.test.com/schema/MainData/V1">
<soapenv:Header/>
<soapenv:Body>
<v1:insertDataRequest>
<v1:Id>311</v1:Id>
<v1:Name>ttttt</v1:Name>
<v1:Age>56</v1:Age>
<v1:Designation>eeeee</v1:Designation>
</v1:insertDataRequest>
</soapenv:Body>
</soapenv:Envelope>
Now I have another web service client flow which is consuming this webservice and the flow is :-
<flow name="ClientFlow" doc:name="ClientFlow">
<file:inbound-endpoint responseTimeout="10000" connector-ref="File_Global" doc:name="File" path="E:\backup\test">
<file:filename-regex-filter pattern="SoapRequestInsert.xml" caseSensitive="false"/>
</file:inbound-endpoint>
<file:file-to-string-transformer encoding="UTF-8" mimeType="text/xml" doc:name="File to String"/>
<cxf:jaxws-client doc:name="SOAP" serviceClass="com.test.services.schema.maindata.v1.MainData" operation="insertDataOperation" port="MainDataPort" />
<http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="8082" path="mainData" doc:name="HTTP"/>
</flow>
Now here I am trying to consume the webservice by using a file inbound endpoint and passing the SOAP request in the file SoapRequestInsert.xml .. But the issue is that I don't get any error but the data is not inserted in database.. I checked the log .. where I found it enters the insert method of webservice implementation class but it doesn't get the input value ... Please help ... I have taken the reference from the following :- Consuming a JAX-WS in a Mule ESB flow
But It's not working .. what should I do to make it consume successfully and insert into DB ??? Pls help
Because the file you are posting contains the whole SOAP envelope you can HTTP POST it as is:
<flow name="ClientFlow" doc:name="ClientFlow">
<file:inbound-endpoint responseTimeout="10000" connector-ref="File_Global" doc:name="File" path="E:\backup\test">
<file:filename-regex-filter pattern="SoapRequestInsert.xml" caseSensitive="false"/>
</file:inbound-endpoint>
<http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="8082" path="mainData" doc:name="HTTP"/>
</flow>
Note that you may need to add the SOAP action header, before the http:outbound-endpoint:
<set-property name="SOAPAction"
value="http://services.test.com/schema/MainData/V1/insertDataOperation" />
The final working solution is as David suggested the flow and by setting SOAPAction before outbound endpoint :-
<set-property name="SOAPAction"
value="http://services.test.com/schema/MainData/V1/insertDataOperation" />

Mule ESB - how to pass multi parameter to soap web service in browser

I just have a little experience with Mule ESB 3.5, and I found that most of Mule examples only create SOAP Web Service with one parameter. For example, you can see that in SOAP Web Service Security example.
http://www.mulesoft.org/documentation/display/current/SOAP+Web+Service+Security+Example
So I have one question, acroding to above example, after using CHOICE flow control, how to pass multi parameter to method of SOAP web service.
Some suggests for me is to use object array to pass multi parameter, but I still have no clue at all.
Thanks to David. I just try your suggestion. But I think I should update my question to make it clearly.
Firstly, I create web service
#WebService
public interface Greeter
{
public String greet(String name);
public String welcome( String name1,String name2);
}
Then I have a control flow for web service configuration
<flow name="UnsecureServiceFlow" doc:name="UnsecureServiceFlow">
<http:inbound-endpoint address="http://localhost:63081/services/unsecure" exchange-pattern="request-response" doc:name="HTTP Inbound Endpoint"/>
<cxf:jaxws-service serviceClass="com.mulesoft.mule.example.security.Greeter" doc:name="Unsecure service"/>
<component class="com.mulesoft.mule.example.security.GreeterService" doc:name="Greeter Service" />
</flow>
Next is the sub flow using jax-ws client to call the method of web service
<flow name="SecurityClients" doc:name="SecurityClients">
<http:inbound-endpoint exchange-pattern="request-response" host="localhost" port="63080" path="client" doc:name="HTTP Inbound Endpoint"/>
<set-payload value="#[message.inboundProperties['http.query.params']['name']]" doc:name="Set payload with 'name' query param"/>
<set-variable variableName="clientType" value="#[message.inboundProperties['http.query.params']['clientType']]" doc:name="Set clientType"/>
<choice doc:name="Choice">
<when expression="#[clientType == 'unsecure']">
<flow-ref name="unsecure" doc:name="Invoke unsecure sub-flow"/>
</when>
<when expression="#[clientType == 'usernameToken']">
<flow-ref name="usernameToken" doc:name="Invoke usernameToken sub-flow"/>
</when>
<when expression="#[clientType == 'usernameTokenSigned']">
<flow-ref name="usernameTokenSigned" doc:name="Invoke usernameToken Signed sub-flow"/>
</when>
<when expression="#[clientType == 'usernameTokenEncrypted']">
<flow-ref name="usernameTokenEncrypted" doc:name="Invoke usernameToken Encrypted sub-flow"/>
</when>
<when expression="#[clientType == 'samlToken']">
<flow-ref name="samlToken" doc:name="Invoke samlToken sub-flow"/>
</when>
<when expression="#[clientType == 'samlTokenSigned']">
<flow-ref name="samlTokenSigned" doc:name="Invoke samlToken Signed sub-flow"/>
</when>
<otherwise>
<set-payload value="Client type is not supported" doc:name="Client type is not supported"/>
</otherwise>
</choice>
<set-property propertyName="Content-Type" value="text/plain" doc:name="Set response Content-Type"/>
<catch-exception-strategy doc:name="Catch Exception Strategy">
<set-payload value="There has been an Error processing the request" doc:name="Set Payload"/>
<set-property propertyName="Content-Type" value="text/plain" doc:name="Set response Content-Type"/>
</catch-exception-strategy>
</flow>
<sub-flow name="unsecure" doc:name="unsecure">
<cxf:jaxws-client operation="greet" serviceClass="com.mulesoft.mule.example.security.Greeter" doc:name="Unsecure SOAP client" doc:description="Unsecure SOAP client"/>
<http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="63081" path="services/unsecure" doc:name="Invoke unsecure Web Service"/>
</sub-flow>
It's ok to use that address to invoke greet method, it only has one parameter.
localhost:63080/client?clientType=usernameToken&name=John
However when I change greet method to welcome method, I do not know how to pass more parameter to it or have to change anything , because payload only contains name parameter
Generate the JAX-WS client classes from the remote web service WSDL and use them in a cxf:jaxws-client configuration element.
In your case, you need to set-payload inside each when in order to create the request object that is needed by cxf:jaxws-client.
Suppose that you need to create a org.saml.SamlToken object for the samlToken case, you would do:
<set-payload value="#[st=new org.saml.SamlToken();st.field1=message.inboundProperties.field1; ... ; st]" />
in the when right before flow-ref.
PS. You can use #[message.inboundProperties.clientType] instead of #[message.inboundProperties['http.query.params']['clientType']]

How to handle exception strategy in SOAP Web Service in Mule

I have a question regarding Web Service in Mule 3.3.1 CE. I have a Web Service which exposes three operation and a class that implements these operations. These operation can return a result (positive) or an Exception (AuthExeception, ValidateExeception, etc).
Thanks to SOAP Mule Component when I raise a Java Exception, the framework is able to marshall the java exception in a SOAP Fault, but if I wanted to both return a SOAP Fault to the client and handle the exception with an exception strategy in Mule (i.e sending an email), the Mule behavior it is not how can I expect.
In other word when I raise, for example, an AuthException, the flow control pass to the Exception Strategy defined and I'am not anymore able to send back to the client the SOAP Fault (AuthException).
The question is: How can I both send back the SOAP Response and handle the Exception Strategy?
Here below there is a snippet of mule xml file where the exception strategy is implemented simply with a Logger component:
<flow name="esb_consignmentFlow1" doc:name="esb_consignmentFlow1">
<http:inbound-endpoint exchange-pattern="request-response" host="${conn.host}" port="8081" doc:name="HTTP" path="sgb/consignment" />
<cxf:jaxws-service doc:name="Process SOAP Request" serviceClass="com.suzuki.sales.webservice.ProcessTriggerPortType"/>
<component class="it.aizoon.suzuki.service.implementation.TriggerConsignmentOperationsImplementation" doc:name="triggerConsignmentOperationsImpl"/>
<catch-exception-strategy doc:name="Catch Exception Strategy">
<flow-ref name="ErrorHandling" doc:name="Flow Reference"/>
</catch-exception-strategy>
</flow>
<flow name="ErrorHandling" doc:name="ErrorHandling" >
<logger level="INFO" doc:name="Logger"/>
</flow>
I've read something about Processing Strategy, but I don't know if it's the correct way.
Thank you very much for your help.
Here below there's my flow.xml
<?xml version="1.0" encoding="UTF-8"?>
<mule xmlns:smtp="http://www.mulesoft.org/schema/mule/smtp" xmlns:vm="http://www.mulesoft.org/schema/mule/vm" xmlns:cxf="http://www.mulesoft.org/schema/mule/cxf" xmlns:context="http://www.springframework.org/schema/context"
xmlns:http="http://www.mulesoft.org/schema/mule/http"
xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation" xmlns:spring="http://www.springframework.org/schema/beans" version="CE-3.3.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="
http://www.mulesoft.org/schema/mule/vm http://www.mulesoft.org/schema/mule/vm/current/mule-vm.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.mulesoft.org/schema/mule/cxf http://www.mulesoft.org/schema/mule/cxf/current/mule-cxf.xsd
http://www.mulesoft.org/schema/mule/smtp http://www.mulesoft.org/schema/mule/smtp/current/mule-smtp.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd ">
<spring:beans>
<spring:bean id="consignmentProperty" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- In questo modo riesco a definire un file di property esterno che non è in una locazione hard-coded -->
<spring:property name="ignoreUnresolvablePlaceholders" value="true"/>
<spring:property name="locations">
<spring:list>
<spring:value>classpath:esb_consignment.properties</spring:value>
<spring:value>classpath:connections.properties</spring:value>
<spring:value>classpath:email.properties</spring:value>
<!-- L'ultimo nella lista è il più generico perché sovrascrive le properties -->
</spring:list>
</spring:property>
</spring:bean>
<spring:bean id="outfaultInterceptor" class="it.aizoon.suzuki.service.interceptors.CustomSoapFaultOutInterceptor">
<spring:property name="outputQueue" value="ErrorHandler"/>
</spring:bean>
</spring:beans>
<flow name="TriggerConsignmentInterceptorService" doc:name="TriggerConsignmentInterceptorService">
<http:inbound-endpoint exchange-pattern="request-response" host="${conn.host}" port="${conn.port}" doc:name="HTTP" path="sgb/consignment" />
<cxf:jaxws-service doc:name="Process SOAP Request" serviceClass="com.suzuki.sales.webservice.ProcessTriggerPortType">
<cxf:outFaultInterceptors>
<spring:ref bean="outfaultInterceptor"/>
</cxf:outFaultInterceptors>
</cxf:jaxws-service>
<component class="it.aizoon.suzuki.service.implementation.TriggerConsignmentOperationsImplementation" doc:name="triggerConsignmentOperationsImpl"/>
</flow>
<flow name="ErrorHandler" doc:name="ErrorHandler">
<vm:inbound-endpoint exchange-pattern="one-way" path="ErrorHandler" doc:name="Error Handler"/>
<logger message="PAYLOAD: #[message.payload]" level="INFO" doc:name="Payload"/>
<set-payload value="Tipo di Eccezione: #[message.payload]" doc:name="Set Payload"/>
<smtp:outbound-endpoint host="${smtp.host}"
from="${email.fromAddress}"
to="${email.toAddress}"
subject="${email.subject}"
responseTimeout="10000"
doc:name="Email Notification"/>
<logger message="EMAIL SENT" level="INFO" doc:name="Result"/>
<catch-exception-strategy doc:name="Catch Exception Strategy">
<logger message="ERRORE INVIO EMAIL" level="INFO" doc:name="Logger"/>
</catch-exception-strategy>
</flow>
</mule>
Here below there's the interceptor which handle the SOAP response and the exception strategy
package it.aizoon.suzuki.service.interceptors;
import javax.xml.bind.UnmarshalException;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.mule.DefaultMuleMessage;
import org.mule.api.MuleContext;
import org.mule.api.MuleEvent;
import org.mule.api.MuleException;
import org.mule.api.client.MuleClient;
import org.mule.api.context.MuleContextAware;
import org.mule.module.cxf.CxfConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.suzuki.sales.webservice.AuthenticationFailedException;
import com.suzuki.sales.webservice.ValidationFailedException;
public class CustomSoapFaultOutInterceptor extends AbstractPhaseInterceptor implements MuleContextAware{
private static final Logger logger = LoggerFactory.getLogger(CustomSoapFaultOutInterceptor.class);
private String outputQueue;
private MuleContext context;
public CustomSoapFaultOutInterceptor() {
// TODO Auto-generated constructor stub
super(Phase.MARSHAL);
}
#Override
public void setMuleContext(MuleContext context) {
// TODO Auto-generated method stub
this.context = context;
}
#Override
public void handleMessage(Message message) throws Fault {
// TODO Auto-generated method stub
MuleClient client = context.getClient();
MuleEvent event = (MuleEvent) message.getExchange().get(CxfConstants.MULE_EVENT);
DefaultMuleMessage muleMessage = (DefaultMuleMessage) event.getMessage();
Throwable genericExec = message.getContent(Exception.class).getCause();
Throwable exception = null;
if(genericExec instanceof ValidationFailedException){
exception = (ValidationFailedException) genericExec;
}else if(genericExec instanceof AuthenticationFailedException){
exception = (AuthenticationFailedException) genericExec;
}else if(genericExec instanceof UnmarshalException){
exception = (UnmarshalException) genericExec;
}
try {
muleMessage.setPayload(exception);
client.send("vm://" + getOutputQueue(), muleMessage);
} catch (MuleException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public String getOutputQueue() {
return outputQueue;
}
public void setOutputQueue(String outputQueue) {
this.outputQueue = outputQueue;
}
public MuleContext getContext() {
return context;
}
public void setContext(MuleContext context) {
this.context = context;
}
}
Mule suppresses the exception beyond the exception strategy.
For this scenario where you want to handle the exception as well as send it as reply, a custom transformer is to be provided to prepare the soap fault with the exception and then set it as payload in the exception strategy flow.
<flow name="ErrorHandling" doc:name="ErrorHandling" >
<logger level="INFO" doc:name="Logger"/>
<custom-transformer class="com.example.service.ErrorTransformer"></custom-transformer>
</flow>
And a transformer to prepare the error message.
#Override
public Object transformMessage(MuleMessage message, String outputEncoding)
throws TransformerException {
String exceptionMessage = message.getExceptionPayload().getException().getCause().getMessage() ;
String outputMessage = "<soap:Fault xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"> " +
" <faultcode>soap:Server</faultcode> " +
"<faultstring>" + exceptionMessage + "</faultstring> " +
"</soap:Fault>";
return outputMessage;
}
Note: This is a sample transformer. Customize it for your scenario.

Building a custom interceptor with SOAP Web Service MULE

I'am using Mule Community Edition 3.4.
I have a problem with the UntilSuccessful component. The scenario is now exposed:
I have a flow composed by a UntilSuccessful component in which there's a SOAP component that makes a request to a Web Service. In this flow there is an ExcpetionStrategy, too. The problem that I have is that when an exception occurs inside the UntilSuccessful (i.e in the SOAP component) the ExcpetionStrategy is not able to handle it because it (the Exception thrown) is handled by some mechanism inside the UntilSuccessful component.
Because I need to handle the Exception in the ExcpetionStrategy, I thought to build a custom outbound interceptor (inside the SOAP component) that intercept the SOAP response (an exception if it's thrown) and that is able to throw an Exception in order to trigger the ExcpetionStrategy.
Could anyone help me with this problem? I tried to read the documentation but it is sparse and does not explain very well how to build a custom outbound exception.
What I would to do is to save somewhere the name of Exception thrown (i.e if server thrown a NumberFormatException, I would save its name somewhere in order to use it in the ExceptionStrategy)
Below you can see a snippet of mule configuration file:
<flow name="ProvaClient" doc:name="ProvaClient">
<quartz:inbound-endpoint jobName="TalendJob" repeatInterval="5000" repeatCount="0" responseTimeout="10000" doc:name="Quartz">
<quartz:event-generator-job>
<quartz:payload>error</quartz:payload>
</quartz:event-generator-job>
</quartz:inbound-endpoint>
<object-to-string-transformer doc:name="Object to String"/>
<until-successful objectStore-ref="OS_Bean" maxRetries="2" secondsBetweenRetries="2" doc:name="Until Successful" deadLetterQueue-ref="myQueue">
<processor-chain doc:name="Processor Chain: Wait For Web Service Response">
<processor-chain doc:name="Processor Chain: Web Service">
<cxf:jaxws-client operation="getCode" clientClass="it.aizoon.prova.client.ProvaService" port="ProvaPort" enableMuleSoapHeaders="true" doc:name="SOAP">
</cxf:jaxws-client>
<http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="8081" path="service/prova" method="POST" doc:name="HTTP"/>
</processor-chain>
<logger message="PAYLOAD: #[payload]" level="INFO" doc:name="Logger"/>
</processor-chain>
</until-successful>
<catch-exception-strategy doc:name="Catch Exception Strategy">
<!-- <processor ref="myExceptionHandler_id"/> -->
<logger message="EXCEPTION STRATEGY" level="INFO" doc:name="Logger"/>
</catch-exception-strategy>
</flow>
Here you can see the server which exposes a web service:
<flow name="provaServer" doc:name="provaServer">
<http:inbound-endpoint exchange-pattern="request-response" doc:name="HTTP" host="localhost" path="service/prova" port="8081"/>
<logger message="SERVER" level="INFO" doc:name="Logger"/>
<cxf:jaxws-service serviceClass="it.aizoon.prova.Prova" doc:name="Process SOAP Request" />
<component class="it.aizoon.prova.ProvaImpl" doc:name="Java"/>
</flow>
And here there is the ProvaImpl.java, the implementation of Web Service. How you can see, if the string passed as argument in getCode() function is error, an exception in thrown and I would that it is managed by the exception strategy defined in the client
#WebService(endpointInterface = "it.aizoon.prova.Prova",
serviceName = "Prova")
public class ProvaImpl implements Prova{
#Override
public String getCode(String code) throws NumberFormatException{
// TODO Auto-generated method stub
if(code.equals("error")) throw new NumberFormatException();
String str = "Andato a buon fine!";
return str;
}
}
I would change the approach rather than using an interceptor. If you need to invoke the exception strategy wihout triggering the until-succesful router first, I would move your cxf:jaxws-client etc. to a private flow. To quote Mule in Action 2nd edition on private flows:
This decoupling allows defining processing and error handling
strategies that are local to the private flow.
<flow name="ProvaClient" doc:name="ProvaClient">
...
<until-successful objectStore-ref="OS_Bean"
maxRetries="2" secondsBetweenRetries="2" doc:name="Until Successful"
deadLetterQueue-ref="myQueue">
<processor-chain doc:name="Processor Chain: Wait For Web Service Response">
<processor-chain doc:name="Processor Chain: Web Service">
<flow-ref name="externalCallFlow" />
</processor-chain>
<logger message="PAYLOAD: #[payload]" level="INFO" doc:name="Logger" />
</processor-chain>
</until-successful>
...
</flow>
<flow name="externalCallFlow">
<cxf:jaxws-client operation="getCode"
clientClass="it.aizoon.prova.client.ProvaService" port="ProvaPort"
enableMuleSoapHeaders="true" doc:name="SOAP">
</cxf:jaxws-client>
<http:outbound-endpoint exchange-pattern="request-response"
host="localhost" port="8081" path="service/prova" method="POST"
doc:name="HTTP" />
<catch-exception-strategy doc:name="Catch Exception Strategy">
<!-- Handle exception here locally and return custom exception or error
message for the unil-successful router -->
</catch-exception-strategy>
</flow>
You can then handle exceptions locally and return a custom exception or error
message for the until-successful router to catch using the following attribute: failureExpression="exception-type:java.lang.NumberFormatException"
Here's a dummy example I knocked up to throw a NumberFormatException, log the exception in the exception strategy and retry:
<flow name="test" doc:name="test">
<http:inbound-endpoint address="http://localhost:8081/test"
doc:name="HTTP" />
<until-successful objectStore-ref="OS_Bean"
maxRetries="2" secondsBetweenRetries="2" doc:name="Until Successful">
<processor-chain doc:name="Processor Chain: Wait For Web Service Response">
<processor-chain doc:name="Processor Chain: Web Service">
<flow-ref name="externalCallFlow" doc:name="Flow Reference" />
</processor-chain>
</processor-chain>
</until-successful>
</flow>
<flow name="externalCallFlow" doc:name="externalCallFlow">
<scripting:component>
<scripting:script engine="groovy">
throw new java.lang.NumberFormatException();
</scripting:script>
</scripting:component>
<default-exception-strategy>
<processor-chain>
<logger level="ERROR"
message="NumberFormatException Occurred : #[message.payload.getException().getCause()]" />
<scripting:component>
<scripting:script engine="groovy">
throw message.payload.getException().getCause();
</scripting:script>
</scripting:component>
</processor-chain>
</default-exception-strategy>
</flow>