In SAP Cloud Integration (CPI), we all use Filter palette item in our various B2B scenarios to validate/filter the incoming payload. One of the most common XPath expression we use is to check whether a particular node in the incoming payload has a particular value.
In this blog, we will use W3C XPath and XQuery functions to validate incoming payload.
What is the benefit of this?
In our integrations, we often have our first step after fetching payload to remove unnecessary data for further processing. One option for this is using Message mapping, but that increases the size of the interface and hence the time complexity as well. CPI has provided us with a standard palette item to achieve our goals.
Pre-requisites for trying this integration scenario:
- CPI Access.
- Basic knowledge of palette items in CPI.
Setting up the interface:
Suppose we are fetching a list of documents. We are getting the incoming payload as:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
<Document>
<DocID>10212</DocID>
<DocName>Passport</DocName>
</Document>
<Document>
<DocID>10214</DocID>
<DocName>Birth Certificate</DocName>
</Document>
<Document>
<DocID>10216</DocID>
<DocName>PAN</DocName>
</Document>
<Document>
<DocID>10212</DocID>
<DocName>Passport</DocName>
</Document>
<Document>
<DocID>1021</DocID>
<DocName>Driving License</DocName>
</Document>
</Root>
Now, our aim is to filter documents which have DocID as 10212 and 10214.
In many of the blogs found on SAP, you will see that they have set a property as DocIDList = ‘10212’,’10214’. This looks something like below:
After this, they will use a filter palette item where the XPath expression would be:
/Root/Document[contains($DocIDList,DocID)]
However, the drawback of using “contains” and such property is that if we get a DocID in our incoming payload as a substring of the DocIDList, those documents will pass as well. If we run the iflow now, and keep the payload like mentioned above, the DocID 1021 will pass as well. You can see the output below:
You can see it clearly in the output above that the DocID “1021” (which is a substring of 10212) also passed the filter. The reason for this is that when we use the function “contains”, it takes two arguments, and both are strings. Hence, our property is treated as a single string and NOT array of strings.
The function “contains” can be seen from the official documentation of W3 XPath functions.
https://www.w3.org/TR/xpath-functions-31/#func-contains
The snapshot from the website is:
So, how do we tackle this scenario? The answer is simple. We have another function named as index-of. This function takes inputs of any type.
According to the official website, here is the signature of the function index-of:
The link is: https://www.w3.org/TR/xpath-functions-31/#func-index-of
As you can see, we can set any type for the searching sequence as well as the element which needs to be searched. The return type of this function is integer which returns the index of the occurring element and it returns -1 in case the element does not exist.
This is the functionality which we are going to use in our case.
Now, we first need to store the array of strings in an ArrayList. We can do this via groovy script which is the second step of the iflow.
Here is the code snippet to set the elements of the list.
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.*;
def Message processData(Message message) {
def body = message.getBody();
List<String> list = new ArrayList<String>();
list.add("10212");
list.add("10216");
map = message.getProperties();
message.setProperty("DocIDList", list);
return message;
}
Note: To make this more dynamic, we can also set an externalized property and store the document IDs in a comma-separated manner. Then we can call that property in our script and dynamically create a list.
Once our ArrayList is set we will store it in a property and then we will put a filter palette item as our third step of iflow.
The XPath expression for it would be:
/Root/Document[index-of($DocIDList, DocID) ne -1]
This XPath expression will make sure that only those documents pass, who have DocID in the dynamically set DocIDList ArrayList.
Let us run the iflow now:
And Voila!
We only have the needed DocIDs.
Conclusion:
You might think that this can be achieved using (DocID/text() eq ‘10212’ or DocID/text() eq ‘10216’ ) as well, but the longer the list gets, the longer the XPath expression will become. The XPath function “contains” takes only strings as input arguments, and hence we cannot filter a string among an array of strings. To handle such scenario, we have to use the function “index-of” which takes any atomic type as arguments.