Showing posts with label Mule. Show all posts
Showing posts with label Mule. Show all posts

Monday, March 25, 2013

Mule 3.3 Start and Stop Flows Programmatically - A Retry Strategy for your Messages

Mule 3.3 brings with it some updates to handle on demand execution of flows. This blog discusses the ability to use a combination of Mule and Active MQ (or a messaging tool of your choice) to provide reliability and fault tolerance by using retry techniques.
The retry is on demand and programmatically controlled. A retry flow programmatically starts and stops a flow.

Your requirement is that: When a call to an external service fails or results in a business exception, you need to be able to store the original request in an error queue and to be able to retry those requests on demand.

Start and Stop Flows Programmatically On-demand


You break your implementation into four separate main flows. Flows three and four are the meat of this blog.
The first flow reads an incoming message from a JMS queue and place it in an intermediate queue.
The second flow transactionally processes the intermediate queue and write to a *.retry queue where appropriate.
The third flow is a trigger for the retry process which listens to a specific message on a queue to programmatically start and stop the fourth flow.
The fourth flow wakes up when told to, sweeps the *.retry queue once for all retry messages to process them and is shut down until the next time it is needed.

To get to the point, I will document Flows 3 and 4 first. If you are insterested, you can scroll down to view the first two flows to understand how the message got into the retry queue in the first place.

Flow 3 The flow that retries the request. Initial state on the flow is "stopped".


   <flow name="retry.masterupdate.request.to.client.jms.flow" initialState="stopped" processingStrategy="synchronous">  
     <jms:inbound-endpoint exchange-pattern="request-response" queue="my.intermediate.queue.masterupdate.retry" mimeType="text/plain" connector-ref="myRetryInConnector" doc:name="JMS"/>  
     <object-to-string-transformer doc:name="Object to String"/>  
     <set-variable variableName="initialMessageReceived" value="#[payload:]" doc:name="Variable"/>  
 <!-- PLACE into the original queue to be reprocessed. See Flow 1 -->  
     <jms:outbound-endpoint queue="my.masterupdate" connector-ref="myOutboundConnector" doc:name="JMS"/>  
     <choice-exception-strategy doc:name="retryEndpoint.exception.strategy">  
       <catch-exception-strategy when="true" doc:name="Catch Exception Strategy">  
         <choice doc:name="Choice">  
           <when expression="#[flowVars['initialMessageReceived'] != null]">  
             <set-payload value="#[flowVars['initialMessageReceived']]" doc:name="Set Payload"/>  
           </when>  
         </choice>  
 <!-- PLACE back into the retry queue to be reprocessed the next time around. -->  
         <jms:outbound-endpoint queue="my.intermediate.queue.masterupdate.retry" connector-ref="myOutboundConnector" doc:name="JMS"/>  
       </catch-exception-strategy>  
     </choice-exception-strategy>  
   </flow>  

Flow 4 The flow that initiates the retry based on a triiger, which is a message in a specific queue.

   <flow name="initiate.retry" processingStrategy="synchronous">  
     <jms:inbound-endpoint exchange-pattern="request-response" queue="masterupdate.retry.trigger" mimeType="text/plain" connector-ref="myThrottledInConnector" doc:name="JMS"/>  
     <object-to-string-transformer doc:name="Object to String"/>  
     <choice doc:name="Choice">  
 <!-- THIS is the trigger queue with na specific message that controls which data to retry. -->  
       <when expression="#[groovy: payload.toUpperCase().startsWith('MASTER_UPDATE_RETRY')]">  
         <processor-chain>  
           <scripting:component doc:name="Start flow to retry">  
             <scripting:script engine="Groovy">  
               <scripting:text><![CDATA[((org.mule.construct.Flow)(muleContext.getRegistry().lookupFlowConstruct('retry.masterupdate.request.to.client.jms.flow'))).start();]]></scripting:text>  
             </scripting:script>  
           </scripting:component>  
 <!-- STOP retry flow after completion. -->  
           <flow-ref name="stop.retry.flows" doc:name="Flow Reference"/>  
         </processor-chain>  
       </when>  
     </choice>  
   </flow>  
   <sub-flow name="stop.retry.flows" doc:name="stop.retry.flows">  
     <scripting:component doc:name="Stop Flow">  
       <scripting:script engine="Groovy">  
         <scripting:text><![CDATA[((org.mule.construct.Flow)(muleContext.getRegistry().lookupFlowConstruct('retry.masterupdate.request.to.client.jms.flow'))).stop();]]></scripting:text>  
       </scripting:script>  
     </scripting:component>  
   </sub-flow>  


