Thursday 21 January 2016

Dynamic Configuration Usages in SAP PI

Purpose


Purpose of this document is to provide the main usage of Dynamic Configuration along with the implementation details for each usage with Java Mapping in SAP PI. Same concept can be used in Graphical Mapping with use of UDFs.

Introduction


As the name suggests, the Dynamic Configuration in SAP PI is to set the attributes and properties at the runtime. The Dynamic Configuration object (com.sap.aii.mapping.api.DynamicConfiguration) is basically a map containing key value pair of adapter specific message attributes and custom attributes. It associates keys (DynamicConfigurationKeys) with string values.

Usages


Following are the main usages of Dynamic Configuration

1. To set Adapter Specific Message Properties
2. To create a common Java Mapping for Synchronous Scenario
3. To pass on the request parameter to response mapping in Synchronous Scenario

Implementation


The implementation detail of each usage is explained below:

1. To set Adapter Specific Message Properties


SAP PI adapters support specific message attributes, which contain additional information about messages. This information is not located in the payload of the message, but in additional message header fields. These parameters are very important for fulfilling the business requirements.

E.g. For File Receiver Adapter, the most common requirement is to create the file with dynamic file name and also sometime in a directory which needs to be decided at runtime. Dynamic Configuration provides the most powerful solution to this problem (Variable Substitution is another method, but it has some limitations).

Following is the code to set the file name and directory dynamically with use of Java Mapping/UDFs, check the comments in the code for the understanding:

