Skip to Content
Technical Articles
Author's profile photo vinay mittal

SAP CI – User Defined Search Made Easy and Automated

Introduction:

 

We often come across this very strong desire plagued by our laziness that I cannot log payloads in SAP CI but what if I was not lazy enough to implement User defined Message Search on the inbound XML for 2-3 fields in all my iFlows…..What If I could do it. Wouldn’t it make my life much easier.

But then the realization dawns on us that yes to do that we will have to first

  1. Create externalized properties in a content modifier with type as XPATH
  2. Enter the XPATH in the value for each field.
  3. Then individually add the xpath values to the message log as Custom headers.

Problem with this is that if you need to add a new user defined search criteria you need to modify the groovy script and the content modifier.

Imagine you are in productive environment and live and suddenly you observe there is an issue with an existing Integration, but you need to be able to see the values coming through on the run, following the steps 1 to 3 again in Dev and transporting to prod is not an option anymore.

Solution:

This can be easily solved with a two-step process.

 

Step 1: Add a Search Attribute list or the display header name in the Content modifier property section.

Step 2: Add a xpath list for those attributes in the content modifier property section, i.e. the xpath from where those attributes get called.

Content%20Modifier

Content Modifier

Step3: Add the header names you would like and the source XPATHS in the respective properties in a # separated fashion.

Configure%20section

Configure section

Step 4: Trust in groovy and let it do the rest.

 

This groovy script will parse through the Header names and the corresponding XPATHS and create Message Log Headers dynamically.

import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

import javax.xml.xpath.*
import javax.xml.parsers.DocumentBuilderFactory


def Message logKeys(Message message) {
    
def xml = message.getBody(java.lang.String) as String;
    
    def properties = message.getProperties();
    
    def search_attributes = properties.get("search_attributes");
    def attribute_xpaths = properties.get("attribute_xpaths");

    def messageLog = messageLogFactory.getMessageLog(message);

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


    if(search_attributes_array.length == attribute_xpaths_array.length )
    {
        def xpath = XPathFactory.newInstance().newXPath()
        def builder     = DocumentBuilderFactory.newInstance().newDocumentBuilder()
        def inputStream = new ByteArrayInputStream( xml.bytes )
        def records     = builder.parse(inputStream).documentElement
        
  
        for(int i=0; i<attribute_xpaths_array.length; i++)
        {
            String temp_value;
            
  
           temp_value = xpath.evaluate( attribute_xpaths_array[i], records )
            
            if(temp_value != null)
            {
                messageLog.addCustomHeaderProperty(search_attributes_array[i], temp_value);
            }

        }
    }

    return message;
}



 

Step 5:

Post your XML towards the iFLOW

<invoice>
      <invoiceHeader  InvoiceNumber="59864799">
        <dueDate>1st May 2023</dueDate>
        <companyCode>100</companyCode>
      </invoiceHeader>
</invoice>

 

Result:

We now have fully automated and configurable Custom Message Headers that we can search in message monitoring.

 

Message%20Monitoring

Message Monitoring

 

 

We already have a few blogs around this topic but none of them covers configurable custom Message headers hence this blog.

 

PS: Thank you Daniel Graversen, for suggesting the code improvements.