Flow 1 which you see below receives the request and places it in an intermediate JMS queue after some basic validations. If there is an error, the request goes to an error queue.


   <flow name="my.jms.flow" processingStrategy="synchronous">  
     <jms:inbound-endpoint exchange-pattern="request-response" queue="my.masterupdate" mimeType="text/xml" connector-ref="myThrottledInConnector" doc:name="JMS">  
     </jms:inbound-endpoint>  
     <set-variable variableName="initialMessageReceived" value="#[payload:]" doc:name="Variable"/>  
     <logger message="Incoming message from Master - #[payload:]" level="INFO" doc:name="Logger"/>  
     <processor-chain>  
       <set-payload value="#[flowVars['initialMessageReceived']]" doc:name="Set Payload"/>  
       <object-to-string-transformer />  
       <custom-transformer class="com.esb.transformers.MyCustomTransformer" doc:name="Java"/>  
       <jms:outbound-endpoint exchange-pattern="one-way" queue="my.intermediate.queue.masterupdate" connector-ref="myOutboundConnector" doc:name="JMS">  
       </jms:outbound-endpoint>  
     </processor-chain>  
     <choice-exception-strategy doc:name="my.jms.exception.strategy">  
       <catch-exception-strategy when="#[exception.causedBy(com.esb.exceptions.ValidationException)]" doc:name="Catch Exception Strategy">  
         <object-to-string-transformer doc:name="Object to String"/>  
         <logger message="My Validation Exception." level="ERROR" doc:name="Logger"/>  
         <logger level="ERROR" doc:name="Logger"/>  
         <choice>  
           <when expression="#[flowVars['initialMessageReceived'] != null]">  
             <set-payload value="#[flowVars['initialMessageReceived']]" doc:name="Set Payload"/>  
           </when>  
         </choice>  
         <message-properties-transformer scope="outbound" doc:name="Message Properties">  
           <add-message-property key="BIZ_ERR" value="My Validation Exception."/>  
         </message-properties-transformer>  
         <jms:outbound-endpoint exchange-pattern="one-way" queue="my.masterupdate.err" connector-ref="myOutboundConnector" doc:name="JMS">  
         </jms:outbound-endpoint>  
       </catch-exception-strategy>  
       <catch-exception-strategy when="true" doc:name="Catch Exception Strategy">  
         <choice>  
           <when expression="#[flowVars['initialMessageReceived'] != null]">  
             <set-payload value="#[flowVars['initialMessageReceived']]" doc:name="Set Payload"/>  
           </when>  
         </choice>  
         <logger message="Unhandled exception while calling. #[exception.message] for Payload: #[payload:]" level="ERROR" doc:name="Logger"/>  
         <logger level="ERROR" doc:name="Logger"/>  
         <message-properties-transformer scope="outbound" doc:name="Message Properties">  
           <add-message-property key="BIZ_ERR" value="#[exception.message]" />  
         </message-properties-transformer>  
         <jms:outbound-endpoint exchange-pattern="one-way" queue="my.masterupdate.err" connector-ref="myOutboundConnector" doc:name="JMS">  
         </jms:outbound-endpoint>  
       </catch-exception-strategy>  
     </choice-exception-strategy>  
   </flow>  

