How do I handle an HTTP path parameter in mule Choice router? - mule

I am trying to use a choice router to handle HTTP messages based on their path. This works well until I hit the case where the message is being submitted with a PUT method and the trailing part of the path is the customerID. So I have a path similar to this: services/v1/customer/{custNo}. In the choice router I have:
<choice doc:name="Route Message By Path">
<when expression="message.inboundProperties['http.relative.path'] == 'services/v1/users'">
<flow-ref name="NewUser" doc:name="New User"/>
</when>
<when expression="message.inboundProperties['http.relative.path'] == 'services/v1/users/{userID}'">
<flow-ref name="UpdateUser" doc:name="Update User"/>
</when>
<when expression="message.inboundProperties['http.relative.path'] == 'services/v1/emails'">
<flow-ref name="CaptureEmail" doc:name="Capture Email"/>
</when>
<when expression="message.inboundProperties['http.relative.path'] == 'services/v1/taxes'">
<flow-ref name="Taxes" doc:name="Taxes"/>
</when>
<otherwise>
<logger message="The path submitted is unknown. Submitted path is: #[message.inboundProperties['http.relative.path']]" level="INFO" doc:name="Unknown path"/>
<set-payload value="The path submitted is unknown. Submitted path is: #[message.inboundProperties['http.relative.path']]" doc:name="Set Payload"/>
<http:response-builder status="500" contentType="text/plain" doc:name="HTTP Response Builder"/>
</otherwise>
</choice>
I have this working using rest and annotated java classes but I would rather keep it simple and in mule components if I can. Is there a way to wildcard the path in the MEL for the router? Also, if keeping with using the choice router, is there a good/simple way of extracting the customer number from the path?

For the wildcard, you could use the regex function in your MEL expressions: http://www.mulesoft.org/documentation/display/current/Mule+Expression+Language+Reference
Something like:
<when expression="#[regex('services/v1/users/.*', message.inboundProperties['http.relative.path'])]">
However, I think the apikit and the apikit router might be better suited to your needs as it handles path and method routing and variable templating automatically: http://www.mulesoft.org/documentation/display/current/APIkit+Basic+Anatomy
Or for older versions of Mule maybe the Rest router: http://mulesoft.github.io/mule-module-rest-router/mule/rest-router-config.html

Related

Accessing variable inside of forEach in mule

I have two queries
Suppose if I declared two variables inside a forEach like flowVars.ABC and flowVars.DEF, how can I access those 2 variables outside that forEach block?
And each variable has a JSON payload, how can I add those 2 variable's data into single JSON payload?
Can anyone assist me? I unable to access the variables inside of foreach and adding 2 JSON.
This is my sample code
<flow name="test">
<foreach doc:name="For Each">
<scatter-gather doc:name="Scatter-Gather">
<set-variable variableName="ABC" value="#[payload]" mimeType="application/json" doc:name="ABC"/>
<set-variable variableName="DEF" value="#[payload]" mimeType="application/json" doc:name="DEF"/>
</scatter-gather>
</foreach>
<set-payload value="#[flowVars.ABC + flowVars.DEF]" mimeType="application/json" doc:name="adding 2 vars"/>
</flow>
You need to understand how scoping works with foreach. Any variables set inside the foreach scope will NOT be available outside of that scope. However, variables set outside of the foreach scope (e.g. a set-variable before the foreach) will be available inside the foreach scope. This should help you get around your issue. I'm taking out the scatter-gather because it really doesn't serve any purpose in your example:
<flow name="test">
<set-variable variableName="ABC value="#[payload] mimeType="application/json" doc:name="ABC"/>
<set-variable variableName="DEF value="#[payload] mimeType="application/json" doc:name="DEF"/>
<foreach doc:name="For Each">
<set-variable variableName="ABC" value="#[payload]" mimeType="application/json" doc:name="ABC"/>
<set-variable variableName="DEF" value="#[payload]" mimeType="application/json" doc:name="DEF"/>
</foreach>
<set-payload value="#[flowVars.ABC ++ flowVars.DEF]" mimeType="application/json" doc:name="adding 2 vars"/>
</flow>
Beyond this, I'm not sure if your code is a simplification or not, but as it stands now there are a couple things that are questionable:
Why are you using a scatter-gather? If you don't really need to do multiple things asynchronously (like making calls to multiple services), it's just a complication in your code. Setting two vars doesn't qualify, in my opinion.
What is your code supposed to do? From my perspective it looks like you're just setting the payload to a duplicate of the last element in the original payload. If so you could just do this in a transformer:
%dw 2.0
output application/json
---
if (not isEmpty(payload))
payload[-1] ++ payload[-1]
else
[]

