Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Philip-Li
Associate
Associate
1,421

Introduction: Sample Java Mapping Scenario

In the part 1 of this blog series, we introduced the idea at the conceptual level.
In this part, we will try the idea with a sample Java mapping that maps flight booking data.

Note: This sample is a fictitious scenario, it's used for demo purpose only.

The diagram below depicts the data model and logic of the Java mapping in SAP Process Orchestration.

Mapping_Types.png

It implements the following mapping logic:

  • Determine the field "Preferred_Language" based on the field "Customer_Country"
    • If the preferred language is already provided, use the original value, no further logic for this field
    • Find the value from a mapping list, e.g., if the customer country is DE, then the preferred language is German
    • If no value is found from the mapping list, use the default language "English"
  • Determine the field "Discounted_Price" based on the fields "Flight_Date" and "Full_Price"
    • If the flight date is within a peak season (April 1 to October 31), no discount
    • If the flight date is NOT within a peak season, apply a 10% discount
  • Determine the field "Total_Price"
    • Sum up the field "Discounted_Price" of each flight, and assign the total value to the field "Total_Price"
  • Keep original values for other fields

Migration Process

Recap: The diagram below depicts the main steps of the solution.

Main_Steps.png

 Let us follow these steps one by one.

Preparation

Source Code of the Java Mapping

The Java mapping is a Java class that extends the AbstractTransformation abstract class.
Its source code is as below.

package com.sap.poc.java.mapper;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class BookingDataMapper_DOM extends AbstractTransformation {
    private Map<String, String> countryToLanguageMap;
    public void transform(TransformationInput transformationInput, TransformationOutput transformationOutput) 
    throws StreamTransformationException {
        InputStream inputstream = transformationInput.getInputPayload().getInputStream();
        OutputStream outputstream = transformationOutput.getOutputPayload().getOutputStream();
        mapBookingData(inputstream, outputstream);
    }
    public void mapBookingData(InputStream inputstream, OutputStream outputstream) {
        try {
            DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document inDoc = docBuilder.parse(inputstream);
            Document outDoc = docBuilder.newDocument();
            outDoc = inDoc;

            setPreferredLanguage(outDoc);
            calculateBookingPrice(outDoc);

            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(new DOMSource(outDoc), new StreamResult(outputstream));
        } catch (Exception e) {
            e.printStackTrace();
            this.getTrace().addDebugMessage(e.toString());
        }
    }
    private void setPreferredLanguage(Document outDoc) throws Exception {

        Element preferredLanguageElement = (Element) outDoc.getElementsByTagName("Preferred_Language").item(0);
        if (preferredLanguageElement.getTextContent().isEmpty()) {
            loadCountryToLanguageMap();
            // Find the Customer_Country element
            Element customerCountryElement = (Element) outDoc.getElementsByTagName("Customer_Country").item(0);

            // Get the customer country value
            String customerCountry = customerCountryElement.getTextContent().trim();

            // Find the corresponding language in the map
            String language = countryToLanguageMap.get(customerCountry);

            if (language != null) {
                // Update the Preferred_Language element with the found language
                preferredLanguageElement.setTextContent(language);
            } else {
                preferredLanguageElement.setTextContent("English");
            }
        }
    }

    private boolean isSlowSeason(String date) {
        int iData = Integer.parseInt(date.substring(4));
        int iStartDate = 401;
        int iEndDate = 1031;
        return !(iData >= iStartDate && iData <= iEndDate);
    }

    private void loadCountryToLanguageMap() throws Exception {
        countryToLanguageMap = new HashMap<>();
        countryToLanguageMap.put("DE", "German");
        countryToLanguageMap.put("CN", "Chinese");
    }
    private void calculateBookingPrice(Document outDoc) {
        NodeList flightsList = outDoc.getElementsByTagName("Flight");

        int totalDiscountedPrice = 0;

        // Loop through each "Flights" element
        for (int i = 0; i < flightsList.getLength(); i++) {
            Element flightElement = (Element) flightsList.item(i);
            Element flightDateElement = (Element) flightElement.getElementsByTagName("Flight_Date").item(0);
            Element fullPriceElement = (Element) flightElement.getElementsByTagName("Full_Price").item(0);
            Element discountedPriceElement = (Element) flightElement.getElementsByTagName("Discounted_Price").item(0);

            // Get the values
            String flightDate = flightDateElement.getTextContent();
            int fullPrice = Integer.parseInt(fullPriceElement.getTextContent());

            if (isSlowSeason(flightDate)) {
                // Calculate the discounted price as 90% of the full price
                int discountedPrice = (int) (fullPrice * 0.9);
                discountedPriceElement.setTextContent(String.valueOf(discountedPrice));
                totalDiscountedPrice += discountedPrice;
            } else {
                totalDiscountedPrice += fullPrice;
                discountedPriceElement.setTextContent(String.valueOf(fullPrice));
            }
        }

        // Update the Total_Price element with the sum of discounted prices
        Element totalPriceElement = (Element) outDoc.getElementsByTagName("Total_Price").item(0);
        totalPriceElement.setTextContent(String.valueOf(totalDiscountedPrice));
    }
}