Flow 2 which transactionally reads the intermediate queue and processes the mesage. Upon errors, it writes message to a *.retry exception.


   <flow name="my.intermediate.queue.masterupdate" processingStrategy="synchronous" >  
     <jms:inbound-endpoint exchange-pattern="request-response" queue="my.intermediate.queue.masterupdate" mimeType="text/xml" connector-ref="myThrottledInConnectorInfiniteRetry" doc:name="JMS">  
       <ee:multi-transaction action="ALWAYS_BEGIN"/>  
     </jms:inbound-endpoint>  
     <set-variable variableName="initialMessageReceivedCP" value="#[payload:]" doc:name="Variable"/>  
     <message-properties-transformer scope="outbound" doc:name="Message Properties">  
       <add-message-property key="Content-Type" value="application/xml; charset=UTF8"/>  
     </message-properties-transformer>  
     <http:outbound-endpoint exchange-pattern="request-response" address="http://my.service.call" mimeType="application/xml" contentType="application/xml" responseTimeout="120000" doc:name="HTTP"/>  
     <object-to-string-transformer />  
     <logger message="RETURN message - #[payload:]" level="INFO" doc:name="Logger"/>  
     <choice>  
       <when expression="#[groovy: message.getInboundProperty('http.status') != null &amp;&amp; message.getInboundProperty('http.status').startsWith('400')]">  
         <logger level="DEBUG" message="Returned http status code 400."/>  
 <!-- THROW a business exception here. -->  
 <!-- THIS is a business exception. Can be retried later. -->  
       </when>  
       <when expression="#[groovy: message.getInboundProperty('http.status') != null &amp;&amp; message.getInboundProperty('http.status').startsWith('2')]">  
         <logger level="TRACE" message=" "/>  
 <!-- THIS is a SUCCESS. -->  
       </when>  
       <otherwise>  
 <!-- THIS is a Transaction ROLLBACK with a retry due to network issues with reaching the end point. This is not the same as retrying a business exception.  
 We do not want to use this feature for business exception retries because the same message will be rolled back and retried instead of moving on to the next message.  
 We believe that the business exception is perhaps a data clean up issue where the message can be retried later.  
 -->  
         <logger message="Unable to reach endpoint...HTTP Status #[groovy: message.getInboundProperty('http.status')]" level="DEBUG" doc:name="Logger"/>  
         <scripting:component doc:name="Throw Exception">  
           <scripting:script engine="Groovy">  
             <scripting:text><![CDATA[throw new java.net.ConnectException("Unable to reach end point. Retrying...");]]></scripting:text>  
           </scripting:script>  
         </scripting:component>  
       </otherwise>        
     </choice>  
     <choice-exception-strategy doc:name="my.exception.strategy">  
       <rollback-exception-strategy when="#[exception.causedBy(org.mule.api.routing.filter.FilterUnacceptedException) || exception.causedBy(java.net.UnknownHostException) || exception.causedBy(java.net.ConnectException) || exception.causedBy(java.net.SocketTimeoutException) || exception.causedBy(java.net.SocketException)]" doc:name="Catch Exception Strategy">  
         <logger message="Network/Connection exception while calling endpoint. Payload: #[payload:]. RETRYING..." level="ERROR" doc:name="Logger"/>  
         <set-payload value="#[flowVars['initialMessageReceivedCP']]" doc:name="Set Payload"/>  
       </rollback-exception-strategy>  
       <catch-exception-strategy when="#[exception.causedBy(com.esb.MyBusinessException)]" doc:name="Catch Exception Strategy">  
         <object-to-string-transformer doc:name="Object to String"/>  
         <logger message="My business exception." level="ERROR" doc:name="Logger"/>  
         <set-payload value="#[flowVars['initialMessageReceivedCP']]" doc:name="Set Payload"/>  
         <message-properties-transformer scope="outbound" doc:name="Message Properties">  
           <add-message-property key="BIZ_ERR" value="My business exception text"/>  
         </message-properties-transformer>  
 <!-- THIS is a business exception. Can be retried later. -->  
         <jms:outbound-endpoint exchange-pattern="one-way" queue="my.intermediate.queue.masterupdate.retry" connector-ref="myOutboundConnector" doc:name="JMS">  
         </jms:outbound-endpoint>  
       </catch-exception-strategy>  
       <catch-exception-strategy when="true" doc:name="Catch Exception Strategy">  
         <choice>  
           <when expression="#[flowVars['initialMessageReceivedCP'] != null]">  
             <set-payload value="#[flowVars['initialMessageReceivedCP']]" doc:name="Set Payload"/>  
           </when>  
         </choice>  
 <!-- THIS is an UNKNOWN exception. Can be retried later. -->  
         <logger message="Unhandled exception while calling end point. Payload: #[payload:]" level="ERROR"doc:name="Logger"/>  
         <jms:outbound-endpoint exchange-pattern="one-way" queue="my.intermediate.queue.masterupdate.retry" connector-ref="myOutboundConnector" doc:name="JMS">  
         </jms:outbound-endpoint>  
       </catch-exception-strategy>  
     </choice-exception-strategy>  
   </flow>  

Wednesday, October 10, 2012

Mule ESB Reliability with Exception Strategy

Mule 3.x brings with it some nice exception strategy handling features that you can use to provide reliable message delivery and retrying service calls.

Your requirement is that: You need to try calling a web service from your Mule Flow.

Case 1 If service is unavailable (timeout, 500 etc), retry forever (or a specified number of times and if all retries fail, write the original payload to a JMS message queue).

Case 2 If service is available, the call was successful BUT the web service response indicates that processing failed but operation could be tried again (a recoverable exception on the web service end), then retry the call.

Case 3 If service is available, the call was successful BUT the web service response indicates that the processing  failed (non-recoverable exception on the web service end - meaning no matter how many times you retry, processing will fail), then skip out of retries but still write the original payload to a JMS message queue.