package xyz.eai.common;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.DynamicConfiguration;
import com.sap.aii.mapping.api.DynamicConfigurationKey;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
public class PassThrough extends AbstractTransformation{
      // Dynamic Configuration Key for the target file name which will be used in Receiver Adapter
      private static final DynamicConfigurationKey KEY_FILENAME = DynamicConfigurationKey.create(“http://sap.com/xi/XI/System/File“, “FileName”);
      // Dynamic Configuration Key for the target Directory name which will be used in Receiver Adapter
      private static final DynamicConfigurationKey KEY_DIRECTORY = DynamicConfigurationKey.create(“http://sap.com/xi/XI/System/File“, “Directory”);
      public void transform(TransformationInput arg0, TransformationOutput arg1)
      throws StreamTransformationException {
        String strFileName = “”;
        String strDirectory = “”;
        String strIDocNbr = “”; // to be retrieved from input data
        String strDataType = “”; // to be derived based on the input data
        String strDateTime = (new SimpleDateFormat(“ddMMyyyyHHmmss”)).format(new Date());
            try {
                  // Retrieve Data from the input steam
              InputStream is = arg0.getInputPayload().getInputStream();
              String strInData = convertStreamToString(is);
              /*
               * Implement necessary Business Logic here
               */
              // Create Dynamic Configuration Object
              DynamicConfiguration conf = arg0.getDynamicConfiguration();
            strFileName = “TARGET”+strIDocNbr+“-“+strDataType+“-“+strDateTime+“.xml”;
            strDirectory = “/apps/data/”+strDataType+“/in”;
            // Put the Key and Value in the Dynamic Configuration Object
            conf.put(KEY_FILENAME, strFileName);
            conf.put(KEY_DIRECTORY, strDirectory);
            // Write the data in the output stream
arg1.getOutputPayload().getOutputStream().write(strInData.toString().getBytes(“UTF-8”));
            } catch (Exception e) {
            }
     
      }
      public String convertStreamToString(InputStream in) {
            StringBuffer sb = new StringBuffer();
            try {
                  InputStreamReader isr = new InputStreamReader(in);
                  Reader reader = new BufferedReader(isr);
                  int ch;
                  while ((ch = in.read()) > -1)
                        sb.append((char) ch);
                  reader.close();
            } catch (Exception exception) {
            }
            return sb.toString();
      }
}

2. To create a common Java Mapping for Synchronous Scenario


In case of Synchronous scenarios it is required to create two mappings – Request data mapping and Response data mapping. With help of Dynamic Configuration, you can create only one Java Class which will have mapping for both request data and response data. This reduces the developmentand maintenance efforts significantly.

The Key-Value set in Dynamic Configuration helps in identifying whether to map request data or response data. The basic thing is that the Key-Value set in Dynamic Configuration during request mapping becomes available even in the response mapping.

Below is the sample Java Class which can be selected as the Request Java Mapping and Response Java Mapping in the Operation Mapping in ESR. Check the code comments to understand the concept.

package xyz.eai.common;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.DynamicConfiguration;
import com.sap.aii.mapping.api.DynamicConfigurationKey;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
public class SynchronousMapping extends AbstractTransformation {
      // Dynamic Configuration Key for the identification whether the mapping is called for Request data or for Response Data
      private static final DynamicConfigurationKey KEY_STEP = DynamicConfigurationKey.create(“http://sap.com/xi/XI/System/Custom“, “Step”);
      @Override
      public void transform(TransformationInput arg0, TransformationOutput arg1)
                  throws StreamTransformationException {
            ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
            String strStep = “”;
            try {
                  getTrace().addInfo(“Starting the Processing”);
            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
              // Retrieve Dynamic Configuration Object from Sender and Receiver adapter
            DynamicConfiguration conf = arg0.getDynamicConfiguration();
            // Get the Custom Key which will be null for Request Mapping and “1” for Response Mapping
            strStep = conf.get(KEY_STEP);
            this.getTrace().addInfo(“KEY_STEP: “ + strStep);
            // Decide the mapping based on value of Custom Key
            if (strStep != null && strStep.equals(“1”)) {
                  // Sub Method for Response Data Mapping
                  transformResponse(arg0, arg1, conf);
            } else {
                  // Sub Method for Request Data Mapping
                  transformRequest(arg0, arg1, conf);
                  conf.put(KEY_STEP, “1”);
            }
            } catch (Exception e) {
                  this.getTrace().addInfo((new StringBuilder(“Exception: “)).append(e.getMessage()).toString(), e);
            } finally {
                  Thread.currentThread().setContextClassLoader(oldLoader);
            }
      }
      public void transformRequest(TransformationInput arg0, TransformationOutput arg1, DynamicConfiguration conf)
      throws Exception {
            // Retrieve Request Data from the input steam of Sender Adapter
        InputStream is = arg0.getInputPayload().getInputStream();
        String strInData = convertStreamToString(is);
        String strOutData = “”;
        /*
         * Do the Request Data Mapping here
         */
        // Send the data to Receiver Adapter
arg1.getOutputPayload().getOutputStream().write(strOutData.toString().getBytes(“UTF-8”));
arg1.getOutputPayload().getOutputStream().flush();
      }
      public void transformResponse(TransformationInput arg0, TransformationOutput arg1, DynamicConfiguration conf)
      throws Exception {
            // Retrieve Response Data from the input steam of Receiver Adapter
        InputStream is = arg0.getInputPayload().getInputStream();
        String strInData = convertStreamToString(is);
        String strOutData = “”;
        /*
         * Do the Response Data Mapping here
         */
        // Send the data to Sender Adapter
arg1.getOutputPayload().getOutputStream().write(strOutData.toString().getBytes(“UTF-8”));
        arg1.getOutputPayload().getOutputStream().flush();
      }

      public String convertStreamToString(InputStream in) {
            StringBuffer sb = new StringBuffer();
            try {
                  InputStreamReader isr = new InputStreamReader(in);
                  Reader reader = new BufferedReader(isr);
                  int ch;
                  while ((ch = in.read()) > -1)
                        sb.append((char) ch);
                  reader.close();
            } catch (Exception exception) {
            }
            return sb.toString();
      }
}

3. To pass on the request parameter to response mapping in Synchronous Scenario


Many times in the synchronous integration scenarios with RFC Adapter, SOAP Adapter or even JDBC Adapter at the receiver side, the adapter response does not send back all the data which are passed as a part of request. But the final response needs to the Sender adapter needs some data from the request data.

For example, customer id is passed in the RFC/SOAP request, but the response doesn’t have the customer id back. And the final response to sender adapter needs customer id along with other details.

The Dynamic Configuration becomes handy to send parameters from request mapping to response mapping. All the Key-Values which are set in Request Mapping become available in the Response Mapping. Here no need to do any module configuration or anything else.

Below are the sample Java Mapping which is similar to usage 2. Here the Customer Id is retrieved in request mapping and utilized in response mapping. Check the comments in the code for the understanding.

package xyz.eai.common;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import com.sap.aii.mapping.api.AbstractTransformation;
import com.sap.aii.mapping.api.DynamicConfiguration;
import com.sap.aii.mapping.api.DynamicConfigurationKey;
import com.sap.aii.mapping.api.StreamTransformationException;
import com.sap.aii.mapping.api.TransformationInput;
import com.sap.aii.mapping.api.TransformationOutput;
public class RequestResponseMapping extends AbstractTransformation {
      // Dynamic Configuration Key for the identification whether the mapping is called for Request data or for Response Data
      private static final DynamicConfigurationKey KEY_STEP = DynamicConfigurationKey.create(“http://sap.com/xi/XI/System/Custom“, “Step”);
      // Dynamic Configuration Key for the request data to be made available in response
      private static final DynamicConfigurationKey KEY_CUSTID = DynamicConfigurationKey.create(“http://sap.com/xi/XI/System/Custom“, “CustomerId”);
      @Override
      public void transform(TransformationInput arg0, TransformationOutput arg1)
                  throws StreamTransformationException {
            ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
            String strStep = “”;
            try {
                  getTrace().addInfo(“Starting the Processing”);
            Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
              // Retrieve Dynamic Configuration Object from Sender and Receiver adapter
            DynamicConfiguration conf = arg0.getDynamicConfiguration();
            // Get the Custom Key which will be null for Request Mapping and “1” for Response Mapping
            strStep = conf.get(KEY_STEP);
            this.getTrace().addInfo(“KEY_STEP: “ + strStep);
            // Decide the mapping based on value of Custom Key
            if (strStep != null && strStep.equals(“1”)) {
                  // Sub Method for Response Data Mapping
                  transformResponse(arg0, arg1, conf);
            } else {
                  // Sub Method for Request Data Mapping
                  transformRequest(arg0, arg1, conf);
                  conf.put(KEY_STEP, “1”);
            }
            } catch (Exception e) {
                  this.getTrace().addInfo((new StringBuilder(“Exception: “)).append(e.getMessage()).toString(), e);
            } finally {
                  Thread.currentThread().setContextClassLoader(oldLoader);
            }
      }
      public void transformRequest(TransformationInput arg0, TransformationOutput arg1, DynamicConfiguration conf)
      throws Exception {
            // Retrieve Request Data from the input steam of Sender Adapter
        InputStream is = arg0.getInputPayload().getInputStream();
        String strInData = convertStreamToString(is);
        String strCustId = “”; // To be retrieved from the request data
        String strOutData = “”;
        /*
         * Do the Request Data Mapping here
         */
        // Put the request parameter in the Dynamic Configuration
        conf.put(KEY_CUSTID, strCustId);
        // Send the data to Receiver Adapter
arg1.getOutputPayload().getOutputStream().write(strOutData.toString().getBytes(“UTF-8”));
arg1.getOutputPayload().getOutputStream().flush();
      }
      public void transformResponse(TransformationInput arg0, TransformationOutput arg1, DynamicConfiguration conf)
      throws Exception {
            // Retrieve ResponseData from the input steam of Receiver Adapter
        InputStream is = arg0.getInputPayload().getInputStream();
        String strInData = convertStreamToString(is);
        String strCustId = conf.get(KEY_CUSTID);
        this.getTrace().addInfo(“KEY_CUSTID: “ + strCustId);
        String strOutData = “”;
        /*
         * Do the Response Data Mapping here with data retrieved from Dynamic Configuration
         */
        // Send the data to Sender Adapter
arg1.getOutputPayload().getOutputStream().write(strOutData.toString().getBytes(“UTF-8”));
arg1.getOutputPayload().getOutputStream().flush();
      }

      public String convertStreamToString(InputStream in) {
            StringBuffer sb = new StringBuffer();
            try {
                  InputStreamReader isr = new InputStreamReader(in);
                  Reader reader = new BufferedReader(isr);
                  int ch;
                  while ((ch = in.read()) > -1)
                        sb.append((char) ch);
                  reader.close();
            } catch (Exception exception) {
            }
            return sb.toString();
      }
}

Same concept even work when the request mapping class and response mapping class are different. For the Graphical Mapping, UDFs can be leveraged to set and get the parameters to and from the Dynamic Configuration Object.