Sample Input XML

The sample XML data contains two flights, the first flight is in the peak season, and the second one is not.
It will be sent to the integration flow in later chapters.

<ns0:Ext_FlightBooking_MT xmlns:ns0="http://www.aif.com">
    <Agency_ID>1001</Agency_ID>
    <Customer_ID>100</Customer_ID>
    <Customer_Name>SAP Manager</Customer_Name>
    <Customer_Country>DE</Customer_Country>
    <Preferred_Language></Preferred_Language>
    <Total_Price />
    <Flight>
        <Airport_From>FRA</Airport_From>
        <Airport_To>SHA</Airport_To>
        <Flight_Date>20231001</Flight_Date>
        <Flight_Number>LH770</Flight_Number>
        <Seat_Class>B</Seat_Class>
        <Full_Price>1000</Full_Price>
        <Discounted_Price />
    </Flight>
    <Flight>
        <Airport_From>SHA</Airport_From>
        <Airport_To>FRA</Airport_To>
        <Flight_Date>20231201</Flight_Date>
        <Flight_Number>LH780</Flight_Number>
        <Seat_Class>B</Seat_Class>
        <Full_Price>1000</Full_Price>
        <Discounted_Price />
    </Flight>
</ns0:Ext_FlightBooking_MT>

Sample Output XML

The sample output XML is produced by the Java mapping.
It will be used to compare with the XML produced by the Groovy script in later chapters.

<ns0:Ext_FlightBooking_MT xmlns:ns0="http://www.aif.com">
    <Agency_ID>1001</Agency_ID>
    <Customer_ID>100</Customer_ID>
    <Customer_Name>SAP Manager</Customer_Name>
    <Customer_Country>DE</Customer_Country>
    <Preferred_Language>German</Preferred_Language>
    <Total_Price>1900</Total_Price>
    <Flight>
        <Airport_From>FRA</Airport_From>
        <Airport_To>SHA</Airport_To>
        <Flight_Date>20231001</Flight_Date>
        <Flight_Number>LH770</Flight_Number>
        <Seat_Class>B</Seat_Class>
        <Full_Price>1000</Full_Price>
        <Discounted_Price>1000</Discounted_Price>
    </Flight>
    <Flight>
        <Airport_From>SHA</Airport_From>
        <Airport_To>FRA</Airport_To>
        <Flight_Date>20231201</Flight_Date>
        <Flight_Number>LH780</Flight_Number>
        <Seat_Class>B</Seat_Class>
        <Full_Price>1000</Full_Price>
        <Discounted_Price>900</Discounted_Price>
    </Flight>
</ns0:Ext_FlightBooking_MT>

Generation

Prompt

For this sample, we use the following prompt:

Please convert this SAP Process Orchestration Java mapping code to Cloud Integration Groovy script, 
fulfill following requirements:
   1. Use Groovy 2 syntax, must compatible with JDK 8
   2. Generate def Message processData(Message message), don't wrap it in any class
   3. Follow the code pattern below:  
       def inputstream = message.getBody(InputStream) // get input stream  
       def outputstream = new ByteArrayOutputStream() // create output stream  
       transform(inputstream, outputstream) // transform data  
       message.setBody(outputstream) // set the output payload
   4. Prefer DOM to process XML 
   5. Import necessary packages, classes and interfaces 
   6. Import com.sap.gateway.ip.core.customdev.util.Message 
   7. Run the generated code by yourself, make sure no syntax error
   8. Just provide code, don't say anything else

Note: In this sample prompt, we prefer DOM to process XML, just because DOM is also used by the original Java mapping.
In your scenario, DOM might not be the right choice, especially when the XML payload size is big.

In the context of generative AI, the prompt is crucial for obtaining accurate and useful results. You can also try a tailored prompt that fits your specific requirements.

