How to Java code to generate a string from array to DataWeave 2? - mule

Am working on migrating a Mule 3 application to Mule 4.
We are using Java code to generate an Oracle database query
Here is the existing Java function:
public static String generateStringInClause(String tableAlias, String fieldName, List<String> keys) {
String inClause = "";
for (int i = 0; i < keys.size(); i++){
if (i==0) {
inClause += "('" + keys.get(i) + "'";
} else if ((i+1)%10==0) {
inClause += ",'" + keys.get(i) + "')";
} else if (i%10==0) {
inClause += " or "+tableAlias+"."+fieldName+" in ('" + keys.get(i) + "'";
} else if (i == keys.size()-1){
inClause += ",'" + keys.get(i) + "')";
} else {
inClause += ",'" + keys.get(i) + "'";
}
}
if (keys.size() % 10 == 1 || keys.size() == 1) {
inClause = inClause + ")";
}
return inClause;
}
Here are the actual outputs from the Java code when I pass a List of keys of different sizes:
* 21 = ('a0','a1','a2'...'a8','a9') or xyz.abc in ('a10','a11',...'a19') or xyz.abc in ('a20')
* 12 = ('a0','a1','a2','a3','a4','a5','a6','a7','a8','a9') or xyz.abc in ('a10','a11')
* 11 = ('a0','a1','a2','a3','a4','a5','a6','a7','a8','a9') or xyz.abc in ('a10')
*
* 10 = ('a0','a1','a2','a3','a4','a5','a6','a7','a8','a9')
* 09 = ('a0','a1','a2','a3','a4','a5','a6','a7','a8')
* 01 = ('a0')
Now In Mule 4 we do not want to use any Java methods / classes so need to convert this function into DataWeave
Here is what I have tried:
%dw 2.0
output application/java
var inClause = ""
var size = sizeOf(payload.keys)
fun test() = payload.keys map ((item, index) ->
if (index ==0) ( inClause ++ "('" ++ item ++ "'")
else if (mod((index+1),10)==0) ( "'" ++ item ++ "')")
else if (mod((index),10)==0) ( " or "++ payload.tableAlias ++ "." ++ payload.fieldName ++ " in ('" ++ item ++ "'")
else if (index == (size-1) ) ( "'" ++ item ++ "')")
else ("'" ++ item ++ "'")
)
var result = test() joinBy ","
var result1 = if((mod(size,10) == 1) or (size == 1)) (result ++ ")") else (result)
---
result1
This script works fine and generates the same result when I have a list up to 10 elements. It does not produce the same result as the Java method when I have > 10 elements in the list of keys.
Input to the DataWeave script:
{
"tableAlias": "xyz",
"fieldName": "abc",
"keys" : ["a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","a10"]
}
In the output there is an additional comma , before or which will cause SQL query to fail.
Actual output:
('a0','a1','a2','a3','a4','a5','a6','a7','a8','a9'), or xyz.abc in ('a10')

You are trying to migrate the logic from the Java method but you need to take into account that Java is an imperative language and DataWeave a functional language. A 1 to 1 migration may not work or be too difficult to maintain. For example the variable inClause does absolutely nothing in the DataWeave script. You can not accumulate a value over a loop like in Java. You should think of what you are trying to achieve as the output and then think on how to express that.
As an example, I choose first separate the keys into blocks of n. I only need a condition to identify the first block. I don't need to transform each value after I know if it is the first block or one of the rest. That way I don't need to concern myself with matching commas or parenthesis. Then I transform each block more naturally in my view. I added an auxiliary function to format the values in each block. I use a reduce() at the end to concatenate the string resulting of each block. The intention of the code should be more clear than in the Java code.
I encapsulated the inputs to the functions in parameters so it is more reusable and clean. The block size is also a parameter. Magic numbers in code are not a good practice.
%dw 2.0
output application/java
import * from dw::core::Arrays
fun quoteArray(a) = "(" ++ (a map ("'" ++ $ ++ "'") joinBy ",") ++ ")"
fun generateStringInClause(tableAlias, fieldName, keys, size) =
keys divideBy size
map ((item, index) ->
if (index == 0) quoteArray(item)
else (" or "++ tableAlias ++ "." ++ fieldName ++ " in " ++ quoteArray(item) )
)
reduce ((item, accumulator="") -> accumulator ++ item)
---
generateStringInClause(payload.tableAlias, payload.fieldName, payload.keys, 10)
Input:
{
"tableAlias": "xyz",
"fieldName": "abc",
"keys" : ["a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","a10","a11", "a12","a13","a14","a15","a16","a17","a18","a19","a20"]
}
Output:
('a0','a1','a2','a3','a4','a5','a6','a7','a8','a9') or xyz.abc in ('a10','a11','a12','a13','a14','a15','a16','a17','a18','a19') or xyz.abc in ('a20')

