Enterprise Resource Planning Blogs by SAP
Get insights and updates about cloud ERP and RISE with SAP, SAP S/4HANA and SAP S/4HANA Cloud, and more enterprise management capabilities with SAP blog posts.
cancel
Showing results for 
Search instead for 
Did you mean: 
hoang_vu3
Employee
Employee

Overview


I would like to share with you my first impression and experience using Groovy and XSLT Mapping on SAP CPI. I usually tend to use the graphical mapping, but it was time for me to check out other mapping options 🙂

This is my very first impression and first time using it, so the mapping itself is very basic. But if you are a fresh beginner like me, this blog might help you getting into this topic.

In my scenario I intended to integrate product master data into S/4HANA Cloud via CPI. This is a scenario which is covered with scope item 1RO, but I am putting a twist on it using the OData API instead of the SOAP APIs.

Steps in S/4HANA Cloud


I wanted to use the Product Master (A2X) API. To activate the OData API, we have to activate communication arrrangement Product Integration (SAP_COM_0009). This can be performed by creating a communication user, a communication system and the mentioned communication arrangement. If you need further information on how to activate the API, you can check out this blog.

Once the API is activated, we need to figure out, which information is required to create a product. For that we log on to the S/4HANA Cloud system and access the app Manage Product Master Data.

In this app we are able to create a new product master data record and we see that following fields are required to create a product:

  • Product Type

  • Base Unit

  • Description


So I tried to create the most basic product ever using only these fields including the field Product Number.


And this is how the product looks like once I created it:


In my case the description is set to English per default.

Now I have a basic understanding of the mandatory fields. Addtionally I wanted to use multiple descriptions of a product in different languages. So when I want to create a product via OData API, the payload needs to look like this:
{
"Product":"TIRE002",
"ProductType":"FERT",
"BaseUnit":"PC",
"to_Description": {
"results": [
{
"Product": "TIRE002",
"Language": "EN",
"ProductDescription": "Tire"
},
{
"Product": "TIRE002",
"Language": "DE",
"ProductDescription": "Reifen"
},
{
"Product": "TIRE002",
"Language": "ES",
"ProductDescription": "Neumatico"
}
]
}
}

I was able to create another product via Postman by fetching the X-CSRF-Token and pushing this payload to the respective endpoint. The endpoint should have following format:

https://myxxxxxx-api.s4hana.ondemand.com/sap/opu/odata/sap/API_PRODUCT_SRV/A_Product

When I check the app Manage Product Master Data in S/4HANA Cloud, I can see my newly created product:



CPI Configuration


Now that I knew how to work with the API, I wanted to create the most basic integration flow including a small mapping step to convert a MATMAS05 IDOC to the OData API structure. For this I created an integration flow with following configuration:


Sender SOAP channel

  • Address = /product

  • Service definition = manual

  • Message Exchange Pattern = one-way

  • Authorization = User Role

  • User Role = ESBMessaging.send


Mapping step to transform my IDOC message to the OData API structure. I wanted to try out both groovy and XSLT options. The coding for these mapping options can be found below.

Groovy Script to capture the payload as an attachment:
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
def messageLog = messageLogFactory.getMessageLog(message);
def bodyAsString = message.getBody(String.class);
messageLog.addAttachmentAsString("Payload after Mapping", bodyAsString, "text/xml");
return message;
}

Request Reply to handle the OData call

OData receiver adapter

Source message


Let's start with the incoming message. The message that I was using looked like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" >
<soapenv:Header/>
<soapenv:Body>
<Z1MATMAS05>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
</EDI_DC40>
<E1MARAM SEGMENT="1">
<MATNR>TIRE003</MATNR>
<MTART>FERT</MTART>
<MEINS>PC</MEINS>
<E1MAKTM SEGMENT="1">
<MAKTX>Reifen</MAKTX>
<SPRAS_ISO>DE</SPRAS_ISO>
</E1MAKTM>
<E1MAKTM SEGMENT="1">
<MAKTX>Tire</MAKTX>
<SPRAS_ISO>EN</SPRAS_ISO>
</E1MAKTM>
<E1MAKTM SEGMENT="1">
<MAKTX>Neumatico</MAKTX>
<SPRAS_ISO>ES</SPRAS_ISO>
</E1MAKTM>
</E1MARAM>
</IDOC>
</Z1MATMAS05>
</soapenv:Body>
</soapenv:Envelope>