Use Generative AI

We use OpenAI's ChatGPT-3.5 model and invoke it via the official website UI.

Combine the prompt and the Java mapping source code together, the complete input message should be as below:

Please convert this SAP Process Orchestration Java mapping code to Cloud Integration Groovy script, 
fulfill following requirements:
    1. Use Groovy 2 syntax, must compatible with JDK 8
    2. Generate def Message processData(Message message), don't wrap it in any class
    3. Follow the code pattern below:
        def inputstream = message.getBody(InputStream) // get input stream
        def outputstream = new ByteArrayOutputStream() // create output stream
        transform(inputstream, outputstream) // transform data
        message.setBody(outputstream) // set the output payload
    4. Prefer DOM to process XML 
    5. Import necessary packages, classes and interfaces 
    6. Import com.sap.gateway.ip.core.customdev.util.Message 
    7. Run the generated code by yourself, make sure no syntax error
    8. Just provide code, don't say anything else
 
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class BookingDataMapper_DOM extends AbstractTransformation {
    private Map<String, String> countryToLanguageMap;

    public void transform(TransformationInput transformationInput, TransformationOutput transformationOutput) throws StreamTransformationException {
        InputStream inputstream = transformationInput.getInputPayload().getInputStream();
        OutputStream outputstream = transformationOutput.getOutputPayload().getOutputStream();
        mapBookingData(inputstream, outputstream);
    }


    public void mapBookingData(InputStream inputstream, OutputStream outputstream) {
        try {
            DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            Document inDoc = docBuilder.parse(inputstream);
            Document outDoc = docBuilder.newDocument();
            outDoc = inDoc;

            setPreferredLanguage(outDoc);
            calculateBookingPrice(outDoc);

            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(new DOMSource(outDoc), new StreamResult(outputstream));
        } catch (Exception e) {
            e.printStackTrace();
            this.getTrace().addDebugMessage(e.toString());
        }
    }
    private void setPreferredLanguage(Document outDoc) throws Exception {

        Element preferredLanguageElement = (Element) outDoc.getElementsByTagName("Preferred_Language").item(0);
        if (preferredLanguageElement.getTextContent().isEmpty()) {
            loadCountryToLanguageMap();
            // Find the Customer_Country element
            Element customerCountryElement = (Element) outDoc.getElementsByTagName("Customer_Country").item(0);

            // Get the customer country value
            String customerCountry = customerCountryElement.getTextContent().trim();

            // Find the corresponding language in the map
            String language = countryToLanguageMap.get(customerCountry);

            if (language != null) {
                // Update the Preferred_Language element with the found language
                preferredLanguageElement.setTextContent(language);
            } else {
                preferredLanguageElement.setTextContent("English");
            }
        }
    }

    private boolean isSlowSeason(String date) {
        int iData = Integer.parseInt(date.substring(4));
        int iStartDate = 401;
        int iEndDate = 1031;
        return !(iData >= iStartDate && iData <= iEndDate);
    }

    private void loadCountryToLanguageMap() throws Exception {
        countryToLanguageMap = new HashMap<>();
        countryToLanguageMap.put("DE", "German");
        countryToLanguageMap.put("CN", "Chinese");
    }
    private void calculateBookingPrice(Document outDoc) {
        NodeList flightsList = outDoc.getElementsByTagName("Flight");

        int totalDiscountedPrice = 0;

        // Loop through each "Flights" element
        for (int i = 0; i < flightsList.getLength(); i++) {
            Element flightElement = (Element) flightsList.item(i);
            Element flightDateElement = (Element) flightElement.getElementsByTagName("Flight_Date").item(0);
            Element fullPriceElement = (Element) flightElement.getElementsByTagName("Full_Price").item(0);
            Element discountedPriceElement = (Element) flightElement.getElementsByTagName("Discounted_Price").item(0);

            // Get the values
            String flightDate = flightDateElement.getTextContent();
            int fullPrice = Integer.parseInt(fullPriceElement.getTextContent());

            if (isSlowSeason(flightDate)) {
                // Calculate the discounted price as 90% of the full price
                int discountedPrice = (int) (fullPrice * 0.9);
                discountedPriceElement.setTextContent(String.valueOf(discountedPrice));
                totalDiscountedPrice += discountedPrice;
            } else {
                totalDiscountedPrice += fullPrice;
                discountedPriceElement.setTextContent(String.valueOf(fullPrice));
            }
        }

        // Update the Total_Price element with the sum of discounted prices
        Element totalPriceElement = (Element) outDoc.getElementsByTagName("Total_Price").item(0);
        totalPriceElement.setTextContent(String.valueOf(totalDiscountedPrice));
    }
}