Using JMS, JMS Transactions and Exception Strategies

You break your implementation into two separate main flows.
Flow 1 which you see below receives the request and places it in a JMS queue, transactionally.

1:    <flow name="myServiceV2" doc:name="myServiceV2">  
2:      <http:inbound-endpoint exchange-pattern="one-way" host="localhost" port="63012" path="my/service/v2/operation" doc:name="myService"/>  
3:      <object-to-string-transformer doc:name="Object to String"/>  
4:      <message-properties-transformer doc:name="Message Properties">  
5:        <add-message-property key="srcPayload" value="#[payload:]"/>  
6:      </message-properties-transformer>  
7:      <set-session-variable value="#[payload:]" variableName="srcPayload" doc:name="Session Variable" />  
8:      <logger message="Request SRC message is #[payload:]" level="INFO" doc:name="Logger"/>  
9:      <vm:outbound-endpoint exchange-pattern="one-way" path="unprocessedOriginalMessageStoreV2" responseTimeout="20000" mimeType="text/plain" doc:name="Dispatch to audit"/>  
10:      </flow>  
11:        
12:    <flow name="unprocessedOriginalMessageStoreV2" doc:name="unprocessedOriginalMessageStoreV2">  
13:      <vm:inbound-endpoint exchange-pattern="one-way" path="unprocessedOriginalMessageStoreV2" doc:name="VM">  
14:      </vm:inbound-endpoint>  
15:        <object-to-string-transformer />  
16:      <expression-transformer doc:name="Expression">  
17:        <return-argument evaluator="header" expression="session:srcPayload"/>  
18:      </expression-transformer>  
19:      <jms:outbound-endpoint queue="esb.unprocessed.original.message" connector-ref="jmsPalEsbXAConnector" doc:name="JMS">  
20:          <jms:transaction action="ALWAYS_BEGIN"/>  
21:      </jms:outbound-endpoint>  
22:    </flow>  
23:    


Next, write another flow that reads from the message queue and processes the message. Note the transactions, the exception handling strategy and the rollback of the message.

