Integration Blog Posts
cancel
Showing results for 
Search instead for 
Did you mean: 

Introduction

If you are familiar with a previous blog I had written on Configurable Custom headers in SAP CI where you could just add more custom headers in SAP CI message just by configuring the iflow and without having to edit the iflow and then transport it you understand how important and useful this solution is.

https://community.sap.com/t5/technology-blog-posts-by-members/sap-ci-user-defined-search-made-easy-a...

This solution was however limited in the sense that it didn't allow you to log the payload in the same step, didn't work at all if the value to be logged in custom headers had to be derived from an expression, or if the value to be logged was actually a file path for instance and shouldn't be evaluated as an xpath, or the value was just a constant or if the value was the be derived from a gpath.

This advanced solution enables 360 degree message logging and Custom header logging for all these scenarios.

Solution 

This upgraded Script handles all such cases as we shortly see. - This script has been tested with payloads up to 10mb and has resulted in a processing time of 10-50milli seconds for the script step in my tenant. 

import com.sap.gateway.ip.core.customdev.util.Message
import javax.xml.xpath.XPathFactory
import javax.xml.xpath.XPathExpression
import javax.xml.xpath.XPathConstants
import javax.xml.parsers.DocumentBuilderFactory
import groovy.json.JsonSlurper
import java.io.InputStream
import java.io.ByteArrayInputStream

def Message processData(Message message) {

    InputStream bodyIS = message.getBody(InputStream)   // <-- InputStream
    def headers = message.getHeaders()
    def properties = message.getProperties()
    def messageLog = messageLogFactory.getMessageLog(message)

    def value = headers.get("SAPJMSRetries")
    int SAPJMSRetries = -1
    if (value != null) {
        SAPJMSRetries = Integer.parseInt((String) value)
    }

    String logger = properties.get("logger")
    String logPayloadFileName = properties.get("logPayloadFileName")

    // Payload logging (still needs String)
    if (logger == "1" && SAPJMSRetries == -1 && messageLog != null) {
        if (logPayloadFileName == null) {
            logPayloadFileName = "payload"
        }
        def payloadString = bodyIS.getText("UTF-8")
        messageLog.addAttachmentAsString(logPayloadFileName, payloadString, "text/plain")

        // Reset stream for further processing
        bodyIS = new ByteArrayInputStream(payloadString.bytes)
        message.setBody(bodyIS)
    }

    message = logKeys(message)
    return message
}

def Message logKeys(Message message) {

    InputStream bodyIS = message.getBody(InputStream)   // <-- InputStream
    def properties = message.getProperties()
    def messageLog = messageLogFactory.getMessageLog(message)

    def search_attributes = properties.get("search_attributes")
    def attribute_xpaths = properties.get("attribute_xpaths")

    if (!search_attributes || !attribute_xpaths) {
        return message
    }

    String[] search_attributes_array = search_attributes.split("#")
    String[] attribute_xpaths_array = attribute_xpaths.split("#")

    if (search_attributes_array.length != attribute_xpaths_array.length) {
        return message
    }

    String saveCustomHeadersAsAnAttachment = ""
    def xmlDocument = null
    def jsonObject = null

    for (int i = 0; i < attribute_xpaths_array.length; i++) {

        String temp_value = ""
        String attr = attribute_xpaths_array[i]

        // ---------- XPATH ----------
        if (attr.startsWith("XPATH:")) {

            try {
                if (xmlDocument == null) {
                    def builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                    xmlDocument = builder.parse(bodyIS)

                    // reset stream for next usage
                    bodyIS.reset()
                }

                def xpathExp = attr.substring(6)
                def xpath = XPathFactory.newInstance().newXPath()
                XPathExpression expr = xpath.compile(xpathExp)
                def nodes = expr.evaluate(xmlDocument, XPathConstants.NODESET)

                for (int j = 0; j < nodes.length; j++) {
                    temp_value += nodes.item(j).textContent + ","
                }

                if (temp_value.endsWith(",")) {
                    temp_value = temp_value[0..-2]
                }

            } catch (Exception e) {
                temp_value = attr
            }
        }

        // ---------- GPATH ----------
        else if (attr.startsWith("GPATH:")) {

            try {
                if (jsonObject == null) {
                    jsonObject = new JsonSlurper().parse(bodyIS)

                    // reset stream for next usage
                    bodyIS.reset()
                }

                def gpath = attr.substring(6)
                temp_value = Eval.me("json", jsonObject, "json.${gpath}")?.toString()

            } catch (Exception e) {
                messageLog?.addAttachmentAsString(
                        "Exception Encountered in Logging",
                        e.message,
                        "text/plain"
                )
            }
        }

        // ---------- CONSTANT ----------
        else {
            temp_value = attr
        }

        if (temp_value != null) {
            messageLog?.addCustomHeaderProperty(search_attributes_array[i], temp_value)
            saveCustomHeadersAsAnAttachment += "${search_attributes_array[i]} : ${temp_value}\n"
        }
    }

    if (saveCustomHeadersAsAnAttachment.length() > 200) {
        messageLog?.addAttachmentAsString(
                "FullTextOfTruncatedCustomHeaders",
                saveCustomHeadersAsAnAttachment,
                "text/plain"
        )
    }

    return message
}

 

