How to handle exception strategy in SOAP Web Service in Mule - web-services

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.

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/

MUnit testing a flow with collection-aggregator

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!

mule simple web client soap request with wsdl

I would like to make request to web service from mule flow. So far I got WSDL generate classes (wsdl2java), prepared flow (below), and "successfully" send request. Now, problem is, that even thou I have taken base class from generated class and put it in payload it does not generate required soap.
My flow:
<flow name="flow1" doc:name="flow1">
<quartz:inbound-endpoint jobName="testingJob" repeatInterval="10810000" repeatCount="0" startDelay="5000" responseTimeout="10000" doc:name="Quartz">
<quartz:event-generator-job groupName="g1job" jobGroupName="g1job">
<quartz:payload>a</quartz:payload>
</quartz:event-generator-job>
</quartz:inbound-endpoint>
<logger message="Starting quartz for testing purpouses" level="INFO" doc:name="Logger"/>
<custom-transformer class="com.example.GenerateSimpleRoutePublish" doc:name="Java"/>
<cxf:simple-client doc:name="SOAP" serviceClass="com.example.ws.MyWebService" operation="send">
</cxf:simple-client>
<logger message="#[payload]" level="INFO" doc:name="Logger"/>
<http:outbound-endpoint exchange-pattern="request-response" host="localhost" port="9094" path="sync-server-web/services/myws" method="POST" connector-ref="http_internal" doc:name="HTTP"/>
</flow>
GenerateSimpleRoutePublish just put base object into payload and move on.
Result that is sent as soap request:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns1:send xmlns:ns1="http://somenamespace.si/">
<ns1:arg0>
<ns1:MyRequest>
...
But it should look like this:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<ns1:send xmlns:ns1="http://somenamespace.si/">
<ns1:MyRequest>
...
Notice that is not there anymore in desired xml.
Anyone knows how to achieve that? I did googled a lot but just don't find right solution. If more is required then I can provide info.
Thanks!
EDIT
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions name="MyWebService" targetNamespace="http://ws.example.com/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://ws.example.com/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<xs:schema elementFormDefault="unqualified" targetNamespace="http://ws.example.com/"
version="1.0" xmlns:tns="http://ws.example.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="MyRequest" type="tns:MyRequest" />
<xs:element name="send" type="tns:send" />
<xs:element name="sendResponse" type="tns:sendResponse" />
<xs:complexType name="send">
<xs:sequence>
<xs:element minOccurs="0" ref="tns:MyRequest" />
</xs:sequence>
</xs:complexType>
<xs:complexType final="extension restriction" name="MyRequest">
<xs:complexContent>
...
Transformer class GenerateSimpleRoutePublish.java:
#Override
public MuleEvent process(MuleEvent event) throws MuleException {
event.setMessage(transformMessage(event.getMessage(), event.getEncoding()));
// TODO Auto-generated method stub
return event;
}
public MuleMessage transformMessage(MuleMessage message, String outputEncoding) throws TransformerException {
MyRequest myRequest = new MyRequest();
/**
* some filling of testing data that is formatted correctly when soap is sent.
*/
Send send = new Send();
send.setMSyequest(myRequest);//the only setter method available for Send
message.setPayload(send);
return message;
}
Send.java methods and xml definition:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "send", propOrder = {"myRequest"})
public MyRequest getMyRequest()
public void setMyRequest(MyRequest value)
Instead of using a custom-transformer you could use Groovy script to send SOAP request to webservice client just as Java pojo class... Please check the reference :- https://m-square.com.au/consuming-net-wcf-soap-web-services-from-mule/

java.io.IOException: Error writing request body to server