1:    <flow name="processMyRequests" doc:name="processMyRequests">  
2:      <jms:inbound-endpoint queue="esb.unprocessed.original.message" connector-ref="jmsPalEsbXAConnector" doc:name=" processor">  
3:        <xa-transaction action="ALWAYS_BEGIN" timeout="60000"/>  
4:      </jms:inbound-endpoint>  
5:      <vm:outbound-endpoint exchange-pattern="one-way" path="jmsTask" responseTimeout="20000" mimeType="text/plain" doc:name="Dispatch to processing">  
6:        <xa-transaction action="ALWAYS_JOIN" timeout="60000"/>  
7:      </vm:outbound-endpoint>  
8:    </flow>  
9:      
10:    <flow name="processMyRequestJMS" processingStrategy="synchronous">  
11:      <vm:inbound-endpoint exchange-pattern="one-way" path="jmsTask" doc:name="VM">  
12:        <xa-transaction action="BEGIN_OR_JOIN" timeout="60000"/>  
13:      </vm:inbound-endpoint>  
14:      <set-session-variable value="#[payload:]" variableName="srcPayload" doc:name="Session Variable" />  
15:      <cxf:jaxws-service serviceClass="com.mycompany.operations.v1.IMemberManager" doc:name="SOAP Service"/>  
16:      <set-session-variable value="N" variableName="skipRetry" doc:name="Variable" />  
17:      <processor-chain doc:name="Processor Chain">  
18:        <message-properties-transformer scope="invocation" doc:name="Message Properties">  
19:          <add-message-property key="MessageHeader" value="#[groovy:payload.find{it instanceof javax.xml.ws.Holder}.value]"/>  
20:          <add-message-property key="MyRequest" value="#[groovy:payload.find{it instanceof com.mycompany.member.MyRequest}]"/>  
21:          <delete-message-property key=".*"/>  
22:        </message-properties-transformer>  
23:        <set-session-variable variableName="skipRetry" value="N" doc:name="Variable"/>  
24:        <choice doc:name="Choice">  
25:          <when expression="#[groovy:validationService.isValidValue(payload[1].data.value)]">  
26:            <processor-chain>  
27:              <custom-transformer class="com.mycompany.transformer.MyRequestTransformer" doc:name="Java"/>  
28:              <choice doc:name="Choice">  
29:                <when expression="groovy:message.getInvocationProperty('requestValid') == 'Y'">  
30:                  <processor-chain>  
31:  <!-- LEARN: Call the client webservice -->  
32:                    <flow-ref name="webServiceClientV2" doc:name="Invoke WebService"/>  
33:  <!-- LEARN: This is the custom transformer that reads the response on successful call of client and sets the skip property -->  
34:  <!-- LEARN: 'skip' is set to 'D' (for Done) if call was successfull; -->  
35:  <!-- LEARN: 'skip' is set to 'Y' if call was successfull but response indicated exception was non-recoverable; (so no retries but write to original msg to JMS Queue)-->  
36:  <!-- LEARN: 'skip' is set to 'N' if call was successfull but response indicated exception was recoverable; (so retry); -->  
37:                    <custom-transformer class="com.mycompany.transformer.MyResponseTransformer" doc:name="Java"/>  
38:                                      <choice>  
39:                                          <when evaluator="groovy" expression="message.getInvocationProperty('skip') != 'D'">  
40:  <!-- LEARN: When 'skip' is NOT 'D' then set the value of skip to a session or flow variable and throw a dummy exception-->   
41:                                              <set-session-variable value="#[groovy:message.getInvocationProperty('skip')]" variableName="skipRetry" doc:name="Variable" />  
42:                                              <scripting:transformer doc:name="Create Dummy Exception">  
43:                                            <scripting:script engine="groovy">  
44:                                              <scripting:text><![CDATA[throw new java.lang.Exception('IGNORE THIS EXCEPTION');]]></scripting:text>  
45:                                            </scripting:script>  
46:                                          </scripting:transformer>  
47:                            </when>  
48:                                      </choice>  
49:                  </processor-chain>  
50:                </when>  
51:                <otherwise>  
52:                  <processor-chain>  
53:                    <flow-ref name="errorHandlerBadInput" doc:name="errorHandlerBadInput"/>  
54:                  </processor-chain>  
55:                </otherwise>  
56:              </choice>  
57:            </processor-chain>  
58:          </when>  
59:          <otherwise>  
60:            <processor-chain>  
61:              <flow-ref name="defaultErrorHandlerV2" doc:name="defaultErrorHandlerV2"/>  
62:            </processor-chain>  
63:          </otherwise>  
64:        </choice>  
65:      </processor-chain>  
66:      <choice-exception-strategy doc:name="Choice Exception Strategy">  
67:  <!-- LEARN: When dummy exception thrown, check the flow or session variable 'skipRetry' ; If do not skip, then rollback JMS transaction to re queue the JMS message -->  
68:         <rollback-exception-strategy when="#[groovy:message.getProperty('skipRetry', org.mule.api.transport.PropertyScope.SESSION) == 'N' || exception.causedBy(java.net.UnknownHostException) || exception.causedBy(java.net.ConnectException)]" maxRedeliveryAttempts="2">  
69:            <on-redelivery-attempts-exceeded>  
70:              <logger message="STRATEGY ROLLBACK WITH LIMITED RETRIES #[payload:]" level="INFO" doc:name="Logger"/>  
71:              <flow-ref name="jmsAuditDirectWriteV2" doc:name="Log Unprocessed Message to Original Queue"/>  
72:            </on-redelivery-attempts-exceeded>  
73:        </rollback-exception-strategy>  
74:    
75:  <!-- LEARN: When dummy exception thrown, check the flow or session variable 'skipRetry' ; If do not skip, then place the original message back on main processing queue for reprocessing -->  
76:  <!-- LEARN: If you uncomment out this forever strategy AND comment out the above ROLLBACK strategy, you get retry forever -->  
77:  <!--      <catch-exception-strategy when="#[groovy:message.getProperty('skipRetry', org.mule.api.transport.PropertyScope.SESSION) == 'N' || exception.causedBy(java.net.UnknownHostException) || exception.causedBy(java.net.ConnectException)]">  
78:          <logger message="STRATEGY RETRY FOREVER #[payload:]" level="INFO" doc:name="Logger"/>  
79:              <flow-ref name="unprocessedOriginalMessageStoreV2" doc:name="Log Unprocessed Message to Original Queue"/>  
80:        </catch-exception-strategy>  
81:  -->  
82:        <catch-exception-strategy when="#[groovy:message.getProperty('skipRetry', org.mule.api.transport.PropertyScope.SESSION) == 'Y']" doc:name="Write to Error Queue">  
83:          <logger message="STRATEGY WRITE TO ERROR QUEUE; SKIP RETRY; #[payload:]" level="INFO" doc:name="Logger"/>  
84:          <flow-ref name="jmsAuditDirectWriteV2" doc:name="Log Unprocessed Message to Error Queue"/>  
85:        </catch-exception-strategy>  
86:        <catch-exception-strategy doc:name="Write to Error Queue">  
87:          <logger message="STRATEGY DEFAULT WRITE TO ERROR QUEUE; #[payload:]" level="INFO" doc:name="Logger"/>  
88:          <flow-ref name="jmsAuditDirectWriteV2" doc:name="Log Unprocessed Message to Error Queue"/>  
89:        </catch-exception-strategy>  
90:      </choice-exception-strategy>  
91:    </flow>  
92:    <sub-flow name="webServiceClientV2" doc:name="webServiceClientV2">  
93:      <logger message="client 0 : #[payload:]" level="ERROR" doc:name="Log Error"/>  
94:      <cxf:jaxws-client operation="MyRequest" serviceClass="com.mycompany.operations.IMemberManager" doc:name="Webservice Client">  
95:        <cxf:outInterceptors/>  
96:      </cxf:jaxws-client>  
97:      <http:outbound-endpoint exchange-pattern="request-response" address="http://..." doc:name="request"/>  
98:    </sub-flow>  
99:    <sub-flow name="jmsAuditDirectWriteV2" doc:name="jmsAuditDirectWriteV2">  
100:      <expression-transformer doc:name="Expression">  
101:        <return-argument evaluator="header" expression="session:srcPayload"/>  
102:      </expression-transformer>  
103:      <jms:outbound-endpoint queue="esb.unprocessed.request.err" connector-ref="jmsPalEsbXAConnector" doc:name="JMS"/>  
104:    </sub-flow>  
105:    <flow name="unprocessedOriginalMessageStoreV2" doc:name="unprocessedOriginalMessageStoreV2">  
106:      <expression-transformer doc:name="Expression">  
107:        <return-argument evaluator="header" expression="session:srcPayload"/>  
108:      </expression-transformer>  
109:        <object-to-string-transformer />  
110:      <logger message="JMS message is #[payload:]" level="INFO" doc:name="Logger"/>  
111:      <jms:outbound-endpoint queue="esb.unprocessed.original.message" connector-ref="jmsPalEsbXAConnector" doc:name="JMS">  
112:      </jms:outbound-endpoint>  
113:    </flow>  
114:  </mule>  
115:    