Groovy


I wanted to try out groovy mapping first. I have never done this before, but after reading Eng See's great blog, I tried to do the same thing and use his groovy script as my basis.

After some testing, following groovy script performed the mapping for me:
import com.sap.gateway.ip.core.customdev.util.Message
import groovy.xml.MarkupBuilder

Message processData(Message message) {
// Access message body and properties
Reader reader = message.getBody(Reader)

// Define XML parser and builder
def Z1MATMAS05 = new XmlSlurper().parse(reader)
def writer = new StringWriter()
def builder = new MarkupBuilder(writer)

// Define target payload mapping
builder.A_Product {
'A_ProductType' {
'Product'(Z1MATMAS05.IDOC.E1MARAM.MATNR)
'ProductType'(Z1MATMAS05.IDOC.E1MARAM.MTART)
'BaseUnit' (Z1MATMAS05.IDOC.E1MARAM.MEINS)
'to_Description'{
Z1MATMAS05.IDOC.E1MARAM.E1MAKTM.each {item->
'A_ProductDescriptionType'{
'Product' (Z1MATMAS05.IDOC.E1MARAM.MATNR)
'Language' (item.SPRAS_ISO)
'ProductDescription' (item.MAKTX)
}
}

}
}
}
// Generate output
message.setBody(writer.toString())
return message
}

XSLT


Now I wanted to try out XSLT for the first time. So I replaced the groovy script with an XSLT mapping. This is the code that I used to perform the exact same mapping:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<!-- TODO: Auto-generated template -->
<xsl:for-each select="/Z1MATMAS05/IDOC">
<A_Product>
<A_ProductType>
<Product> <xsl:value-of select="./E1MARAM/MATNR"/> </Product>
<ProductType> <xsl:value-of select="./E1MARAM/MTART"/> </ProductType>
<BaseUnit> <xsl:value-of select="./E1MARAM/MEINS"/></BaseUnit>
<to_Description>
<xsl:for-each select="./E1MARAM/E1MAKTM">
<A_ProductDescriptionType>
<Product> <xsl:value-of select="/Z1MATMAS05/IDOC/E1MARAM/MATNR"/> </Product>
<Language> <xsl:value-of select="./SPRAS_ISO"/> </Language>
<ProductDescription> <xsl:value-of select="./MAKTX"/> </ProductDescription>
</A_ProductDescriptionType>
</xsl:for-each>
</to_Description>
</A_ProductType>
</A_Product>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

Target message


With both mapping options I received following payload, exactly what I needed for my OData call 🙂
<A_Product>
<A_ProductType>
<Product>TIRE003</Product>
<ProductType>FERT</ProductType>
<BaseUnit>PC</BaseUnit>
<to_Description>
<A_ProductDescriptionType>
<Product>TIRE003</Product>
<Language>DE</Language>
<ProductDescription>Reifen</ProductDescription>
</A_ProductDescriptionType>
<A_ProductDescriptionType>
<Product>TIRE003</Product>
<Language>EN</Language>
<ProductDescription>Tire</ProductDescription>
</A_ProductDescriptionType>
<A_ProductDescriptionType>
<Product>TIRE003</Product>
<Language>ES</Language>
<ProductDescription>Neumatico</ProductDescription>
</A_ProductDescriptionType>
</to_Description>
</A_ProductType>
</A_Product>

Conclusion


All together it was really fun trying out new mapping options besides the graphical message mapping. Of course I could have met this mapping requirement just as easy with message mapping.

However, having the possibility to evaluate multiple mapping options to perform a highly complex mapping is for me a strong benefit of CPI. Like many of you I rarely code anything on a daily basis, but groovy and XSLT both seemed very intuitive for me to learn. I believe that all mapping options  have their place within CPI and I am excited to explore more!
3 Comments