Here is what I came up with. It is similar to aled's version, with small modifications to make the code smaller
%dw 2.0
import divideBy from dw::core::Arrays
output application/java
fun quoteArray(a) = "(" ++ (a map ("'$'") joinBy ",") ++ ")"
fun generateInClause(tableAlias, fieldName, keys, size) =
keys divideBy size
map quoteArray($)
joinBy (" OR $(tableAlias).$(fieldName) IN ")
---
"(" ++ generateInClause(payload.tableAlias, payload.fieldName, payload.keys, 10) ++ ")"

Related

Array in payload is string, need to coerce to array type (DataWeave)

I have a payload I'm receiving from debezium/kafka as
"reasons": "[7,10,9]" with the array as a string.
I need to filter the array to extract when the item is 10 or 11. Since the array is actually a string in the payload I need to coerce it to an array to filter.
This is my current solution, but I feel there has to be a more efficient way:
%dw 2.0
output application/json
var data = payload.payload.after
var reasons = data.reasons replace "[" with "" replace "]" with "" splitBy "," filter ((num, numIndex) -> num != "10" and num != "11")
---
{
"dnsType": if (dnsType[0] == "11") "clinical" else if (dnsType[0] == "10") "non-clinical" else ""
}
If the string content is compatible with a JSON array then you can use the read() function to let DataWeave parse it for you.
Example read(data.reasons,"application/json")

How to Identify last element in an Array in Dataweave

i have a dataweave expression to create a custom String , but i do not want to append || to the last element of the input array . How can i achieve it ?
%dw 2.0
output application/json
var arr = ["ABC","XYZ"]
---
arr map((item,index) -> {
index: "custom ('" ++ item ++ "'); " ++ "||"
})
Would this help?
Script
%dw 2.0
output application/json
var arr = ["ABC","XYZ"]
---
arr map((item,index) -> {
index: if((index) < (sizeOf(arr)-1)) "custom ('" ++ item ++ "'); " ++ "||" else item
})
Output
[
{
"index": "custom ('ABC'); ||"
},
{
"index": "XYZ"
}
]
You could also try with the following:
%dw 2.0
output application/json
import * from dw::core::Arrays
var arr = ["ABC","XYZ"]
---
take(arr,(sizeOf(arr)-1)) map (
$ ++ "||" // do your manipulation to modify the custom entry
)
take is documented here
Out of curiosity, are you then taking this resulting array and joining it into a string? Your question seems to indicate the end goal of the function is returning a string. If you are, you can just map and then joinBy
%dw 2.0
output application/json
var arr = ["ABC","XYZ", "DKJL"]
---
arr map "custom($($));" joinBy " || "
Outputs
"custom(ABC); || custom(XYZ); || custom(DKJL);"
And if like in Salim's answer you shouldn't be wrapping the last value in custom, you could also take advantage of the default behavior of reduce:
%dw 2.0
output application/json
var arr = ["ABC","XYZ", "DKJL"]
---
arr[-1 to 0] reduce ("custom($($)); || " ++ $$)
Outputs
"custom(ABC); || custom(XYZ); || DKJL"

In Kotlin, how to check if the input is alphabetic only