The Custom Response Transformer

Within com.mycompany.transformer.MyResponseTransformer, this happens:
// jsr is the unmarshalled response returned from the web service I called.
// It carries a status code. A non-zero value is a failure in processing.
// The resendFlag indicates whether it was a recoverable exception (resend=true)
// or a non-recoverable exception (resend=false)

 
1:  returnValue.setMessage(jsr.getMessage());  
2:  returnValue.setStatus(jsr.getStatus());  
3:  if (jsr.getStatus().codeValue() == 0) {  
4:      logger.info("Client SUCCESSFULLY PROCESSED with status code + message : " + returnValue.getStatus() + ";;;;" +returnValue.getMessage());  
5:      message.setInvocationProperty("skip", "D");  
6:  } else {  
7:    if (!jsr.isResend()) {  
8:          logger.info("Client FAILED PROCESSING with status code + message : " + returnValue.getStatus() + ";;;;" +returnValue.getMessage());  
9:          message.setInvocationProperty("skip", "Y"); //do not resend; store;  
10:  } else {  
11:          logger.info("Client requested a RESEND of the request with status code + message : " + returnValue.getStatus() + ";;;;" +returnValue.getMessage());  
12:          message.setInvocationProperty("skip", "N");  
13:          }  
14:      }  
15:  }   




Mule 3.3.1 Using Until-Successful To Skip Retries

Introduction

Mule 3.x brings with it a new feature called Until-Successful to loop through some flow tasks where you can specify the number of retries, time between retries and an action to be performed upon retry exhaustion. It also allows you specify a failure condition in addition to the default failure conditions (network issues etc).

However, it is pretty rigid in that the configuration does not let you break out of retries on demand. In other words, your code inside the Until-successful flow succeeded BUT based on a response code (from your web service call) or any other condition, you just need to stop retrying and go straight to the failure condition or simulate the failure condition.

Your requirement is that: You need to try calling a web service from your Mule Flow.

Case 1 If service is unavailable (timeout, 500 etc), retry for a specified number of times and if all retries fail, write the original payload to a JMS message queue.

Case 2 If service is available, the call was successful BUT the web service response indicates that processing failed but operation could be tried again (a recoverable exception on the web service end), then retry the call.

Case 3 If service is available, the call was successful BUT the web service response indicates that the processing  failed (non-recoverable exception on the web service end - meaning no matter how many times you retry, processing will fail), then skip out of retries but still write the original payload to a JMS message queue.