Let's consider following XML sample as inbound XML payload

<Invoice>
  <InvHeader>
    <InvNumber>4385689</InvNumber>
    <CustName>Cust 1</CustName>
  </InvHeader>
  <InvLineItem>
    <Item>
      <ItemNum>100</ItemNum>
    </Item>
    <Item>
      <ItemNum>101</ItemNum>
    </Item>
    <Item>
      <ItemNum>102</ItemNum>
    </Item>
    <Item>
      <ItemNum>103</ItemNum>
    </Item>
  </InvLineItem>
</Invoice>

 

Iflow- 

You just need a content modifier with the logger parameters and the actual script after it.

Parameter / Properties format - 

logPayloadFileName - Give the file Name for the log attachment for saving the message as an attachment

logger - 1 : message will be logged as an attachment , 0 : Message wont be logged

search_attributes : Names of the search attributes / Custom headers separated by #

example : HTTPStatus#InvNumber#CustName#ItemNumbers

attribute_xpaths : Values of the custom headers specified above in same order separated by #, Keep data type as java.lang.String

example :

${header.CamelHttpResponseCode}#XPATH://InvNumber#XPATH://CustName#XPATH://InvLineItem/Item/ItemNum

iflow1.png

 

 

As you can see from the message log the Custom headers were created from the supplied expression, and XPATH's, if there were multiple xpath matches all those values were concatenated, separated by ",". If you only want only the first xpath matche value do specify [0] explicitly.

result 1 - headers.png

 

 A salient feature is that the allowed character limit for a header is 100 characters, In case this limit is exceeded the script will create an attachment as well for that particular header as the SAP CI custom header would display only 100 characters.

And here is the MPL attachment saved with your custom name

payload-2.png

 

Let's have a look at a JSON Payload as an input example

{
	"Invoice": {
		"InvHeader": {
			"InvNumber": 4385689,
			"CustName": "Cust 1"
		},
		"InvLineItem": {
			"Item": [
				{
					"ItemNum": 100
				},
				{
					"ItemNum": 101
				},
				{
					"ItemNum": 102
				},
				{
					"ItemNum": 103
				}
			]
		}
	}
}

search_attributes : 

HTTPStatus#InvNumber#CustName#ItemNumbers#ArchiveFilePath

attribute_xpaths

${header.CamelHttpResponseCode}#GPATH:Invoice.InvHeader.InvNumber#GPATH:Invoice.InvHeader.CustName#GPATH:Invoice.InvLineItem.Item*.ItemNum#/Folder1/Folder2/File1.txt

This should now add the custom headers in exactly same way as it added for the previous XML input and should also add the file path which starts with : so as to tell the script to not evaluate it as a XPATH, and should also add the expression. The GPATHS should start with "GPATH:"

iflow11.png

Result:
result23.png

 

One more important feature is that it checks SAPJMSRetries header and if this is used in a JMS flow this will only add MPL attachment 1 time. 

So use it in your iflows and do add the blog link in your script for helping operations team understand how this works.

don't forget to make the search_attributes and attributes_xpaths externalized parameters, that way you would never have to change the artifact and transport it if you need additional custom headers ever.

configparams.png


The combination of the content modifier which has the parameters and the groovy script step can be used as many times as you want in the integration flow , just remember to reset the values before the script step by making the xpath_attributes and search_attributes properties blank but configurable. also if you dont want logging externalize the logger property and set it to 0. this takes 5 minutes but will save you hours in future developments.  

 

9 Comments
Labels in this area