In kotlin, how to check if the input is alphabetic only.
Input could be anything, a String, Int or Double etc.
For example
val input = readLine()
if(check) {
doSomeTask
}
else doSomethingElse
You can have a look here, there are a lot of examples.
for example you can check via
fun isLetters(string: String): Boolean {
return string.all { it.isLetter() }
}
A good answer for checking if a String is entirely alphabetical was given by #HakobHakobyan: String.all { it.isLetter() }.
I will borrow his solution to target a second aspect of your question, that is
Input could be anything, a string, int or double etc.
Here's another method that checks Any input type:
fun isAplhabetical(input: Any): Boolean {
when (input) {
// if the input is a String, check all the Chars of it
is String -> return input.all { it.isLetter() }
// if input is a Char, just check that single Char
is Char -> return input.isLetter()
// otherwise, input doesn't contain any Char
else -> return false
}
}
and it can be used in an example main() like this:
fun main() {
val a = "Some non-numerical input"
val b = "45"
val c = "Some numbers, like 1, 2, 3, 4 and so on"
val d: Int = 42
val e: Double = 42.42
val f: Float = 43.4333f
val g = "This appears as entirely alphabetical" // but contains whitespaces
val h = "ThisIsEntirelyAlphabetical"
println("[$a] is" + (if (isAplhabetical(a)) "" else " not") + " (entirely) alphabetical")
println("[$b] is" + (if (isAplhabetical(b)) "" else " not") + " (entirely) alphabetical")
println("[$c] is" + (if (isAplhabetical(c)) "" else " not") + " (entirely) alphabetical")
println("[$d] is" + (if (isAplhabetical(d)) "" else " not") + " (entirely) alphabetical")
println("[$e] is" + (if (isAplhabetical(e)) "" else " not") + " (entirely) alphabetical")
println("[$f] is" + (if (isAplhabetical(f)) "" else " not") + " (entirely) alphabetical")
println("[$g] is" + (if (isAplhabetical(g)) "" else " not") + " (entirely) alphabetical")
println("[$h] is" + (if (isAplhabetical(h)) "" else " not") + " (entirely) alphabetical")
}
The output is
[Some non-numerical input] is not (entirely) alphabetical
[45] is not (entirely) alphabetical
[Some numbers, like 1, 2, 3, 4 and so on] is not (entirely) alphabetical
[42] is not (entirely) alphabetical
[42.42] is not (entirely) alphabetical
[43.4333] is not (entirely) alphabetical
[This appears as entirely alphabetical] is not (entirely) alphabetical
[ThisIsEntirelyAlphabetical] is (entirely) alphabetical
Only the last String is entirely alphabetical.
You can use a regex with the alphabet range:
fun alphabetCheck(input: String): Boolean {
val regex = Regex("[a-zA-Z]+?")
return regex.matches(input)
}
First convert your input to string by using toString():
val str = input.toString()
val matchesAlphabet = alphabetCheck(str)
You can check the ascii value of a character as in the example:
fun main(args: Array) {
val c = 'a'
val ascii = c.toInt()
println("The ASCII value of $c is: $ascii")
}
If you look at the ascii table, you can see that alphabetic characters are the one between the values 65 and 90 for capital letters. For small letters you have the interval 97 - 122.
If you want to build an arbitrary lookup (say characters that fit an encoding like base 64) you can do this kind of thing too:
val acceptable = ('a'..'z').plus('A'..'Z').plus("+-/~".asIterable())
So that's using ranges as a quick way of defining a... range of characters, and using a string to easily specify some individual ones (and turning it into an Iterable<Char> so plus can add them to the list.
val Char.isAcceptable get() = this in acceptable
"ab+5%".filter(Char::isAcceptable).let { print("VIPs: $it")}
>>>> VIPs: ab+

How to build SELECT * WHERE using collection of conditions

I want to build a SELECT statement using a list of conditions that come from the query string of a REST api. I wrote this function, but maybe it is vulnerable to SQL injection. Can someone tell me if this is vulnerable how to fix it? Perhaps I should use some kind of SQLBuilder package? or is there a way to do it with just dotNet. I'm using dotNet 4.6.1
string BuildSelect(NameValueCollection query)
{
var result = "SELECT * FROM MYTABLE";
if (query.Count == 0) return result;
var logic = " WHERE ";
foreach (string key in query)
foreach (string v in query.GetValues(key))
{
result += logic + key + " = " + v;
logic = " AND ";
}
return result;
}
Yes it is vulnerable to SQL injection attack. You could build your query to use parameters instead (you are simply using an = check only).
Since you know the tablename, that means you also know what the columns (keys) can be. Thus, you could loop your columns, if the collection has that key then add it to the where as a parameterized statement BUT value part is NOT passed as a string, you parse it to the type it should be (or let the backend do the conversion and get error if cannot be converted). In pseudocode:
List<string> clauses = new List<string>();
var result = "SELECT * FROM MYTABLE";
foreach( var col in myTable.Columns )
{
if (query.ContainsKey(col.Name))
{
clauses.Add( $"{col.Name} = #{col.Name}";
string v = query[col.Name];
command.Parameters.Add( $"#{col.Name}", col.Type).Value = typeParse(v);
}
}
if (clauses.Any())
{
result += " WHERE " + string.Join( " AND ", clauses );
}
return result;
HTH

escape underscore in Web Sql

I am developing mobile application in which I have used WebSql as local database. Now I am creating a search functionality where I want to escape "_" when the user will search the records. I tried using the MS-SQL approach by passing it in square bracket "[ _ ]"
Below are my code example
if ($.trim($('#txtPolicy').val()).length > 0) {
policy = $.trim($('#txtPolicy').val());
if (policy.indexOf("_") >= 0)
policy = policy.replace(/_/g, "[_]");
query += " (";
var arrploicy = policy.split(',');
for (var j = 0; j < arrploicy.length; j++) {
query += " policy like ? or ";
arr[i] = "%" + arrploicy[j] + "%";
++i;
}
query = query.substring(0, query.length - 3);
query += ") ";
}
I have a records which has data as 1234_456789. But it does not return any records, probably because it might be considering it as string.
You can use parameterized query without requering uou to escape. It is more secure too.