How I do check if the variable exist in Mule 3.2?

How do check if the variable exist in Mule 3.2?
How create or replace a variable?
My Flow Exception is
<choice doc:name="Choice">
<when expression="#[myVar==null]">
<processor-chain>
<set-variable variableName="myVar" value="value1" doc:name="myVar"/>
</processor-chain>
</when>
<otherwise>
<processor-chain>
<set-variable variableName="myVar" value="#[variable:myVar]" doc:name="myVar"/>
</processor-chain>
</otherwise>
</choice>
"myVar==null" does not work
if the variable is an invocation variable please try
flowVars['myVar'] == null
Worst case scenario you could do:
message.getProperty('myVar', org.mule.api.transport.PropertyScope.INVOCATION).
Please notice the scope is an enum so you should provide the canonical in a MEL expression.
HTH
You can use this MEL to print in log or use in Choice component
#[message.invocationProperties('myVar')] == null
There is a component "Message Property" which will help you remove or replace or create Mule message properties. You should use that to perform your operation.
To best find it use keyword "propert" in the search of pallets in the Mule studio and explore the various components there to suit your needs.

Aggregate data from for-each loop

Scenario - Converting a csv file to json format, taking each json element and making a get request api call. I am doing this in a for-each loop sequence. I am getting a json response (extracting eventId and cost from each). Now I wish to club all these responses together under the main header listings and make a bigger json payload.
For example:
{
"listings": [
{
"eventId":"8993478",
"cost":34
},
{
"eventId":"xxxxxyyyy",
"cost":zz
},
]
}
How would I do this for all iteration entries. I can do it for a single entry(using groovy script).
You could define a variable before the for-each loop as an empty list with:
<set-variable variableName="listings" value="#[[]]" />
Then, on each iteration inside the for-each loop add an element to the previous variable with:
<expression-transformer expression="#[flowVars.listings.add(flowVars.iterationMap)]" />
In the previous code fragment I used the variable flowVars.iterationMap to denote the map generated on each iteration.
Finally, if needed, you can add a set-payload transformer after the for-each loop:
<set-payload value="#[flowVars.listings]" />
HTH, Marcos
You can use the Batch module but you would have to rewrite this logic a little bit different. For example, you will no longer be able to use an aggregation flowVar like Marcos suggested. Instead, you would need to use a fixed size batch:commit block (which would actually be better in many ways, for example you could start sending bulks to the remote API while still processing some of the other records in the background).
...I like Marco's answer and it worked perfectly for my use case.
Simply creating an array in a flow variable and using the add() method on the array in a ForEach scope did the trick.
The OP follow up question was a good one. It prompted me to do an alternate test using the approach suggested. See both of my flows here:
<flow name="sampleAggregatorFlow" doc:description="this is a simple demo that shows how to aggregate results into an accumulator array">
<http:listener config-ref="manage-s3-api-httpListenerConfig" path="/aggregate" allowedMethods="GET" doc:name="HTTP"/>
<set-payload value="#[['red','blue','gold']]" doc:name="Set Payload"/>
<set-variable variableName="accumulator" value="#[[]]" doc:name="accumulator"/>
<foreach doc:name="For Each">
<expression-transformer expression="#[flowVars.accumulator.add(payload)]" doc:name="addEm"/>
</foreach>
<set-payload value="#[flowVars.accumulator]" doc:name="Set Payload"/>
<json:object-to-json-transformer doc:name="Object to JSON"/>
</flow>
<flow name="Copy_of_sampleAggregatorFlow" doc:description="this is a simple demo that shows how to aggregate results into an accumulator array">
<http:listener config-ref="manage-s3-api-httpListenerConfig" path="/aggregate2" allowedMethods="GET" doc:name="Copy_of_HTTP"/>
<set-payload value="#[['red','blue','gold']]" doc:name="Copy_of_Set Payload"/>
<set-variable variableName="accumulator" value="#[new java.util.ArrayList()]" doc:name="Copy_of_accumulator"/>
<foreach doc:name="Copy_of_For Each">
<expression-transformer expression="#[flowVars.accumulator.add(payload)]" doc:name="Copy_of_addEm"/>
</foreach>
<set-payload value="#[flowVars.accumulator]" doc:name="Copy_of_Set Payload"/>
<json:object-to-json-transformer doc:name="Copy_of_Object to JSON"/>
</flow>
Both flows produced the same outcome:
[
"red",
"blue",
"gold"
]
Tests conducted 12/26/2017 with Anypoint Studio 6.4.1 and wth Mule Runtime 3.9

