XPath expressions and Groovy script in HANA cloud integration
Since we started developing in HANA Cloud Integration we came to appreciate the integration pattern Content Modifier more and more.
It is a very powerful way of creating variables that can be used both inside (using properties) and outside the iFlows (using headers). These variables can contain a single value but also a complete xml message.
There are many ways in which the variables you want to use can be set. Just look at the list of types and you will see:
- Constant
- XPath
- Expression
- Property
- External parameter
- Local variable
- Global variable
One of the types we have used extensively during our developments is the XPath expression.
This is an easy way of reading the value of an element, e.g. /Order/LineItems/Number/text() returns the value/text in <Number> or for counting the number of occurrences of an element, e.g count(/Order/LineItems) results in the number of <LineItems> in a messages.
Using these XPath expressions in the Content Modifier the results can easily be stored in variables and used throughout the iFlows.
Some examples from real messages coming from Salesforce:
There are however limits to the usage of XPath expressions in the Content Modifier.
Case 1: XPath returning multiple values
In one of the iFlows we received a messages containing multiple UUID elements, but we only needed the UUIDs belonging to the element AddressInformation and not the UUIDs in the other parts of the message.
Take a look this message (it is part of the response from the querybusinesspartnerin webservice on ByDesign):
<n0:BusinessPartnerByIdentificationResponse_sync xmlns:n0="http://sap.com/xi/SAPGlobal20/Global" xmlns:prx="urn:sap.com:proxy:K7F:/1SAI/TAS37D7D57087649686562C:804">
<BusinessPartner>
<UUID>00163e06-fdd4-1ed5-a0bf-81adb3476f60</UUID>
<InternalID>3000009166</InternalID>
<CategoryCode>2</CategoryCode>
<CustomerIndicator>true</CustomerIndicator>
<SupplierIndicator>true</SupplierIndicator>
<LifeCycleStatusCode>2</LifeCycleStatusCode>
<Organisation>
<CompanyLegalFormCode>32</CompanyLegalFormCode>
<FirstLineName>Test Customer</FirstLineName>
</Organisation>
<AddressInformation>
<UUID>00163e06-fdd4-1ed5-a0bf-81adb347cf64</UUID>
<AddressUsage>
<AddressUsageCode>XXDEFAULT</AddressUsageCode>
</AddressUsage>
<Address>
…
</Address>
</AddressInformation>
<AddressInformation>
<UUID>00163e06-fdd4-1ed5-a0bf-81adb348af65</UUID>
<AddressUsage>
<AddressUsageCode>XXDEFAULT</AddressUsageCode>
</AddressUsage>
<Address>
…
</Address>
</AddressInformation>
</BusinessPartner>
</n0:BusinessPartnerByIdentificationResponse_sync>
As you can see there are three elements UUID, one directly underneath <BusinessPartner> and two underneath <AddressInformation>.
Retrieving the required values of the UUIDs is quite easy with an XPath expression, using the expression /n0:BusinessPartnerByIdentificationResponse_sync/BusinessPartner/AddressInformation/UUID/text()
will get result in:
Text=’00163e06-fdd4-1ed5-a0bf-81adb347cf64′
Text=’00163e06-fdd4-1ed5-a0bf-81adb348af65′
However, when you use this XPath in a header or property variable of the Content Modifier it will only put the first value, i.e. ‘00163e06-fdd4-1ed5-a0bf-81adb347cf64’, in the variable ignoring the second one completely.
We have discussed this with SAP and they confirmed that the Content Modifier works this way.
Since we needed to have both values we turned to Groovy scripting and after experimenting a bit we found that using XMLSlurper was the easiest way to get both values, it only took a couple of lines in Groovy:
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
def xml = message.getBody(String.class)
def completeXml= new XmlSlurper().parseText(xml)
def AddressUUIDs = completeXml.BusinessPartner.AddressInformation.'**'.findAll{ node-> node.name() == 'UUID' }*.text()
message.setProperty("AddressUUIDs", AddressUUIDs)
return message
}
As you can see in the script we use XMLSlurper to put the body of the message into the variable completeXML so we can then use the function findAll to find the UUIDs under the path we have specified, in this case completeXML.BusinessPartner.AddressInformation.
This way the values of UUID that are found are placed in one string separated by a comma:
[00163e06-fdd4-1ed5-a0bf-81adb347cf64, 00163e06-fdd4-1ed5-a0bf-81adb348af65].
This string is put into a property variable called AddressUUIDs and the message is returned.
This last statement is very important because without it the variable will not be set in the message.
Now we had both values in the property and we could query them later on our iFlow.
Case 2: Namespace challenges
We have also used XMLSlurper in a case where we received data from Salesforce and wanted to use an XPath expression but were unable to do so because Salesforce used a default namespace xmlns=urn:partner.soap.sforce.com.
At first we did not understand why a simple XPath expression was not working with this namespace until we tested it with an online XPath test tool.
We wanted to read the session ID from the following Salesforce login response using //sessionId:
<loginResponse xmlns="urn:partner.soap.sforce.com" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<result>
<metadataServerUrl>https://salesforce_url</metadataServerUrl>
<passwordExpired>false</passwordExpired>
<sandbox>true</sandbox>
<serverUrl>https://salesforce_url</serverUrl>
<sessionId>Hliushlasdh32847o31bfaefbB(AS9hfadoif</sessionId>
<userId>userId</userId>
<userInfo>
….
</userInfo>
</result>
</loginResponse>
The online tool gave the following error:
The default (no prefix) Namespace URI for XPath queries is always ” and it cannot be redefined to ‘urn:partner.soap.sforce.com’.
We realized that HCI was probably having the same problems with the namespace and therefore used the XMLSlurper again, which did not have this problem.
We hope that this small bit of groovy can help you too in your developments.
Frank Bakermans, YourIntegration
Martin Jaspers, Q for IT BV
Hello Jaspers,
I had a similar requirement with regards to the SFDC Session ID XPATH and had ended up writing a XSLT Mapping to anonymize the XML as I have illustrated in this blog of mine - HCI -Integrating SalesForce (SFDC) using HCI -Part 1
When I was trying to figure out why I could not get the XPATH to working, I was using the XMLTools Plugin of notepad++ which returned the valid output for my XPATH.
Can you let me know which online tool you had used to evaluate your XPATH so I can also use that for my future tests as obviously Notepad++ does not seem to be consistent to HCI!
Regards,
Bhavesh
Hi Bhavesh,
I always use Free Online XPath Tester / Evaluator - FreeFormatter.com for testing XPath expressions. The results are not always completely the same as in HCI, but at least it did give me the error with the namespace.
You have created a nice blog about SalesForce and HCI.
It is a pity that we cannot use dynamic url's in SOAP calls yet.
Regards,
Martin
Thank you for Sharing. It ended my endless troubleshooting why Xpath from SalesForce didn't work 🙂
Hello Jasper,
Thanks for the examples.
I’ve fully reproduced your code and test using your xml example.
I’ve got following error for the code line
So, it’s better to use:
It allows to avoid described exception.
I tried this one
def Message processData(Message message) {
def xml = message.getBody(String.class)
def completeXml= new XmlSlurper().parseText(xml)
String UserIds = completeXml.LeaveBalance.Details.'**'.findAll{ node-> node.name() == 'EmpID' }*.join(",")
// def UserIds = completeXml.LeaveBalance.Details.'**'.findAll{ node-> node.name() == 'EmpID' }*.text()
message.setProperty("filterClause", UserIds)
return message
}
org.apache.olingo.odata2.api.uri.UriSyntaxException: Invalid filter expression: 'userId eq []'., cause: org.apache.olingo.odata2.core.uri.expression.TokenizerException: Unknown character '[' at position '10' detected.
Values for user id are available in the payload as EmpID field.
I’m reading user ids and i will have to use in the query. Userids are returned as an array list and i converted to string based on the above syntax and I got the error.
Hello Jaspers,
I had a similar requirement with regards to populating multiple occurrences of the same field.So I have used this groovy script in my Iflow but I am not getting the multiple valuesets as intended.
Can you help me by showcasing how did you initialize this variable property that we are trying to set through the groovy script.
In my case I have declared it as type XPath in the Content Modifier step which is placed right after the groovy script. (Mapping -> Groovy Script -> Content Modifier)
But it seems that the script is bypassed & the value is taken directly from the concerned xpath of target xml after mapping.
Thanks in advance for your response.
Hello,
Sorry for my late reply... I hope your question is resolved.
If not, you cannot use the content modifier, but have to set the property using the a groovy script. This will fill the property with an array of values instead of one value.
Kind regards,
Martin
Did anyone ever find out why default namespaces are an issue in CPI?
I'm having the same issue trying to run a (somewhat) simple XPath on a SuccessFactors oData XML query response. I'm having similar issues to what you are describing as your 'Namespace challenges'. I don't believe I should need to strip the namespaces or use Groovy to get this to work.
Here's my question:
https://answers.sap.com/questions/13112620/how-to-use-xpath-with-default-namespace-in-cpi.html
I've found an acceptable workaround. If you create an alias prefix in the namespace mapping that matches the default namespace and then use it in your XPaths then they work. I've put it as an answer to my question linked above.
In my mind it isn't a perfect solution, but an acceptable workaround (which removing inbound namespaces or shelling out to Groovy really isn't).