Assigned Tags

      6 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Michael John
      Michael John

      Hi vinay mittal - Excellent blog. As beginner, I want to know after adding parameters in the content modifier exchange section where would I go to maintain the values shown in the screenshot named "Configure section"? I want to know which step and screen is related to this. Please clarify.

      Author's profile photo vinay mittal
      vinay mittal
      Blog Post Author

      You can do it from here Michael

      Author's profile photo vasanthkumar sabapathi
      vasanthkumar sabapathi

      Good One Vinay...

      Author's profile photo Roberto Solotun
      Roberto Solotun

      Hi vinay mittal, excellent blog!!

      Said that, I've tried to use your code and succeed at it but with one small condition, the looked xpath needs to be only once in the entire xml otherwise it will log only the first occurrence. In normal integrations, this does not happens (as per my experience), we usually get several business objects per message (not talking about bulk but a few).

      In order to fix your code I've changed the evaluate a little bit.

      Please, find the new code below:

      import com.sap.gateway.ip.core.customdev.util.Message;
      import java.util.HashMap;

      import javax.xml.xpath.*;
      import javax.xml.parsers.DocumentBuilderFactory;

      def Message logKeys(Message message) {

      def xml = message.getBody(java.lang.String) as String;

      def properties = message.getProperties();

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

      def messageLog = messageLogFactory.getMessageLog(message);

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

      if(search_attributes_array.length == attribute_xpaths_array.length && search_attributes.size() > 0 && attribute_xpaths.size() > 0)
      {
      def xpath = XPathFactory.newInstance().newXPath()
      def builder = DocumentBuilderFactory.newInstance().newDocumentBuilder()
      def inputStream = new ByteArrayInputStream( xml.bytes )
      def records = builder.parse(inputStream).documentElement

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

      XPathExpression expr = xpath.compile( attribute_xpaths_array[i])
      def nodes = expr.evaluate(records, XPathConstants.NODESET);

      if(nodes != null)
      {
      for(int j = 0; j < nodes.getLength(); j++){
      messageLog.addCustomHeaderProperty(search_attributes_array[i], nodes.item(j).getTextContent());
      }
      }

      }
      }

      return message;
      }

       

      Highlighted my changes here.

      Also, I've added in the condition a validation just in case it is not configured for the interface. In this way, you can put it in all iflows without the need of worrying beforehand on how it should be configured. You place it and use it when you want it.

       

      Regards,

      Roberto

      Author's profile photo Roberto Solotun
      Roberto Solotun

      Even better version of the code above is the one below that handles the xpath namespaces if any:

      import com.sap.gateway.ip.core.customdev.util.Message;
      import java.util.HashMap;
      import javax.xml.xpath.*;
      import javax.xml.parsers.DocumentBuilderFactory;
      import javax.xml.namespace.NamespaceContext;
      import javax.xml.parsers.DocumentBuilder;
      import org.w3c.dom.Document;
      import javax.xml.XMLConstants;
      def Message processData(Message message) {
        def xml = message.getBody(java.lang.String) as String;
        def properties = message.getProperties();
        def search_attributes = properties.get("search_attributes");
        def attribute_xpaths = properties.get("attribute_xpaths");
        def messageLog = messageLogFactory.getMessageLog(message);
        String[] search_attributes_array = search_attributes.split("#");
        String[] attribute_xpaths_array = attribute_xpaths.split("#");
        if (search_attributes_array.length == attribute_xpaths_array.length &&
          search_attributes.size() > 0 && attribute_xpaths.size() > 0) {
          def xpath = XPathFactory.newInstance().newXPath()
          DocumentBuilderFactory fa = DocumentBuilderFactory.newInstance();
          fa.setNamespaceAware(true);
          DocumentBuilder builder = fa.newDocumentBuilder();
          def inputStream = new ByteArrayInputStream(xml.bytes)
          Document records = builder.parse(inputStream)
          xpath.setNamespaceContext(new NamespaceResolver(records))
        for (int i = 0; i < attribute_xpaths_array.length; i++) {
          XPathExpression expr = xpath.compile(attribute_xpaths_array[i])
          def nodes = expr.evaluate(records.documentElement, XPathConstants.NODESET);
          if (nodes != null) {
            for (int j = 0; j < nodes.getLength(); j++) {
              messageLog.addCustomHeaderProperty(search_attributes_array[i], nodes.item(j).getTextContent());
            }
          }
        }
      }
      return message;
      }
      class NamespaceResolver implements NamespaceContext {
        private Document sd;
        public NamespaceResolver(Document dmt) {
          sd = dmt;
        }
        public String getNamespaceURI(String prefix) {
          if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
            return sd.lookupNamespaceURI(null);
          } else {
            return sd.lookupNamespaceURI(prefix);
          }
        }
        public String getPrefix(String namespaceURI) {
          return sd.lookupPrefix(namespaceURI);
        }
        @SuppressWarnings("rawtypes")
        public Iterator getPrefixes(String namespaceURI) {
          return null;
        }
      }
      Author's profile photo vinay mittal
      vinay mittal
      Blog Post Author

      This is indeed Brilliant Roberto. Kudos