The Most Obvious Solution (does not work)

If you dig deep into mule code, you will see the way mule decides the number of times to retry is by comparing the current retry number against the maxRetries configuration value. If current retry number exceeds the maxRetries, then the assumption is that it has tried to call the client web service 'x' number of times but has failed and so, will now go ahead and execute the failure flow.

Also, if you print out the Mule message, you will see that the current retry number data is in an invocation scoped variable called: process.attempt.count. You would be forgiven if you think, in Case 3 above, you can update this to a value higher than maxRetries and the Until-Successful code would see that and exit the loop and execute the failure condition. But alas no! Before entering the loop, Mule stores the original message in an in memory storage structure and reads that to try it every time. Thus, any invocation property you modify within the until-successful code will not be available because the failed message is discarded and the original message is read from memory for each retry.

The Not So Obvious Solution (and it works!!):

As you can see, first the message received is stored in a session variable for later use. Follow the <!-- LEARN:... tag for notes on what's happening in the flow.
The key takeaway is that you push all processing to a sub-flow of the Until-Successful loop. Within there, you handle your recoverable and non-recoverable exception conditions.
If you wish to simulate failure in any case, you can throw an exception using Groovy Script transformer which tells the Until-Successful in the main loop that processing failed and the message has to be retried.
In the example below, in case of an non-recoverable exception, the JMS message is written and a success is simulated in the sub-flow so that Until-Successful gets out of the loop assuming successful execution. For the recoverable exception, a failure is indicated by settng a message invocation property that is part of the failure expression check.

