Aggregate data from for-each loop - mule

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

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
[]

mule org.json.JSONObject returning property value as null though the json property does have value for it

I am using the below code to convert the input payload string to json in mule. The below code sometimes working and sometimes not. its not working on standalone and working on studio. Not able to nail down the exact cause for it. but based on the loggers that i see that the property value is coming null after the expression statement. i am suspecting this could be with the jar that's getting used here. i am still digging further on it.
<logger message="input: #[payload]" level="INFO" doc:name="Logger"/>
<set-payload value="#[payload.'data']" mimeType="application/json" doc:name="Set Payload" encoding="ISO-8859-2"/>
<logger message="createConnection: #[payload]" level="INFO" doc:name="Logger"/>
<expression-component doc:name="Expression"><![CDATA[String input = payload;
payload = new org.json.JSONObject(input);
]]></expression-component>
<logger message="before json to object: #[payload.con_id] #[payload.'con_id']" level="INFO" doc:name="Logger"/>
<json:json-to-object-transformer returnClass="java.util.HashMap" doc:name="JSON to Object"/>
Input JSON:
data: {"name":"QA_tst2","description":"tst","con_id":10,"con_connection_id":null,
"verticalParam":[{"param_value":"abc","param_name":"Host"},{"param_value":"21","param_name":"Port"}],"CON_CATEGORY_NAME":"File"}
I don't think that notation will work for JSONObject, try using
payload.get('con_id')
as per the javadoc: https://stleary.github.io/JSON-java/org/json/JSONObject.html.
The reason this won't work with the notation you have tried, is that Mule supports that notation for Maps, and org.json.JSONObject does not implement java.util.Map. You could try using javax.json.JSONObject instead, which will support that notation.
I have figured out the current issue. if there is any logger added to fetch the properties from the payload right after the expression component then its screwing up further. if you just remove the logger that was added after the expression component then after json to object conversion, i am able to fetch the values. that solves the current issue. but i would like to understand the difference between fetching the properties #[payload.con_id] vs #[payload.'con_id']. i can start a separate conversation for the same.

how to insert multiple rows in mule database connector?

I want to insert multiple rows in mule database connector at a time. Could anyone kindly please help me on this?.
I can successfully insert the below message as a post request to a mule flow.
{
"patient_todo_id" : "2",
"comment_date" : "2017-09-20 14:41:16",
"comment_text" : "send me the steps to check the Pulse rate"
}
How to insert the below post message into a database in a mule flow?
[{
"patient_todo_id" : "2",
"comment_date" : "2017-09-20 14:41:16",
"comment_text" : "send me the steps to check the Pulse rate"
},
{
"patient_todo_id" : "2",
"comment_date" : "2017-09-20 14:41:16",
"comment_text" : "send me the steps to check the Pulse rate"
}]
Please find the below mule flow configuration file which has been configured to insert a single row at a time.
<flow name="carrotcube-patient-todo-commentFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="${http.path.mrs.todo.comment}" doc:name="HTTP"/>
<set-variable variableName="variable" value="#[payload]" mimeType="application/json" doc:name="Variable"/>
<json:json-to-object-transformer returnClass="java.lang.Object" doc:name="JSON to Object"/>
<logger message="#[payload.comment_text]" level="INFO" doc:name="Logger"/>
<db:insert config-ref="MySQL_Configuration" doc:name="Database">
<db:parameterized-query><![CDATA[insert into patient_todo_detail(patient_todo_id,comment_date,comment_text) values (#[payload.patient_todo_id],#[payload.comment_date],#[payload.comment_text])]]></db:parameterized-query>
</db:insert>
</flow>
Use the Bulk update mode and pass your connector a collection of object to insert. In Studio, simply check "Bulk mode" in the Basic Settings section of the database connector. The example array you provide in your question is just fine, you can then do something like:
<db:insert config-ref="MySQL_Configuration" bulkMode="true" doc:name="Database">
<db:parameterized-query><![CDATA[
INSERT INTO mytable(id, name)
VALUES (#[payload.id], #[payload.name]);]]>
</db:parameterized-query>
</db:insert>
Each element of your list will then become the payload in the connector and will be inserted. You do not need to use a for-each or any loop mechanism. Make sure to pass an iterable object though.
See the related docs:
https://docs.mulesoft.com/mule-user-guide/v/3.8/database-connector#setting-up-database-connector-operation
This is super easy, you can do it by 2 ways:
solution1 using splitter:
<flow name="testFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/test" doc:name="HTTP"/>
<json:json-to-object-transformer returnClass="java.lang.Object" doc:name="JSON to Object"/>
<collection-splitter doc:name="Collection Splitter"/>
<logger message="insert into patient_todo_detail(patient_todo_id,comment_date,comment_text) values (#[payload.patient_todo_id],#[payload.comment_date],#[payload.comment_text])" level="INFO" doc:name="Logger"/>
<collection-aggregator failOnTimeout="true" doc:name="Collection Aggregator"/>
<json:object-to-json-transformer doc:name="Object to JSON"/>
</flow>
solution2 using foreach:
<flow name="testFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/test" doc:name="HTTP"/>
<json:json-to-object-transformer returnClass="java.lang.Object" doc:name="JSON to Object"/>
<foreach doc:name="For Each" collection="#[payload]">
<logger message="insert into patient_todo_detail(patient_todo_id,comment_date,comment_text) values (#[payload.patient_todo_id],#[payload.comment_date],#[payload.comment_text])" level="INFO" doc:name="Logger"/>
</foreach>
<json:object-to-json-transformer doc:name="Object to JSON"/>
</flow>
In both the cases, you can see your logger is getting the correct values in your SQL statement you are using :
<logger message="insert into patient_todo_detail(patient_todo_id,comment_date,comment_text) values (#[payload.patient_todo_id],#[payload.comment_date],#[payload.comment_text])" level="INFO" doc:name="Logger"/>
Now you can replace the logger with your DB component
UPDATE based on the comment:
<set-variable variableName="myMap" value="#[new java.util.HashMap()]" doc:name="Variable"/>
<foreach doc:name="For Each" collection="#[payload]">
<db:insert config-ref="MySQL_Configuration" doc:name="Database">
<db:parameterized-query><![CDATA[insert into patient_todo_detail(patient_todo_id,comment_date,comment_text) values (#[payload.patient_todo_id],#[payload.comment_date],#[payload.comment_text])]]></db:parameterized-query>
</db:insert>
<expression-component doc:name="Expression"><![CDATA[flowVars.myMap.put('row'+flowVars.counter,payload)]]></expression-component>
</foreach>
<logger message="Final status #[flowVars.myMap.toString()]" level="INFO" doc:name="Logger"/>
Here at the end you will get in logger the status of each row inserted which is 1 means successful
To get a particular row details ouside foreach:
<logger message="#[flowVars.myMap.get('row1').toString()]" level="INFO" doc:name="Logger"/>
So, based on the status you can further display your custom status messages
Ashok, I just crossed this hurdle, so you got really lucky here as I had to struggle through this for a while. Very surprising that such a common scenario was not to be found on the Internet. The steps are listed below ,and the flow is below too.
1) Use transform to convert the post payload to java list (application/java) – the output should just have payload (remove the curly braces and just put payload). This payload should come from the body.
2) Use for each scope and put the database insert statement there
3) In the database insert statement use [payload[‘username’]] kind of syntax to refer to the value of username in the current record (or whatever field names you have). I am using a stored prod to insert, but you get the idea.
I am not able to post the flow here .. it's cutting it off and showing it weird. I'll try to send you through email if you can share.
Now all I have to figure out is how to send a nice message back with the insert status. Anybody who already has done that.. appreciate your inputs!
Ashok, replying to your comment on aggregating the response from the for each here, as I can't add comments yet. Check out the following link, which offer s a few options.
https://forums.mulesoft.com/questions/60273/save-of-for-each-database-query-response-to-a-new.html

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.

Mule - split a big JSON list into multiple smaller JSON lists

I have a list of json objects containing about 200 objects. I want to split that list into smaller lists where each list contains max 20 objects each. I would like to POST each sublist to HTTP based endpoint.
<flow name="send-to-next-step" doc:name="send-to-vm-flow">
<vm:inbound-endpoint exchange-pattern="one-way"
path="send-to-next-step-vm" doc:name="VM" />
<!-- received the JSON List payload with 200 objects-->
<!-- TODO do processing here to split the list into sub-lists and call sub-flow for each sub-list
<flow-ref name="send-to-aggregator-sf" doc:name="Flow Reference" />
</flow>
One possible way is that I write a java component which iterates over the list and after iterating over each 20 objects, call sub-flow. Is there any better way of accomplishing this?
If your payload is a Java Collection, the Mule foreach scope has batching built in: http://www.mulesoft.org/documentation/display/current/Foreach
Example:
<foreach batchSize="20">
<json:object-to-json-transformer/>
<http:outbound-endpoint ... />
</foreach>
You could use the Groovy collate method for the batching, and then foreach or collection-splitter, depending on your needs:
<json:json-to-object-transformer returnClass="java.util.List"/>
<set-payload value="#[groovy:payload.collate(20)]"/>
<foreach>
<json:object-to-json-transformer/>
<http:outbound-endpoint exchange-pattern="request-response" host="0.0.0.0" port="8082" path="xx"/>
</foreach>
<set-payload value="#[groovy:payload.flatten()]"/>
This will send each batch of 20 objects to the http endpoint and then flatten back to the original list.