Skip to Content
Technical Articles
Author's profile photo Hoang Vu

Product Integration with Groovy/XSLT Mapping

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

  • Address = https://myxxxxxx-api.s4hana.ondemand.com/sap/opu/odata/sap/API_PRODUCT_SRV
  • Proxy Type = Internet
  • Authentication = Basic
  • Credential Name = my technical user which refers to the communication user in S/4HANA Cloud
  • CSRF Protected = ticked
  • Operation Details = Create (POST)
  • Resource Path = A_Product
  • Fields = all fields mentioned in the payload above

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!

Assigned tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jens Neumann
      Jens Neumann

      Once again an entertaining and useful blog! I like using xslt myself but now I'm also eager to try out the groovy mapping option.

      Author's profile photo Állan Cunha
      Állan Cunha

      Thanks for sharing, very interesting and informative!

      Does anyone know a way to call a Groovy or JS custom script from within XSLT Mapping?

      Author's profile photo Saurabh Kabra
      Saurabh Kabra

      Excellent write-up Hoang. I was always wondering where to start with XSLT mapping but this really gave me the first step to work with XSLT.

      Thank you and keep writing.

      BR
      Saurabh