Submit the message, after a few seconds, ChatGPT will return code similar like below:

import com.sap.gateway.ip.core.customdev.util.Message
import org.w3c.dom.Document

import java.io.ByteArrayOutputStream
import java.io.InputStream
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.Transformer
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult

def Message processData(Message message) {
    def inputstream = message.getBody(InputStream) // get input stream
    def outputstream = new ByteArrayOutputStream() // create output stream
    mapBookingData(inputstream, outputstream) // transform data
    message.setBody(outputstream) // set the output payload
}

def mapBookingData(InputStream inputstream, ByteArrayOutputStream outputstream) {
    try {
        DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
        def inDoc = docBuilder.parse(inputstream)
        def outDoc = docBuilder.newDocument()
        outDoc = inDoc

        setPreferredLanguage(outDoc)
        calculateBookingPrice(outDoc)

        Transformer transformer = TransformerFactory.newInstance().newTransformer()
        transformer.transform(new DOMSource(outDoc), new StreamResult(outputstream))
    } catch (Exception e) {
        e.printStackTrace()
        this.getTrace().addDebugMessage(e.toString())
    }
}

def setPreferredLanguage(Document outDoc) throws Exception {
    def preferredLanguageElement = outDoc.getElementsByTagName("Preferred_Language").item(0)
    if (preferredLanguageElement.getTextContent().isEmpty()) {
        loadCountryToLanguageMap()

        def customerCountryElement = outDoc.getElementsByTagName("Customer_Country").item(0)
        def customerCountry = customerCountryElement.getTextContent().trim()
        def language = countryToLanguageMap.get(customerCountry)

        if (language != null) {
            preferredLanguageElement.setTextContent(language)
        } else {
            preferredLanguageElement.setTextContent("English")
        }
    }
}

def isSlowSeason(String date) {
    def iData = Integer.parseInt(date.substring(4))
    def iStartDate = 401
    def iEndDate = 1031
    !(iData >= iStartDate && iData <= iEndDate)
}

def loadCountryToLanguageMap() throws Exception {
    countryToLanguageMap = new HashMap<>()
    countryToLanguageMap.put("DE", "German")
    countryToLanguageMap.put("CN", "Chinese")
}

def calculateBookingPrice(Document outDoc) {
    def flightsList = outDoc.getElementsByTagName("Flight")
    def totalDiscountedPrice = 0

    for (def i = 0; i < flightsList.getLength(); i++) {
        def flightElement = flightsList.item(i)
        def flightDateElement = flightElement.getElementsByTagName("Flight_Date").item(0)
        def fullPriceElement = flightElement.getElementsByTagName("Full_Price").item(0)
        def discountedPriceElement = flightElement.getElementsByTagName("Discounted_Price").item(0)

        def flightDate = flightDateElement.getTextContent()
        def fullPrice = Integer.parseInt(fullPriceElement.getTextContent())

        if (isSlowSeason(flightDate)) {
            def discountedPrice = (int) (fullPrice * 0.9)
            discountedPriceElement.setTextContent(String.valueOf(discountedPrice))
            totalDiscountedPrice += discountedPrice
        } else {
            totalDiscountedPrice += fullPrice
            discountedPriceElement.setTextContent(String.valueOf(fullPrice))
        }
    }

    def totalPriceElement = outDoc.getElementsByTagName("Total_Price").item(0)
    totalPriceElement.setTextContent(String.valueOf(totalDiscountedPrice))
}

Note: Don't expect you will get the exact same code as above.
Usually, you will get (slightly) different code every time. And it is very possible that the code you get has syntax issues. You can copy the code into your IDE to check the syntax issues.

The most common issues could be:

  • The org.w3c.dom.Document class is not imported, you need to add the import statement manually
  • Other used classes not imported, you need to check and import the missing classes manually

Alternatively, OpenAI also provides rest API and even programming libraries, you can also programmatically invoke it.

Evaluation

Create Testing Integration Flow

