
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.
It implements the following mapping logic:
Recap: The diagram below depicts the main steps of the solution.
Let us follow these steps one by one.
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)); } }
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>
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>
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.
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:
Alternatively, OpenAI also provides rest API and even programming libraries, you can also programmatically invoke it.
After verifying the syntax of the generated Groovy script, we will run it in a testing integration flow, which contains the following steps:
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; }
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.
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.
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.
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.
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.
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.
This idea described in the blog is not an SAP officially released feature, SAP will not formally support any implementation of this idea.
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).
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).
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
14 | |
13 | |
12 | |
9 | |
9 | |
6 | |
6 | |
6 | |
6 | |
5 |