Speeding up transforming DB select to XML with Mule 3.5 - mule

I'm trying to convert data from a database query to XML. There are about 20,000 rows. It takes something like ten minutes to run, which seems unreasonably slow to me.
I noticed that eventContext has a getOutputStream method, but that throws an exception when I try to call it returns null whenever I call it. I also don't have the Java expertise to know the best way to stream the XML, though I'm sure I can look that up, I'd appreciate any hints there.
The structure of the XML I want is something like:
<myElements>
<myElement>
<someId>123454</someId>
<someProperty>Some Value</someProperty>
<mySubElements>
<mySubElement>
<anotherId>67890</anotherId>
<anotherProperty>Another Value</anotherProperty>
</mySubElement>
<mySubElement>
<anotherId>24680</anotherId>
<anotherProperty>Yet Another Value</anotherProperty>
</mySubElement>
</mySubElements>
</myElement>
</myElements>
The output of my query is something like this:
SOME_ID SOME_PROPERTY ANOTHER_ID ANOTHER_PROPERTY
12345 Some Value 67890 Another Value
12345 Some Value 24680 Yet Another Value
I'm using the component transformer, and my code there looks something like this:
public Object onCall(MuleEventContext eventContext) throws Exception {
List queryResults = (List) eventContext.getMessage().getPayload();
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
// root elements
Document doc = docBuilder.newDocument();
Element myElements = doc.createElement("myElements");
doc.appendChild(myElements);
Element myElement = null;
Integer currentSomeId = null;
for (Integer i = 0; i < queryResults.size(); i++)
{
Map queryRow = (Map) queryResults.get(i);
if (!queryRow.get("SOME_ID").equals(currentSomeId))
{
myElement = doc.createElement("myElement");
/* populate my element, including with an empty mySubElements */
}
Element mySubElement = doc.createElement("myElement");
/* populate mySubElement */
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.transform(new DOMSource(doc), new StreamResult(baos));
eventContext.getMessage().setPayload(doc);
return baos.toByteArray();
}
My mule flow looks like this:
<flow name="MyElementsFlow-Https">
<http:listener config-ref="HTTP_Listener_Configuration" path="${webapi.myelements.path}" allowedMethods="GET" doc:name="HTTP"/>
<flow-ref name="MyElementsFlow" doc:name="Flow Reference"/>
</flow>
<flow name="MyElementsFlow" processingStrategy="synchronous">
<db:select config-ref="DB-Connector" doc:name="Select">
<db:template-query-ref name="select-template" />
</db:select>
<component class="myJavaComponent" doc:name="To XML"/>
</flow>
edit: I thought I'd try setting the HTTP connector to responseStreamingMode="ALWAYS" and the DB select to streaming="true", but when I do that, the iterator I get in my payload returns false from hasNext(), and next() throws an exception about being closed.
edit: Corrected result of calling getOutputStream.

The problem is that you're trying to load an XML which is unreasonably big into Memory (by using dom), the 10 minutes you're experiencing is probably spent most in garbage collection, so the first trivial thing to do to improve this is to add more heap to your wrapper.conf, you can do this only if the process will run in very low load.
I would suggest you to try a divide and conquer technique, maybe using your disk as intermediate storage.
You can use stax to stream XML
https://docs.oracle.com/javase/tutorial/jaxp/stax/using.html

Related

how to reuse the xpr file in Mule 4

In my case, I'm doing a migration from Mule 3 to Mule 4.
This flow, which includes transformers like DOM to XML, XML to DOM, and an expression component, needs to be migrated.
In Mule 4, I want to reuse the xrp file.
My flow for XML payload transform uses an XPR file by the expression component in Mule 3.
<flow name="rate-dtostepFlow">
<http:listener config-ref="HTTP_Listener_Configuration" path="/dtostep" allowedMethods="POST" doc:name="HTTP"/>
<mulexml:xml-to-dom-transformer returnClass="org.dom4j.Document" doc:name="XML to DOM"/>
<set-variable variableName="domPayload" value="#[payload]" doc:name="set domPayload "/>
<expression-component file="xpr/responseStubCannasure.xpr" doc:name="Expression"/>
<mulexml:dom-to-xml-transformer doc:name="DOM to XML"/>
</flow>
Input XML: https://github.com/Manikandan99/rate-dtostep/blob/master/request.xml
Output XML: https://github.com/Manikandan99/rate-dtostep/blob/master/response.xml
my MULE 3 application : https://github.com/Manikandan99/rate-dtostep/tree/master/rate-dtostep
ResponseStubcannsure xpr file:
import org.dom4j.*;
import java.util.*;
import java.util.logging.Logger;
Logger logger = Logger.getLogger("");
dtoCoverageStepsNodes = flowVars.domPayload.selectNodes("//DTOCoverage[#Status=\'Active\']/DTOSteps");
for (Node node : dtoCoverageStepsNodes){
//logger.info("inside: detach");
node.detach();
}
dtoCoverageNodes = flowVars.domPayload.selectNodes("//DTOCoverage[#Status=\'Active\']");
int i = 500;
for (Node node : dtoCoverageNodes){
//node.detach();
//logger.info("inside: assign prem");
node.addAttribute("FullTermAmt", Integer.toString(i));
node.addElement("DTOSteps");
stepNode = node.selectSingleNode("DTOSteps");
stepNode.addElement("DTOStep")
.addAttribute("Order","1")
.addAttribute("Name","Final Premium")
.addAttribute("Desc","Final Premium Desc")
.addAttribute("Operation","=")
.addAttribute("Factor",Integer.toString(i))
.addAttribute("Value",Integer.toString(i));
i+=1;
}
The xpr file transform the xml payload in the following ways:
updated the value of the DTOStep node.
The attribute value of DTOStep is autoincremented from 500 each time.
Please assist me.
You need to migrate the complete flow to Mule 4. The file responseStubCannasure.xpr is just a script in MEL (Mule 3 expression language). The extension is irrelevant, it could have been anything. MEL is very similar to Java so you could reuse the logic by encapsulating it into a Java class. You will need to add to the Java code the conversion to DOM4J from the input XML because Mule 4 doesn't support it. Probably is slightly easier to migrate the MEL script to a Groovy script because the basic syntax is very similar and both support scripts. Migrating to Java is just taking the additional steps of encapsulating the script into a method of a class and defining explicitly the types for variables.
Alternatively you could just delete the last 4 operations of the flow and replace them with a DataWeave transformation. Using a recursive function to navigate the keys and value recursively, using a condition to check if we are in the element DTOCoverage, with attribute Status == "Active" and then replace the nested element with the DTOSteps/DTOStep combination. Which is what your script does.
Example:
%dw 2.0
output application/xml
var startingValue=500
fun transformSteps(x, index)=
x filterObject ($$ as String != "DTOSteps") ++
{
DTOSteps: DTOStep #(Order:1, Factor: index + startingValue, Value: index + startingValue, Name:"Final Premiun", Operation:"=", Desc: "Final Premium Desc"): null
}
fun transformCoverage(x, index)=
{
val: x match {
case is Object -> x mapObject
if ($$ as String == "DTOCoverage" and $$.#Status == "Active")
{
DTOCoverage #(( $$.# - "FullTermAmt" ), FullTermAmt: $$$ + startingValue):
transformSteps($, index)
}
else
(($$): transformCoverage($, index+1))
else -> $
},
index: index
}
---
transformCoverage(payload,1).val
This solution doesn't completely resolve the Value and Factor (why two attributes with the same value?) sequential increase. You may need to do an additional transformation, or use Groovy or Java code to renumber them.

<Mule - DataWeave> Reference to variables where the variable name is dynamic

Hi I would like to seek for assistance for a DataWeave usage scenario.
I need to check if a variable for a card name exists (the card name is dynamic and cannot forsee beforehand).
If the variable already exists, then append current payload to that card name variable;
Else create the variable with the current payload
The problem is I do not have idea on how to refer to a variable with dynamic name.
I can save the current card name to a variable say "cardName", but how can I refer to the variable in DataWeave code afterwards?
Pseudoly below is what I would like to achieve
May I seek for advice on the way to achieve it?
You can access the vars using below notation
vars[dynamic variable]
As I do not know how your flow looks like and assuming you have a payload,
{
"data": [
{
"cardName": "cardName1",
"dataToMap": "first data"
},
{
"cardName": "cardName2",
"dataToMap": "2nd data"
},
{
"cardName": "cardName1",
"dataToMap": "2nd data for card name 1"
}
]
}
You can loop through the payload.data (using for each) and you can map it as
%dw 2.0
output application/java
var varName = payload.cardName
---
if (vars[varName] != null)
vars[varName] ++ "** **" ++ payload.dataToMap
else
payload.dataToMap
and have a set variable with name set with #[****] to dynamically choose the variable.
End result of this will have two vars with name cardName1 and cardName2 and their corresponding value will be "first data** **2nd data for card name 1" and "2nd data", respectively.
Here is an example flow of dynamically naming variables using a Set Variable component inside a For-Each loop.
This is a good way to persist data after the For-Each loop exits, since the payload resets to the payload before the For-Each scope is called.
<mule xmlns="http://www.mulesoft.org/schema/mule/core" xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd">
<flow name="dynamic-varsFlow" >
<scheduler doc:name="Scheduler" >
<scheduling-strategy >
<fixed-frequency frequency="10000"/>
</scheduling-strategy>
</scheduler>
<set-payload value='#[output application/json
var someInput = {
"data": [
{
"cardName": "cardName1",
"dataToMap": "first data"
},
{
"cardName": "cardName2",
"dataToMap": "2nd data"
},
{
"cardName": "cardName1",
"dataToMap": "2nd data for card name 1"
}
]
}
---
someInput.data]' doc:name="Set Payload" />
<foreach doc:name="For Each" >
<set-variable value='#[output application/json
var varName = payload.cardName as String
---
(if (vars[varName] != null)
vars[varName] ++ "** **" ++ payload.dataToMap
else
payload.dataToMap
)]' doc:name="Set Variable" variableName="#[payload.cardName]"/>
</foreach>
<foreach doc:name="Copy_of_For Each" >
<logger level="INFO" doc:name="Logger"
message="#[output application/json --- {varName: payload.cardName, varValue: vars[payload.cardName]}]"/>
</foreach>
</flow>
</mule>
`
Here are the log messages for each of the dynamically named vars inside the second For-Each loop. Notice there are two new vars dynamically named from the initial Set Payload data. In a more real-world flow, the payload would be read from the output of the Event Source, such as from an HTTP Connector or DB Connector:
Here is the payload and vars after the second For-Each scope is exited. The payload reverts back to it's initial value, but the changes made inside the first For-Each scope persist in the two vars:
You should use a Java map instead and use cardName as the key.
ok i might have not understood the ask 100% but i did a pretty simple flow to illustrate setting a variable dynamically if that variable doesnt exist. hope that helps you upto some extent. Here is how my complete flow looks like
in the set payload i am setting a dummy payload:
in the transform message next i declared a variable card which doesnt exists until then as you can see in the flow
and finally in the last set payload i am setting the payload with whatever vars.card hold
and i get the response back when i test my API
now if you want to dynamically declare the variable name with some value i believe that can be done too.

How can I stop <foreach> loop in between in mulesoft

I have an array list values which I loop using foreach and choice. As soon as the first success happens in choice I want to come out of the foreach loop. And I don't want continue to check remaining conditions. In mulesoft flow
Thank you in advance.
You might not be able "break" foreach loop, but you can always achieve this functionality using expression/groovy script. Please refer this: Is there a break statement in Mule <foreach>
Please use Break Statement for exit from foreach loop according to your condition true.
As you have mentioned in your use case
Wrap the message filter inside your for-loop.
<foreach doc:name="For Each" collection="#[]">
<message-filter onUnaccepted="flow" >
<expression-filter expression="#[]"/>
</message-filter>
</foreach>
First of all you should find out on which index you will get success message by using indexOf method. Then after use subList method to get the required list and use that list in Foreach loop.
<foreach collection="#[payload.subList(0,3)]" doc:name="For Each">
<logger message="#[payload]" level="INFO" doc:name="Logger"/>
</foreach>
Use Groovy
You can control the flow of a Groovy script much easier than a traditional "for-each" transformer. In your particular case, your groovy script would look something (but not exactly) like:
<scripting:component doc:name="Groovy">
<scripting:script engine="Groovy"><![CDATA[// Define the collection to loop
def loopCollection = flowVars.nameOfCollectiontoLoop
// Define the condition which needs to be met
def exitCondition = "Some condition to match"
// Loop the collection to do what you need
loopCollection.each { row ->
if (row.identifierName == exitCondition)
{
return
}
else
{
// Continue your processing
}
}
return payload]]></scripting:script>
</scripting:component>
Use a Choice strategy to stop processing
The other way which I can think of is to use a Choice as soon as you enter the loop to see if the condition has been previously met, if so, do nothing; as follows:
<flow name="testsFlow">
<set-variable variableName="conditionMet" value="#[false]" doc:name="conditionMet"/>
<foreach doc:name="For Each">
<choice doc:name="Choice">
<when expression="#[(flowVars.conditionMet == true)]">
<scripting:component doc:name="Do nothing">
<scripting:script engine="Groovy"/>
</scripting:component>
</when>
<otherwise>
<scripting:component doc:name="Continue Loop">
<scripting:script engine="Groovy"><![CDATA[// Define the collection to loop
def loopCollection = flowVars.nameOfCollectiontoLoop
// Define the condition which needs to be met
def exitCondition = "Some condition to match"
// Define the "conditionMet" variable
def conditionMet = flowVars.conditionMet
// Loop the collection to do what you need
loopCollection.each { row ->
if (row.identifierName == exitCondition)
{
conditionMet = true
}
else
{
conditionMet = false
// Continue your processing
}
}
return payload]]></scripting:script>
</scripting:component>
</otherwise>
</choice>
</foreach>
</flow>
Try these and let us know if you need more help.
There is no component to break the foreach loop. If you only want to process till the success condition, then you could use MEL to execute the foreach on a sublist like this:
<foreach collection="#[payload<condition>]" />
...
</foreach>
You can also use loops in Groovy.
Example. In a payload i have some HashMap -
def nap = payload.Result
for (v in nap)
{
if (v.Name == '!!! а папка')
{
assert v.Id != null
flowVars['folderid'] = v.Id
payload = 'recived Id of folder object is '+v.Id
break
}
}
return payload

Facing issue while convering json data into xml : mule esb

I tried to convert my json data into xml format. But Only half of data is convert into xml.
My payload is
{"orders":[{"orderName":"Laptop","price":34000,"Date":"2014/01/12","Type":"DELL","stock":52,"code":"152666AS"},
{"orderName":"Chip","price":345,"Date":"2014/02/20","Type":"DELL","stock":50,"code":"152666AW"},
{"orderName":"Laptop1","price":35000,"Date":"2015/02/13","Type":"DELL1","stock":51,"code":"152666AX"}]}
But in output I got only one json item
<?xml version='1.0'?>
<orders>
<orderName>Laptop</orderName>
<price>34000</price>
<Date>2014/01/12</Date>
<Type>DELL</Type>
<stock>52</stock>
<code>152666AW</code>
</orders>
My flow is as follow
<flow name="testFlow">
<http:listener config-ref="HTTP_Quickbook" path="/" doc:name="HTTP"/>
<connector-test:my-processor config-ref="ConnectorTest__Configuration_type_strategy" content="APP" doc:name="ConnectorTest"/>
<json:json-to-xml-transformer mimeType="application/json" doc:name="JSON to XML"/>
<logger message="#[payload]" level="INFO" doc:name="Logger"/>
</flow>
I need whole json string in xml format . What I have to change?
I tested with creating custom transformer.. My custom transformer is as follow
InputStream input = new FileInputStream(new File("test.json"));
OutputStream output = System.out;
XMLInputFactory inputFactory = XMLInputFactory.newInstance();
inputFactory.setProperty(XMLInputFactory.IS_COALESCING, true);
XMLEventReader reader = inputFactory.createXMLEventReader(input);
XMLOutputFactory outputFactory = new JsonXMLOutputFactory();
outputFactory.setProperty(JsonXMLOutputFactory.PROP_PRETTY_PRINT, true);
XMLEventWriter writer = outputFactory.createXMLEventWriter(output);
writer = new XMLMultipleEventWriter(writer, false,"/orders");
writer.add(reader);
reader.close();
writer.close();
Now I got following error
com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character '{' (code 123) in prolog; expected '<'
at [row,col {unknown-source}]: [1,1]
How to solve this?
i can suggest you one thing to make your requirement to work.
if you are want to generate the xml from json.
do the below
add set-payload component to you flow explicitly specify the value as
{"root":#[payload]}
it will work and i could able to generate the xml data out of the sample data you pasted.
please do try and post your status
Your issue comes from the transformation of the array of orders that fails and returns only one entry.
Behind the scene, the json:json-to-xml-transformer uses Staxon for XML to JSON transformation. Here is the doc on array transformation in Staxon: https://github.com/beckchr/staxon/wiki/Mastering-Arrays
We can see in the Mule source that PROP_MULTIPLE_PI is false and PROP_AUTO_ARRAY is not set to true, therefore only one item of the array is considered, the others are dropped.
So the best is for you to write your own transformer either using Staxon too, configured with the array handling settings you want, or using Groovy XML Markup builder to generate the XML in a nice and easy way.
For what you're looking for json
{
"ocs:price": {
"#exponent": "-1",
"#text": "2"
}
}
That's the format to convert for example that from JSON to XML like this :
<ocs:price exponent="-1">2</ocs:price>
In java/groovy script I used something like this to convert it
import net.sf.json.JSON
import net.sf.json.JSONSerializer
import net.sf.json.xml.XMLSerializer
String str = '''{
"ocs:price": {
"#exponent": "-1",
"#text": "2"
}
}'''
JSON json = JSONSerializer.toJSON( str )
XMLSerializer xmlSerializer = new XMLSerializer()
xmlSerializer.setTypeHintsCompatibility( false )
String xml = xmlSerializer.write( json )
System.out.println(xml)

Aggregating files for the second time not working in Mule 3.2

I have upgraded from mule 3.2.1 to 3.3.1 and I am facing following problem.
I am using the custom Aggregator and custom transformer for aggregating multiple files for processing. The problem is that after first time aggregation when I am dropping another set of files, only transformer class is called and no other component is getting execute. There is no error on console either.
When I restart the mule, again the process is executing for the first time only.
My transformer class is :
public class FileCorrelationTransformer extends AbstractMessageTransformer {
#Override
public Object transformMessage(MuleMessage message, String arg1)
throws TransformerException {
System.out.println("FileCorelationTransformer called");
System.out.println("=============================");
System.out.println("Extract Name = "
+ message.getOutboundProperty("originalFilename"));
final String fileName = message.getOutboundProperty("filename");
final String extractName = Generate.extractName(fileName);
final ArrayList<ExtractVO> extractVOs = ExtractDAO.getInstance()
.load(extractName);
System.out.println("Group Size: " + extractVOs.get(0).getNoOfParts());
System.out.println("=============================");
message.setCorrelationId(extractName);
message.setCorrelationGroupSize(extractVOs.get(0).getNoOfParts());
return message;
}
}
My aggregator class is :
public class FileCollectionAggregator extends AbstractAggregator {
#Override
protected EventCorrelatorCallback getCorrelatorCallback(
MuleContext muleContext) {
return new CollectionCorrelatorCallback(muleContext, false, "") {
#Override
public MuleEvent aggregateEvents(EventGroup events)
throws AggregationException {
CopyOnWriteArrayList<File> list = new CopyOnWriteArrayList<File>();
try {
for (Iterator<MuleEvent> iterator = events.iterator(); iterator
.hasNext();) {
MuleEvent event = iterator.next();
try {
list.add(new File(event.transformMessageToString()));
} catch (TransformerException e) {
throw new AggregationException(events, null, e);
}
}
} catch (ObjectStoreException e) {
throw new AggregationException(events, null, e);
}
return new DefaultMuleEvent(new DefaultMuleMessage(list,
muleContext), events.getMessageCollectionEvent());
}
};
}
}
My mule flow is :
<vm:connector name="ConnectorSinglePartMonthly"
dynamicNotification="true" doc:name="VM" />
<custom-transformer
class="com.transformers.FileCorrelationTransformer" name="fileMultiPartCorrelationTransformer"
doc:name="Java" />
<flow name="CountTP" doc:name="CountTP">
<file:inbound-endpoint path="c:/test/input"
connector-ref="fileConnector" doc:name="Count CSV">
<file:filename-regex-filter pattern="UGJERT[0-9]*.csv"
caseSensitive="false" />
</file:inbound-endpoint>
<vm:outbound-endpoint connector-ref="ConnectorMultiPartMonthly"
path="monthlyMultiPartFiles" transformer-refs="fileMultiPartCorrelationTransformer"
doc:name="TP Multi VM Out" />
<logger message="Done with the Flow!" doc:name="Logger" />
</flow>
<flow name="MultiPartMonthlyFlow" doc:name="MultiPartMonthlyFlow">
<vm:inbound-endpoint connector-ref="ConnectorMultiPartMonthly"
path="monthlyMultiPartFiles" doc:name="TP Multi Monthly VM In" />
<custom-aggregator class="com.routing.FileCollectionAggregator"
doc:name="Custom Aggregator" />
<component doc:name="Csv Reader">
<prototype-object
class="com.utility.CsvReader" />
</component>
</flow>
My console Output is :
After First Execution
INFO 2013-07-19 16:51:02,296 [[test].fileConnector.receiver.05] org.mule.transport.file.FileMessageReceiver: Lock obtained on file: C:\test\input\UGJERT12.csv
INFO 2013-07-19 16:51:02,311 [[test].fileConnector.receiver.05] org.mule.transport.file.FileMessageReceiver: Lock obtained on file: C:\test\input\UGJERT22.csv
FileCorelationTransformer called
FileCorelationTransformer called
=============================
=============================
Extract Name = UGJERT12.csv
Extract Name = UGJERT22.csv
Group Size: 2
=============================
Group Size: 2
=============================
INFO 2013-07-19 16:51:02,421 [[test].ConnectorMultiPartMonthly.dispatcher.01] org.mule.lifecycle.AbstractLifecycleManager: Initialising: 'ConnectorMultiPartMonthly.dispatcher.6508195'. Object is: VMMessageDispatcher
INFO 2013-07-19 16:51:02,421 [[test].ConnectorMultiPartMonthly.dispatcher.02] org.mule.lifecycle.AbstractLifecycleManager: Initialising: 'ConnectorMultiPartMonthly.dispatcher.29350820'. Object is: VMMessageDispatcher
INFO 2013-07-19 16:51:02,421 [[test].ConnectorMultiPartMonthly.dispatcher.01] org.mule.lifecycle.AbstractLifecycleManager: Starting: 'ConnectorMultiPartMonthly.dispatcher.6508195'. Object is: VMMessageDispatcher
INFO 2013-07-19 16:51:02,421 [[test].ConnectorMultiPartMonthly.dispatcher.02] org.mule.lifecycle.AbstractLifecycleManager: Starting: 'ConnectorMultiPartMonthly.dispatcher.29350820'. Object is: VMMessageDispatcher
Process execution starts and extractName is = UGJERT
After Second Execution
INFO 2013-07-19 16:53:18,530 [[test].fileConnector.receiver.09] org.mule.transport.file.FileMessageReceiver: Lock obtained on file: C:\test\input\UGJERT12.csv
INFO 2013-07-19 16:53:18,546 [[test].fileConnector.receiver.09] org.mule.transport.file.FileMessageReceiver: Lock obtained on file: C:\test\input\UGJERT22.csv
FileCorelationTransformer called
=============================
Extract Name = UGJERT12.csv
Group Size: 2
=============================
FileCorelationTransformer called
=============================
Extract Name = UGJERT22.csv
Group Size: 2
=============================
For every set of files, the correlation id is the name of the file which is unique across different set of files. Cannot anyone tell me what Is wrong with the code.
You can't reuse a correlation id because that's kept on the object store (that explains why it works again if you restart Mule).
You have to use a different correlation id for each of the aggregation groups.