I am developing a web service to allow uploading from client to server. I was able to get wsdl file at http://localhost:9999/UploadWebservice?wsdl. Here is its content
<?xml version="1.0" encoding="UTF-8"?>
<!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.6 in JDK 6. -->
<!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI 2.1.6 in JDK 6. -->
<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://ws/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace="http://ws/" name="ImageServerImplService">
<types></types>
<message name="upload">
<part name="arg0" type="xsd:string"></part>
<part name="arg1" type="xsd:base64Binary"></part>
</message>
<message name="uploadResponse">
<part name="return" type="xsd:string"></part>
</message>
<portType name="ImageServer">
<operation name="upload" parameterOrder="arg0 arg1">
<input message="tns:upload"></input>
<output message="tns:uploadResponse"></output>
</operation>
</portType>
<binding name="ImageServerImplPortBinding" type="tns:ImageServer">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"></soap:binding>
<operation name="upload">
<soap:operation soapAction=""></soap:operation>
<input>
<soap:body use="literal" namespace="http://ws/"></soap:body>
</input>
<output>
<soap:body use="literal" namespace="http://ws/"></soap:body>
</output>
</operation>
</binding>
<service name="ImageServerImplService">
<port name="ImageServerImplPort" binding="tns:ImageServerImplPortBinding">
<soap:address location="http://localhost:9999/UploadWebservice"></soap:address>
</port>
</service>
</definitions>
Now I am writing a client to consume the service. Here is the client
package ws;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Map;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.soap.SOAPBinding;
import com.sun.xml.ws.developer.JAXWSProperties;
public class ClientTaiwan {
public static void main(String[] args) {
ImageServerImplService service = new ImageServerImplService();
ImageServer delegate = service.getImageServerImplPort();
// Enable MTOM
BindingProvider bp = (BindingProvider) delegate;
SOAPBinding binding = (SOAPBinding) bp.getBinding();
binding.setMTOMEnabled(true);
// Set chunk size
Map<String, Object> ctxt =
((BindingProvider)delegate).getRequestContext();
ctxt.put(JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE, 8192);
byte[] b;
try {
RandomAccessFile f = new RandomAccessFile("c:\\100MB.zip", "r");
b = new byte[(int) f.length()];
f.read(b);
f.close();
delegate.upload("c://Upload//100MB.zip", b);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Done!");
}
}
I receive the following stack trace
Exception in thread "main" javax.xml.ws.WebServiceException: java.io.IOException: Error writing request body to server
at com.sun.xml.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:225)
at com.sun.xml.ws.transport.http.client.HttpTransportPipe.processRequest(HttpTransportPipe.java:136)
at com.sun.xml.ws.transport.DeferredTransportPipe.processRequest(DeferredTransportPipe.java:110)
at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:1063)
at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:979)
at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:950)
at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:825)
at com.sun.xml.ws.client.Stub.process(Stub.java:443)
at com.sun.xml.ws.client.sei.SEIStub.doProcess(SEIStub.java:174)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:119)
at com.sun.xml.ws.client.sei.SyncMethodHandler.invoke(SyncMethodHandler.java:102)
at com.sun.xml.ws.client.sei.SEIStub.invoke(SEIStub.java:154)
at com.sun.proxy.$Proxy33.upload(Unknown Source)
at ws.ClientTaiwan.main(ClientTaiwan.java:47)
Caused by: java.io.IOException: Error writing request body to server
at sun.net.www.protocol.http.HttpURLConnection$StreamingOutputStream.checkError(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection$StreamingOutputStream.write(Unknown Source)
at com.sun.xml.ws.transport.http.client.HttpClientTransport$WSChunkedOuputStream.write(HttpClientTransport.java:365)
at javax.activation.DataHandler.writeTo(Unknown Source)
at com.sun.xml.ws.encoding.MtomCodec$ByteArrayBuffer.write(MtomCodec.java:229)
at com.sun.xml.ws.encoding.MtomCodec.encode(MtomCodec.java:185)
at com.sun.xml.ws.encoding.SOAPBindingCodec.encode(SOAPBindingCodec.java:242)
at com.sun.xml.ws.transport.http.client.HttpTransportPipe.process(HttpTransportPipe.java:214)
The line that causes the problem is delegate.upload("c://Upload//100MB.zip", b);
Why does a published service is still not visible for its client? How can I fix it?
If you have any concern about my coding style please let me know. Things are a little out of my control right now.
Best regards

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>