1:  <vm:endpoint exchange-pattern="one-way" path="unprocessedMessageStore" name="unprocessedMessageStore" doc:name="VM"/>  
2:  <flow name="myService" doc:name="myService">  
3:       <http:inbound-endpoint exchange-pattern="one-way" host="localhost" port="63012" path="my/service/path/v1/operation" doc:name="myService"/>  
4:       <object-to-string-transformer doc:name="Object to String"/>  
5:       <message-properties-transformer doc:name="Message Properties">  
6:              <add-message-property key="srcPayload" value="#[payload:]"/>  
7:       </message-properties-transformer>  
8:       <set-session-variable value="#[payload:]" variableName="srcPayload" doc:name="Session Variable" />  
9:  <!-- LEARN: This is a Web Service hosted from within Mule which receives the request from a client -->  
10:       <cxf:jaxws-service serviceClass="com.mycomp.member.IMemberManager" doc:name="SOAP Service"/>  
11:       <processor ref="mdcLogging"></processor>  
12:           <processor-chain doc:name="Processor Chain">  
13:              <message-properties-transformer scope="invocation" doc:name="Message Properties">  
14:              <add-message-property key="MessageHeader" value="#[groovy:payload.find{it instanceof javax.xml.ws.Holder}.value]"/>  
15:                  <add-message-property key="MyRequest" value="#[groovy:payload.find{it instanceof com.mycompany.MyRequest}]"/>  
16:                  <delete-message-property key=".*"/>  
17:              </message-properties-transformer>  
18:              <choice doc:name="Choice">  
19:                  <when expression="#[groovy:validationService.isValidValue(payload[1].data.value)]">  
20:                  <processor-chain>  
21:  <!-- LEARN: This is a custom request transformer -->  
22:            <custom-transformer class="com.mycompany.transformer.MyRequestTransformer" doc:name="Java"/>  
23:                      <choice>  
24:  <!-- LEARN: This verifies that the request is valid -->  
25:              <when evaluator="groovy" expression="message.getInvocationProperty('requestValid') == 'Y'">  
26:  <!-- LEARN: This is the until successful with a failure expression that checks that the invocation property indicates processing failure -->  
27:                <until-successful objectStore-ref="objectStore" maxRetries="5"   
28:                              secondsBetweenRetries="10" failureExpression="groovy:message.getInvocationProperty('callAction') == '500'"  
29:                              deadLetterQueue-ref="unprocessedMessageStore" doc:name="Until Successful">  
30:  <!-- LEARN: The key is to move all processing to a sub-flow where you can catch recoverable and unrecoverable exceptions. -->  
31:                  <processor-chain>  
32:                                      <flow-ref name="mySubFlow"/>  
33:                                  </processor-chain>  
34:                              </until-successful>  
35:                          </when>  
36:                          <otherwise>  
37:                              <flow-ref name="errorHandlerBadInput" doc:name="errorHandlerBadInput"/>  
38:                          </otherwise>  
39:                      </choice>  
40:                  </processor-chain>  
41:                  </when>  
42:              </choice>  
43:          </processor-chain>  
44:      <catch-exception-strategy doc:name="Catch Exception Strategy">  
45:          <flow-ref name="jmsAuditDirectWrite" doc:name="Log Unprocessed Message to Queue"/>  
46:    </catch-exception-strategy>  
47:  </flow>  
48:    
49:    <sub-flow name="myWebServiceClient" doc:name="myWebServiceClient">  
50:          <cxf:jaxws-client operation="MyRequest" serviceClass="com.my.client.v1.IMemberManager" doc:name="Webservice Client">  
51:              <cxf:outInterceptors/>  
52:          </cxf:jaxws-client>  
53:          <http:outbound-endpoint exchange-pattern="request-response" address="http://..." doc:name="myClientRequest"/>  
54:      </sub-flow>  
55:      <sub-flow name="jmsAuditDirectWrite" doc:name="jmsAuditDirectWrite">  
56:      <expression-transformer>  
57:      <return-argument expression="session:srcPayload" evaluator="header" />  
58:      </expression-transformer>  
59:      <jms:outbound-endpoint queue="esb.unprocessed.request.err" connector-ref="jmsEsbXAConnector" doc:name="JMS"/>  
60:      </sub-flow>  
61:    
62:      <sub-flow name="mySubFlow">  
63:          <processor-chain>  
64:  <!-- LEARN: Call the client web service. -->  
65:              <flow-ref name="myWebServiceClient" doc:name="Invoke WebService"/>  
66:  <!-- LEARN: This is the custom transformer of the response ( See code snippet below) that brings back with it a status code (which acts like a non-recoverable exception or a recoverable exception exception as the case may be. -->  
67:         <custom-transformer class="com.mycompany.transformer.MyResponseTransformer" doc:name="Java"/>  
68:         <choice>  
69:  <!-- LEARN: This is the condition that says this is a non-recoverable exception and hence, write to a JMS queue and skip the loop. -->  
70:           <when evaluator="groovy" expression="message.getInvocationProperty('skip') == 'Y'">  
71:                      <flow-ref name="jmsAuditDirectWrite" doc:name="Log Unprocessed Message to Queue"/>  
72:                      <message-properties-transformer scope="invocation" doc:name="Message Properties">  
73:  <!-- LEARN: This is the property that fails the failure expression on the Until-Successful loop and hence until successful loop processing stops -->  
74:                          <add-message-property key="callAction" value="202Y"/>  
75:                      </message-properties-transformer>  
76:                  </when>  
77:                  <when evaluator="groovy" expression="message.getInvocationProperty('skip') == '202Y'">  
78:  <!-- LEARN: This is the condition that says this message was processed successfully and hence skip the loop. -->  
79:                      <message-properties-transformer scope="invocation" doc:name="Message Properties">  
80:  <!-- LEARN: This is the property that fails the failure expression on the Until-Successful loop and hence until successful loop processing stops -->  
81:              <add-message-property key="callAction" value="202Y"/>  
82:                      </message-properties-transformer>  
83:                  </when>  
84:              </choice>  
85:          </processor-chain>  
86:      </sub-flow>  
87:    


The Custom Response Transformer

Within com.mycompany.transformer.MyResponseTransformer, this happens:
// jsr is the unmarshalled response returned from the web service I called.
// it carries a status code. A non-zero value is a failure in processing.
// the resendFlag indicates whether it was a recoverable exception (resend=true)
// or a non-recoverable exception (resend=false)

1:  returnValue.setMessage(jsr.getMessage());  
2:  returnValue.setStatus(jsr.getStatus());  
3:  if (jsr.getStatus().codeValue() == 0) {  
4:    message.setInvocationProperty("callAction", "202");  
5:      logger.info("Client SUCCESSFULLY PROCESSED with status code + message : " + returnValue.getStatus() + ";;;;" +returnValue.getMessage());  
6:      message.setInvocationProperty("skip", "202Y");  
7:  } else {  
8:    if (!jsr.isResend()) {  
9:          message.setInvocationProperty("callAction", "500");  
10:          logger.info("Client FAILED PROCESSING with status code + message : " + returnValue.getStatus() + ";;;;" +returnValue.getMessage());  
11:          message.setInvocationProperty("skip", "Y"); //do not resend; store;  
12:  } else {  
13:          message.setInvocationProperty("callAction", "500");  
14:          logger.info("Client requested a RESEND of the request with status code + message : " + returnValue.getStatus() + ";;;;" +returnValue.getMessage());  
15:          message.setInvocationProperty("skip", "N");  
16:          }  
17:      }  
18:  }   
19: