This question is a follow up to my earlier question [here][1]
Thanks to #aled I am using 'set-payload' with expression.
Am reading a file and trying to parse and then validate each row.
Edit2 : added File details below : ( I need to validate that each row of the file has a price value , in below example line item 'Butter' has no price so should be flagged as an error while the other rows are good and they should be sent as a POST request to a third party api
d1|Milk|3.9|
d2|Butter||
g1|Bread|1.6|
g2|Biscuits|3.4|
d3|Yogurt|5.5|
d4|Flavored Yogurt|5.5|
m1|Toothbrush|12|
File reading is a repeatable-in-memory-stream
Now after reading the file , am setting the payload using 'set-payload'.
Here is the complete code of this flow :
<flow name="get:shoppingProducts" >
<logger level="INFO" doc:name="Logger" message="get list of products request received "/>
<file:read doc:name="Read file" config-ref="File_Config" path="/product_master/test.unl" outputMimeType='application/csv; header=false; separator=|' outputEncoding="utf-8">
<repeatable-in-memory-stream />
</file:read>
<choice doc:name="is the file empty ?" >
<when expression="sizeOf(payload) == 0">
<logger level="ERROR" doc:name="Payload is empty " message="Empty payload from File read !"/>
<raise-error doc:name="Raise error on empty file " type="XYZ:EMPTY" description="Empty file"/>
</when>
<otherwise >
<logger level="INFO" doc:name="Payload not empty" message="All good"/>
</otherwise>
</choice>
<set-payload value="#[output application/json
---
payload map (value,index)->
{
id:value.column_0,
description:value.column_1,
price:value.column_2
}]" doc:name="Set Payload" mimeType="application/json" encoding="UTF-8" />
<foreach doc:name="For Each file record" collection="#[payload]">
<logger level="DEBUG" doc:name="Log record" message="#[payload.description]"/>
<scripting:execute doc:name="Validate data" engine="Groovy">
<scripting:code ><![CDATA[println("hello world 001");
println(payload);
println("hello world 002");
println(message.getPayload());
println("hello world 003");
println(payload.description);]]></scripting:code>
</scripting:execute>
</foreach>
<logger level="INFO" doc:name="Logger" message="after reading products file "/>
This seems to work fine and I can see payload like below ( after calling set-payload ) :
[
{
"id": "d1",
"description": "Milk",
"price": 3.9
},
{
"id": "d2",
"description": "Butter",
"price": 4.5
},
...
]
Now I am trying to iterate over the data in a for loop
First component in the for loop is a 'Logger' which is logging the payloads description:
<logger level="DEBUG" doc:name="Log record" message="hello we are here #[payload.description]"/>
This is showing up fine in logs:
hello we are here "Milk"
( I am running code in debug mode ) and now the minute control reaches my groovy script inside the for loop , things go wrong ...
In debug the payload still shows up correctly as :
{
"id": "d1",
"description": "Milk",
"price": "3.9"
}
However when I try and access one of its attributes in groovy script am getting this error:
"groovy.lang.MissingPropertyException: No such property: description for class: org.mule.runtime.core.internal.streaming.bytes.ManagedCursorStreamDecorator"
Could this be happening because I am using 'repeatable-in-memory-stream' which is causing for loop to again get the stream rather than json payload ?
I also tried changing file read to : non-repeatable-stream but in this case my set-payload script is not able to handle :
org.mule.runtime.module.extension.internal.runtime.result.AbstractReturnDelegate$ConnectedInputStreamWrapper#70ee7444
What am I doing wrong and how to correct this please?
The problem is your groovy script. You are assuming the objects are a specific Java class and trying to access it directly. In some cases it may be true but you can not make assumptions. It also completely unneeded since Mule 4 & DataWeave provide a direct alternative to what you are trying to do (ir a logger component). Mule 4 is designed for users to avoid having to resort to Java or Groovy for normal tasks.
In your updated comments and question you made it clear that you need to validate that the price field is not null. That is a very basic validation and there is absolutely no need to go the Groovy/Java route for that. You can add a choice router inside the foreach loop with condition isEmpty(payload.price) to validate if the price is null, empty or spaces (since it is a string spaces is a valid check). Mule built-in components know to handle payloads that are contained in a stream transparently you don't even need to bother about handling streams.
Related
I have defined a variable to keep application/json collection.
My original payload contain following json block;
"amendments": {
"amendmentId": ["8a9a84b76b5a5a59016b687bae35012e",
"8a9a84b76b5a5a59016b6888e90e0144"]
}
<set-variable value="#[payload..amendmentId[1]]" doc:name="AmendmentIds" doc:id="03b6c46a-fc7b-43d4-b23a-502146ef0b13" variableName="amendmentids"/>
<logger level="INFO" doc:name="Logger" doc:id="6a1ad892-65f9-4dd2-9891-bdf3ad64c908" message="#[vars.amendmentids]" />
It prints as;
[
"8a9a84b76b5a5a59016b687bae35012e",
"8a9a84b76b5a5a59016b6888e90e0144"
]
Then I use ForEach loop to define few process logic for each Id,
Within foreach loop how I can get, each above id?
I used,vars.amendmentids[vars.counter]], but getting following error[1]
<foreach doc:name="For Each" doc:id="7b741315-aa28-4704-82f0-629c21d93853" collection="#[vars.amendmentids]">
<logger level="INFO" doc:name="Logger" doc:id="049c3137-2f92-4077-8e3f-b26516cc5528" message="#[vars.amendmentids[vars.counter]]"/>
</foreach>
[1]
Message : "Internal execution exception while executing the script, this is most probably a bug, file an issue with the script and the input data.
Caused by:
java.lang.RuntimeException: Unable to infer a output media type as more than one is being used: application/json,application/java please specify using: output <your mime-type> --- <expr>
at org.mule.weave.v2.el.MuleDataWeaveHelper$.inferImplicitOutput(MuleDataWeaveHelper.scala:69)
.............
" evaluating expression: "vars.amendmentids[vars.counter]".
Error type : MULE:EXPRESSION
I fixed it by defining output mime type like;
<logger level="INFO" doc:name="Logger" doc:id="049c3137-2f92-4077-8e3f-b26516cc5528" message="#[output application/java --- vars.amendmentids[vars.counter - 1]]"/>
Can we access a json element inside flow variables in Mule?
Eg: My flowVar value is {
"Input1": {
"Value1": "UNITED STATES"
}
}
How can I access the element 'Value1' using MEL in mule?
Thanks,
ROA
try the below expression,
#[json:/Input1/Value1]
You can use dw function or json path expression to achieve this.
examples:
[dw('payload.Request.name')]
[json:Request/name]
Right answer is
#[flowVars.theVariable.Input1.Value1]
but get here you need a lot what to do. You have to create variable appropriately to match your description. Also name of the variable is missed but you need it anyway. Here is code
<flow name="AccessFlowVariable">
<poll doc:name="Poll">
<fixed-frequency-scheduler frequency="10000000"/>
<logger message="Flow started" level="INFO" doc:name="Logger"/>
</poll>
<dw:transform-message doc:name="Transform Message">
<dw:set-variable variableName="theVariable"><![CDATA[%dw 1.0
%output application/java
---
{ "Input1": { "Value1": "UNITED STATES" } }]]></dw:set-variable>
</dw:transform-message>
<logger message="#[flowVars.theVariable.Input1.Value1]" level="INFO" doc:name="Logger"/>
<logger level="INFO" doc:name="Logger"/>
</flow>
and here is result
"Can we access a json element inside flow variables in Mule?": Yes we can access json element inside flow variable.
"How can I access the element 'Value1' using MEL in mule?"
Let says if you have stored this json in flow variable (xyz) then you can access it using the bellow syntax:
flowVars.xyz.Input1.Value1
I'm trying to initialize a map with the following in a mule expression-component:
#[message.payload = [ 'changeSet' : #[payload], 'sourceAndConnectionMap' : #[flowVars['sourceAndConnectionMap']]]]
Based on the following page, this looks okay, but does not work.
https://developer.mulesoft.com/docs/display/current/Mule+Expression+Language+Reference
Only the first key is set ('changeSet'), and for some reason the payload, which is a map, becomes an ArrayList containing a map... I can also initialize this with just the second key and valy, so I know there isn't an issue resolving the flowVar.
Is there something basic about how I am initializing this map that is incorrect?
In MEL a map should be created like:
#[{'key':'value', 'key1':'value1'}]
If you do
#[[]]
That's just a list of elements.
The other comments, if you're already inside a:
#[]
You need not to add it again. In your example you have it several times.
Please run this through the Mule debugger (put brakepoints in the logs)and you'll see the difference.
<flow name="test>
<set-payload value="#[{'key':'value', 'key1':'value1'}]" doc:name="Set Payload"/>
<logger level="INFO" doc:name="Logger"/>
<set-payload value="#[{'key':'value', 'key1':payload}]" doc:name="Copy_of_Set Payload"/>
<logger level="INFO" doc:name="Copy_of_Logger"/>
<set-payload value="#[['key','value', 'key1','value1']]" doc:name="Copy_of_Set Payload"/> <logger level="INFO" doc:name="Logger"/>
</flow>
HTH
I'm using Mule 3.4 CE and I have a JSON data coming through HTTP in the following format:
{
"People" : [
{
"Details" :
{
"Name" : "John Smith",
"Email" : "abc#mail.com"
}
},
{
"Details" :
{
"Name" : "Tim Smith",
"Email" : "def#mail.com"
}
},
{
"Details" :
{
"Name" : "Ken Smith",
"Email" : "ghi#mail.com"
}
},
}
I need to extract the emails and lookup the Salesforce contact with these emails and at the same time I want to retain the JSON payload. So my question is how do I extract the emails through MEL? (for e.g. something like "People/Details/*/Email" - I know this is not a valid, but I'm looking for the right syntax.
Edit: I want to extract the emails in one shot rather than indexing (for e.g. People/Details[0].Email, possibly using MEL.
There best way to query json is to transform it to a Map.
<json:json-to-object-transformer returnClass="java.util.HashMap" />
And then query it using MEL like standard MVEL or Java syntax
<logger message="#[payload.People[0].Details.email]" level="INFO" />
If you want to keep the original json payload intact, you can store the map in a variable using an enricher:
<enricher target="#[flowVars.myJsonMap]">
<json:json-to-object-transformer returnClass="java.util.HashMap" />
</enricher>
And query the variable instead of the payload:
<logger message="#[flowVars.myJsonMap.People[0].Details.email]" level="INFO" />
You could also map the json to a custom class using Jackson and change the returnClass attribute to your class.
This MEL cheat sheet detail JSON processing with MEL and also how to handle Maps, arrays etc: http://www.mulesoft.org/documentation/display/current/MEL+Cheat+Sheet
Note: You may come across a #[json:] evaluator, but this is deprecated in favour of the approach above.
UPDATE:
If you want to grab all the emails at once you can use MVEL projections:
<enricher target="#[flowVars.myJsonMap]" source="#[(Details.email in payload.People)]">
<json:json-to-object-transformer returnClass="java.util.HashMap" />
</enricher>
Mvel projections: http://mvel.codehaus.org/MVEL+2.0+Projections+and+Folds
If you want to get the particular email in the details use the below MEL expression.
#[json:People[0]/Details/Email]
If you want to get all the emails, pass the json path of repeatable in ForEach Collections then pass the path to get Email as shown below.
<flow name="parsingjsonFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/" doc:name="HTTP"/>
<foreach collection="#[json:People]" doc:name="For Each">
<logger message="#[json:/Details/Email]" level="INFO" doc:name="Logger"/>
</foreach>
<catch-exception-strategy doc:name="Catch Exception Strategy">
<logger message="#[exception.getClass()]" level="INFO" doc:name="Logger"/>
</catch-exception-strategy>
</flow>
I've a Soap request coming into mule flow. Am tasked with getting information out of payload and depending on outcome, push the original request to different jms queues.
To get the desired information out of payload, I'm using XSLT tranformer (not XPath, because I need to get IDREF attribute from an element, based on IDREF, get the element and then a child element out of the IDREF object).
Based on the outcome of the of XSLT tranformation, I use choice element to push original payload. Am storing original payload in a Session (can do it in Inbound as well). After XSLT tansformation, apply choice router to find out appropriate queue, and then want to push the original payload into queue(original payload in stored in a session variable). I am using <expression-component> element. Below is the snippet of mule-flow:
<flow name="ProcessXML121Order">
<jms:inbound-endpoint queue="mviq.121.order" exchange-pattern="one-way" />
<logger message="121 order payload is #[payload]" level="INFO" />
<message-properties-transformer scope="session">
<add-message-property key="mviPayload" value="#[payload]"/>
</message-properties-transformer>
<xm:xslt-transformer xsl-file="chooseVendor.xslt" />
<logger message="After xsl file payload is #[payload]" level="INFO" />
<choice>
<when expression="'EMSI'">
<logger message="Vendor is EMSI" level="INFO" />
<expression-component>payload=#[header:SESSION:mviPayload]</expression-component>
<jms:outbound-endpoint queue="mviq.121.order.emsi" />
</when>
<when expression="'PRMD'">
<logger message="Vendor is PRMD" level="INFO" />
<jms:outbound-endpoint queue="mviq.121.order.prmd" />
</when>
<when expression="'RSA'">
<logger message="Vendor is RSA" level="INFO" />
<logger message="RSA payload is #[payload]" level="INFO" />
<jms:outbound-endpoint queue="mviq.121.order.rsa" />
</when>
<otherwise>
<logger message="Vendor is Error" level="INFO" />
<logger message="Vendor error payload is #[payload]" level="INFO" />
<jms:outbound-endpoint queue="mviq.error" />
</otherwise>
</choice>
</flow>
Following exception is thrown when evaluating payload=#[header:SESSION:mviPayload]
[ProcessXML121Order.stage1.02] exception.AbstractExceptionListener (AbstractExceptionListener.java:296) -
********************************************************************************
Message : Execution of the expression "payload=#[header:SESSION:mviPayload]" failed. (org.mule.api.expression.ExpressionRuntimeException). Message payload is of type: byte[]
Code : MULE_ERROR-29999
--------------------------------------------------------------------------------
Exception stack is:
1. [Error: illegal use of operator: +]
[Near : {... Unknown ....}]
^
[Line: 1, Column: 0] (org.mvel2.CompileException)
org.mvel2.ast.OperatorNode:46 (null)
2. Execution of the expression "payload=#[header:SESSION:mviPayload]" failed. (org.mule.api.expression.ExpressionRuntimeException)
org.mule.el.mvel.MVELExpressionLanguage:211 (http://www.mulesoft.org/docs/site/current3/apidocs/org/mule/api/expression/ExpressionRuntimeException.html)
3. Execution of the expression "payload=#[header:SESSION:mviPayload]" failed. (org.mule.api.expression.ExpressionRuntimeException). Message payload is of type: byte[] (org.mule.api.MessagingException)
org.mule.execution.ExceptionToMessagingExceptionExecutionInterceptor:35 (http://www.mulesoft.org/docs/site/current3/apidocs/org/mule/api/MessagingException.html)
--------------------------------------------------------------------------------
Root Exception stack trace:
[Error: illegal use of operator: +]
[Near : {... Unknown ....}]
^
[Line: 1, Column: 0]
at org.mvel2.ast.OperatorNode.getReducedValueAccelerated(OperatorNode.java:46)
at org.mvel2.MVELRuntime.execute(MVELRuntime.java:85)
at org.mvel2.compiler.CompiledExpression.getValue(CompiledExpression.java:105)
+ 3 more (set debug level logging or '-Dmule.verbose.exceptions=true' for everything)
********************************************************************************
I've 2 questions:
How can I overwrite the original payload? (Not saying this is the best way to go)
What is the better approach in this scenario? Is it advisable to keep original payload intact (in this case) and store XSLT output in other variable? How can I do that? What is the path (mule component) I can use to achieve that? I'm very new to Mule and seek community advice.
Thanks for your time looking into this.
Before answering your questions, let's rewrite this broken expression:
<expression-component>payload=#[header:SESSION:mviPayload]</expression-component>
as:
<set-payload value="#[sessionVars.mviPayload]" />
The following would work too but would be more complex for no good reason:
<expression-component>payload=sessionVars.mviPayload</expression-component>
Also this:
<message-properties-transformer scope="session">
<add-message-property key="mviPayload" value="#[payload]"/>
</message-properties-transformer>
would be better written:
<set-session-variable variableName="mviPayload" value="#[message.payload]" />
Now to your questions:
Use set-payload
What you are doing is the best: transformers, like XSL-T, applies naturally to the current message payload so saving the original in a property then transforming the main payload is OK. Just one thing: prefer a flow variable instead of a session variable. Indeed, in your case, I don't think you need the original payload outside this flow, so storing in session is overkill.
So I suggest you use:
<set-variable variableName="mviPayload" value="#[message.payload]" />
to store the original payload and the following to re-establish it:
<set-payload value="#[mviPayload]" />
Use Set-Payload and provide the data to be overwritten.
You could do this with lot of ways.
Use set-payload
Use expression, #[payload='']
Use an transformer/component
You can keep the payload intact and store the value of the xslt transformation in a variable using message enricher. Use the variable inside choice to determine which queue to go to. Below is a code snippet
<enricher doc:name="Message Enricher" target="#[flowVars.transformResultVar]">
<!--perform transformation logic here-->
</enricher>
Reference: https://dzone.com/articles/content-enrichment-using-mule-message-enricher-com