Mule foreach : Splitter returned no results

I get a list of files on amazon S3 and iterate over the list of files and process one file at a time. The corresponding flow is as follows --
<flow name="process-from-s3" doc:name="process-from-s3"
processingStrategy="synchronous">
<poll doc:name="Poll" frequency="${s3-poll-interval}">
<s3:list-objects config-ref="Amazon_S3" doc:name="Get List of files"
accessKey="${s3-access-key}" secretKey="${s3-secret-key}"
bucketName="${s3-read-bucket}" />
</poll>
<choice doc:name="Choice">
<foreach doc:name="For Each">
<set-session-variable variableName="s3_file_name" value="#[payload.getKey()]" doc:name="Session Variable"/>
<logger message="From bucket ( ${s3-read-bucket} ), received the file #[s3_file_name]" level="INFO" doc:name="Logger"/>
<flow-ref name="process_s3_file" doc:name="Flow Reference"/>
</foreach>
</choice>
</flow>
The flow works well, however it keeps on spitting the following log statements if there are no files found.
[03-06 21:52:05] WARN Foreach$CollectionMapSplitter
[[myapp].connector.polling.mule.default.receiver.01]: Splitter returned no results.
If this is not expected, please check your split expression
How can I avoid this annoying log message. Should I wrap the foreach within a choice router that processes the foreach if there is atleast one element in the list. Any suggestions are welcome.
I would rather set the log level for org.mule.routing.Foreach$CollectionMapSplitter to ERROR than configure any additional logic for this warning. See Mule docs for configuring logger/log4j if you need to.

How to log or handle original payload message in Mule

<flow>
<jms:inbound-endpoint queue="InputQueue"/>
<component class="MyComponent"/>
<choice>
<when expression="/Response/Status/Success" evaluator="xpath">
<jms:outbound-endpoint queue="LogInputQueue"/>
<jms:outbound-endpoint queue="SuccessQueue"/>
</when>
<when expression="/Response/Status/Error" evaluator="xpath">
<jms:outbound-endpoint queue="LogInputQueue"/>
<jms:outbound-endpoint queue="ErrorQueue"/>
</when>
<otherwise>
<jms:outbound-endpoint queue="LogInputQueue"/>
<jms:outbound-endpoint queue="ExceptionQueue"/>
</otherwise>
</choice>
</flow>
In this flow, MyComponent returns either success message as a response or error response or exception?
I need to log the original message from InputQueue in LogInputQueue in all the cases. How do I achieve this in my flow?
Did you mean to create a log file? In that case, you have to implement log4j using slf4j and use the line
<logger level="log_level" category="your_category" message="#[message:payload]"/>
where log_level is your desired logging level- "error", "debug", "info" etc...
your_category is your category of log defined in the log4j.properties file (it is optional actually)
and message="#[expression:value]" is your message to be logged given as an expression:scope:key combination. Scope is optional here.
Using log4j or slf4j you can log the payload.
[payload],we have logger component using this log payload in console.
Since, you need to send original message from InputQueue to LogInputQueue in all the cases, as you mentioned, what you need to do is :-
1. Remove <jms:outbound-endpoint queue="LogInputQueue"/> from all the cases in choice block
2. Store the original payload from InputQueue in a variable by placing it just after the JMS inbound endpoint
3. At the end of the flow, after the choice router, set the payload in set payload component from variable you stored the original payload
4. Now put <jms:outbound-endpoint queue="LogInputQueue"/> after your set payload component.
In this way you will able to send the original payload to the LogInputQueue as per your requirement.