Introduction
Recently, I worked on a requirement to send data from S/4HANA to a third party system in a specific XML format. The target XML format does not use namespaces in its schema. Since the data is transferred out of S/4HANA via SOAP web service to CPI, a custom ABAP service consumer is generated from a custom WSDL, and that requires a namespace.
In CPI, I would have to perform some manipulation to the source payload, performing an identity transformation and additionally removing any namespace/prefix in the payload.
In PI, this would have been quite easily achieved by adding
XMLAnonymizerBean to a channel's module processor. However, there is no equivalent out-of-the-box functionality in CPI.
Despite being a self-professed XSLT
noob, XSLT seems like an obvious choice to handle such transformation.
So after spending some time scouring the internet forums for solutions, and clumsily testing the transformation using Notepad++'s XML Tools plugin (I don't have Altova or Oxygen XML!), I managed to achieve the requirement using the following XSLT code - hurray!
<?xml version="1.0"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" encoding="utf-8"/>
<!-- template to copy elements -->
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@* | node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Note: This is a simplified version that does not cater for comments or attributes in the XML payload.
What about Groovy?
You might be aware that I am more comfortable working with Groovy - so I gave myself a challenge to see if I could come up with an alternative solution based on Groovy. Ideally, it should solve not just my particular issue, which only removes namespace from the root node, but throughout the entire XML payload as well.
To achieve the requirement, it requires some form of recursive processing similar to XSLT template matching.
Core concepts
Let us look at some of the core concepts required to achieve the requirement in Groovy.
Parsing and updating XML payload
As I have mentioned before, working with XML is easy in Groovy. In particular, if we want to parse and update the XML payload at the same time,
groovy.util.XmlParser is a good choice. More details can be found in Scenario 2 of my blog post
Parse XML easily with Groovy scripts. The following lines show how to instantiate a parser and access the root node of the parsed XML payload.
XmlParser parser = new XmlParser()
Node root = parser.parse(message.getBody(Reader))
Determining nodes with namespace prefix
Once we can access the nodes of the XML payload, we will need to determine which nodes contain namespace prefix. This is achieved by checking if the object returned by the
name() method of
groovy.util.Node is an instance of
groovy.xml.QName.
def nodeName = node.name()
if (nodeName instanceof QName)
... do something ...
Recursive processing through the XML hierarchy
Lastly, the most critical portion is to process through all the nodes the XML hierarchy. This is achieved with a recursive algorithm.
In Groovy, such recursive algorithms can be achieved using
trampolines (
Groovy has the grooviest names, doesn't it!) Trampoline overcomes the limitation of the stack height in case a method recursively calls itself too deep. The calls are made serially therefore avoiding
StackOverflowException.
Following is an example of a recursive function defined as a Groovy closure, followed by an invocation of it.
// Define the recursive function as a closure
def recursiveFunction
recursiveFunction = {
... do something ...
recursiveFunction.trampoline()
... do something ...
}.trampoline()
// call the closure
recursiveFunction()
Putting it together
With all the components in place, let's put them together to see how it would look like in a CPI Groovy script. The steps in the script would be as follows:-
- Parse XML payload
- Process through XML payload starting from root node
- Recreate each node as a node without namespace prefix
- Repeat steps recursively for children of current node
- Serialize modified XML as output
import com.sap.gateway.ip.core.customdev.util.Message
import groovy.xml.QName
import groovy.xml.XmlUtil
Message processData(Message message) {
XmlParser parser = new XmlParser()
// Recursive closure to replace nodes with prefix
def replaceNodePrefix
replaceNodePrefix = { Node node ->
def nodeName = node.name()
// Replace current node
def localNodeName = (nodeName instanceof QName) ? nodeName.getLocalPart() : nodeName
Node outputNode = parser.createNode(null, localNodeName, null)
// Iterate and process the children of the node
node.children().each { child ->
switch (child){
case Node:
outputNode.append(replaceNodePrefix.trampoline(child).call())
break
case String:
outputNode.setValue(child)
break
}
}
return outputNode
}.trampoline()
Node root = parser.parse(message.getBody(Reader))
message.setBody(XmlUtil.serialize(replaceNodePrefix(root)))
return message
}
Conclusion
In the software world, there are more than one approach to fulfilling any particular requirement. Although the outcome of the Groovy script is not as compact as its XSLT counterpart, I was glad I spent the time on the challenge. I definitely learnt one or two things along the way, and hopefully it helped to hone my Groovy skills for further challenges to come
🙂