After verifying the syntax of the generated Groovy script, we will run it in a testing integration flow, which contains the following steps:

  • a HTTPS sender adapter
  • a Groovy script to log the input XML payload
    • Sample code:
    import com.sap.gateway.ip.core.customdev.util.Message;
    import java.util.HashMap;
    
    def Message processData(Message message) {
        def body = message.getBody(java.lang.String);
        def messageLog = messageLogFactory.getMessageLog(message);
        messageLog.addAttachmentAsString("In_Payload", body, "text/plain");
            
        return message;
    }
  • a Groovy script that contains the generated Groovy code
  • a Groovy script to log the output XML payload (produced by the generated Groovy code)
    • Sample code:
    import com.sap.gateway.ip.core.customdev.util.Message;
    import java.util.HashMap;
    
    def Message processData(Message message) {
        def body = message.getBody(java.lang.String);
        def messageLog = messageLogFactory.getMessageLog(message);
        messageLog.addAttachmentAsString("Out_Payload", body, "text/plain");
            
        return message;
    }

The diagram below depicts the main steps of the integration flow. 

iFlow.png

Copy the generated Groovy code to the middle Groovy script step, deploy the testing integration flow, we can get a URL to receive new message.

Next, we send the input XML using an HTTP client tool such as Postman or Insomnia.

<ns0:Ext_FlightBooking_MT
	xmlns:ns0="http://www.aif.com">
	<Agency_ID>1001</Agency_ID>
	<Customer_ID>100</Customer_ID>
	<Customer_Name>SAP Manager</Customer_Name>
	<Customer_Country>DE</Customer_Country>
	<Preferred_Language></Preferred_Language>
	<Total_Price/>
	<Flight>
		<Airport_From>FRA</Airport_From>
		<Airport_To>SHA</Airport_To>
		<Flight_Date>20231001</Flight_Date>
		<Flight_Number>LH770</Flight_Number>
		<Seat_Class>B</Seat_Class>
		<Full_Price>1000</Full_Price>
		<Discounted_Price/>
	</Flight>
	<Flight>
		<Airport_From>SHA</Airport_From>
		<Airport_To>FRA</Airport_To>
		<Flight_Date>20231201</Flight_Date>
		<Flight_Number>LH780</Flight_Number>
		<Seat_Class>B</Seat_Class>
		<Full_Price>1000</Full_Price>
		<Discounted_Price/>
	</Flight>
</ns0:Ext_FlightBooking_MT>

Note: To send a message to the HTTP sender adapter, you need to generate a service key and obtain the authorization token.
How to create the service key and get the token is out of scope of this blog.

Alternatively, you can use the simulation feature to test the Groovy script step before you deploy the integration flow.

Verify Output

After sending the new message to the integration flow, we can check the message status in the monitor of Integration Suite.
We can see both input and output payload have been saved as attachments.
Check the content of the output payload, it should be the same as the XML produced by Java mapping.

Monitor.png

 

Note: It is not uncommon that the messages run into error status.

Sometimes, it complains syntax-related issues, sometimes it complains logical issues.
In any case, you can iterate the process. I.e., copy the error message, send it to ChatGPT (in the same session you generate the code), ask it to fix the issues, and then do the evaluation again.
Of course, you can also check the code and fix the issues by yourself.

Disclaimer

Generative AI and Large Language Model (LLM)

In the blog, we use the terms generative AI and LLM interchangeably.
In theory, the idea is LLM-independent, but at the time of this blog, we only tried the idea with OpenAI's GPT-3.5 and GPT-4 models.

Security

When you invoke the generative AI, you have to send your source code to the AI provider. This implies the business logic is completely exposed to the AI provider, this can lead to security/IP-related issues if the AI provider is not reliable.
So please always keep this in mind, and only use the AI provider you TRUST.

Support

This idea described in the blog is not an SAP officially released feature, SAP will not formally support any implementation of this idea.

AI Limitation

The AI-generated content is not predictable, you may get a different response every time even you feed it with the same input, and there is no chance to debug why the response is different.

Generative AI is incredibly good at generating code, but AI-generated code is not always correct, sometimes the generated code has syntax issues, sometimes it has logical issues.
So you cannot use the generated code for production use directly.

Depending on the size of your Java mapping code, the AI token consumption could go beyond the token limitation, this cloud lead to higher costs, and even worse, a low-quality code (the generated code might be truncated due to token limitation).

Suitable Scenario

The most suitable scenario for this idea is: The Java mapping does not have too many dependencies.

If the Java mapping class (which implements the Java Mapping API) itself has too many dependency Java classes, you will have to do the generation not only for the main Java mapping class, but also for its dependency Java classes as well.

This will increase not only the cost of AI token consumption but also the complexity of code integration (combining different codes together).