Skip to Content

This blog aims in explaining how to assign ASMAs (adapter specific message attributes) for each child message of a BULK message using custom Dynamic Configuration (DC) header fields which can be set using a mapping program during design time, and which will be effective at runtime in IE/AE. To achieve this we require a custom generic adapter module, one ABAP FM and one ABAP table. We can also achieve this, by reading the required child payload fields information and then assigning them to ASMAs using a custom adapter module. However, I am focusing on reading custom DC headers method in adapter module as it has more advantages.

Steps:-

1.    Set the custom DC headers with payload fields, Bulk Message ID and counter during design using multi-mapping. We can pass these values to the custom adapter module using module configuration in the communication channel.
2.    Read these custom headers sequentially with the help of a custom adapter module & ABAP FM.

  • Custom adapter module reads the child message Sequential Number generated by AFW (need not be always 1 for the first child message, hence we need a logic which generates a sequence number) and then it generates its own sequence number starting from 1 using a custom ABAP FM and table.
  • The FM logic uses the read Bulk message ID, AFW sequential number and ACTION (inbound interface name) to generate a unique sequential number which can be used to read the custom DC header fields and assign to ASMAs.
  • The FM uses an ABAP table to persist (latest record) and then it returns the new sequential number which will be used by the custom adapter module.

Flow Chart:-

FlowChart.jpg

Advantages:- 

  1. Module is generic in nature and can be used for any J2EE based technical adapter.
  2. Improves the performance since we are reading the necessary ASMA values for the adapter from the SOAP DC header instead of payload (in case of large size child messages, which can take definite amount of memory while loading the payload in the adapter module with DOM/SAX parser combination)
  3. By passing value ‘X’ for DEL module parameter in the module configuration, we can delete the record from the ABAP table for the last child message of each interface.

Sample Dynamic Configuration Code for mappings (UDF)

DynamicConfigurationKey keyA[] = new DynamicConfigurationKey[a.length];
result.addValue("1");
String allKeyValues = "";
for(int i = 0; i < a.length; i++)
{
          try
          {
                    DynamicConfiguration conf = (DynamicConfiguration) container.getTransformationParameters().get(StreamTransformationConstants.DYNAMIC_CONFIGURATION);
                    keyA[i] = DynamicConfigurationKey.create("urn:CustomDCHdr", "a" + Integer.toString(i+1) );
                    conf.put(keyA[i], a[i]);
                    keyA[i] = DynamicConfigurationKey.create("urn:CustomDCHdr", "b" + Integer.toString(i+1) );
                    conf.put(keyA[i], "/SCD/ich/pi/MULTIMAPPING");
                    if(i == a.length-1)
                    {
                              DynamicConfigurationKey keyCount = DynamicConfigurationKey.create("urn:CustomDCHdr", "Int1Count" );
                              conf.put(keyCount, Integer.toString(a.length) );
                              String messageID = (String) container.getTransformationParameters().get(StreamTransformationConstants.MESSAGE_ID);
                              DynamicConfigurationKey orgBulkMsgID = DynamicConfigurationKey.create("urn:CustomDCHdr", "BulkMsgID" );
                              conf.put(orgBulkMsgID, messageID);
                    }
          }
          catch (Exception e)
          {
                    e.printStackTrace( );
          }
} 

Sample Bulk and Child Message Details

image

FM Definition in PI

image

DB Table Definition in PI

image

image

FM code

*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     VALUE(IV_BULKMSGID) TYPE  STRING
*"     VALUE(IV_SEQNO) TYPE  INTEGER2
*"     VALUE(IV_ACTION) TYPE  STRING
*"     VALUE(IV_FLAG) TYPE  STRING OPTIONAL
*"  EXPORTING
*"     VALUE(EV_COUNT) TYPE  INTEGER
*"----------------------------------------------------------------------
  DATA:  lv_BULKMSG TYPE STRING.
  DATA:  lv_SEQNO TYPE I.
  DATA:  lv_ACTION TYPE STRING.
  DATA:  lv_FLAG TYPE STRING.
  DATA:  lv_ZBULKMSGCOUNT TYPE I.
  DATA:  lv_COUNT TYPE I.
  TABLES ZBULKMSGCOUNT.
  lv_COUNT = 0.
  SELECT SINGLE BULKMSGID SEQNO ACTION CHILDMSGCOUNT FROM ZBULKMSGCOUNT INTO (lv_BULKMSG, lv_SEQNO, lv_ACTION, lv_ZBULKMSGCOUNT) WHERE BULKMSGID = IV_BULKMSGID.
* SELECT SINGLE BULKMSGID FROM ZBULKMSGCOUNT INTO lv_BULKMSG WHERE BULKMSGID = IV_BULKMSGID.
   IF sy-subrc EQ 0.
         IF IV_FLAG = 'X' OR IV_FLAG = 'x'.
             DELETE FROM ZBULKMSGCOUNT WHERE BULKMSGID = IV_BULKMSGID.
             EV_COUNT = 0.
             EXIT.
         ENDIF.
         IF lv_SEQNO NE IV_SEQNO.
             IF lv_ACTION EQ IV_ACTION.
               lv_COUNT = lv_ZBULKMSGCOUNT + ( IV_SEQNO - lv_SEQNO ).
               EV_COUNT = lv_COUNT.
             ELSE.
               lv_COUNT = 1.
               EV_COUNT = lv_COUNT.
             ENDIF.
             UPDATE ZBULKMSGCOUNT SET SEQNO = IV_SEQNO ACTION = IV_ACTION CHILDMSGCOUNT = lv_COUNT WHERE BULKMSGID = IV_BULKMSGID.
         ENDIF.
         IF lv_SEQNO EQ IV_SEQNO AND lv_ACTION EQ IV_ACTION.
             EV_COUNT = lv_ZBULKMSGCOUNT.
         ENDIF.
         IF lv_SEQNO EQ IV_SEQNO AND lv_ACTION NE IV_ACTION.
             lv_COUNT = 1.
             EV_COUNT = lv_COUNT.
             UPDATE ZBULKMSGCOUNT SET ACTION = IV_ACTION CHILDMSGCOUNT = lv_COUNT WHERE BULKMSGID = IV_BULKMSGID.
         ENDIF.
   ELSE.
         ZBULKMSGCOUNT-BULKMSGID = IV_BULKMSGID.
         ZBULKMSGCOUNT-SEQNO = IV_SEQNO.
         ZBULKMSGCOUNT-ACTION = IV_ACTION.
         ZBULKMSGCOUNT-CHILDMSGCOUNT = 1.
         EV_COUNT = 1.
         INSERT INTO ZBULKMSGCOUNT VALUES ZBULKMSGCOUNT.
   ENDIF.
ENDFUNCTION.

Module Configuration in Communication Channel

Note: enable ASMA in the receiver file channel
* image

7:24 PM 3/30/2011 Now updated the module code to get PI server information from module configuration. Now you to need provide encryppted (BASE64) password. Use this link base64 encoding  (http://www.opinionatedgeek.com/dotnet/tools/Base64Encode/Default.aspx) to provide base64 encrypted password as below along with other PI server information.  e.g., 100;pisuper;pwbase64xxxxxxx;EN;piserver.domain.com;00

image

Adapter Module Code

/* Author: Praveen Gujjeti */
package bulkmsg;
import com.sap.aii.adapter.xi.ms.*;
import com.sap.aii.af.mp.module.*;
import com.sap.aii.af.ra.ms.api.*;
import com.sap.aii.af.ra.ms.spi.*;
import com.sap.aii.af.service.auditlog.*;
import javax.ejb.*;
import java.util.StringTokenizer;
import com.sap.mw.jco.*;
public class ChildMsgSequenceNumberBean 
          implements SessionBean, Module
{
          public ChildMsgSequenceNumberBean(){}
          public void ejbRemove(){}
          public void ejbActivate(){}
          public void ejbPassivate(){}
          public void setSessionContext(SessionContext context)
          {
                    myContext = context;
          }
          public void ejbCreate()
                    throws CreateException{}
          public ModuleData process(ModuleContext moduleContext, ModuleData inputModuleData) throws ModuleException
          {
                    AuditMessageKey amk = null;
                    try
                    {  
                              Object obj = null;
                              Message msg =  null;
                              XIMessage xiMsg =  null;
                              MessageProperties msgProp = null;
                              int childMsgSequenceNumber = 0;
                              String action = null;
                              String bulkMsgID = null;
                              String rfcPayload = null;
                              String logicalSequenceNumber = null;
                              String customHeaderNS = (String) moduleContext.getContextData("CustomNS"); //Custom DC NameSpace
                              String customHeaderTNS = (String) moduleContext.getContextData("CustomTNS"); //Custom DC Technical Names
                              String customeHeaderCount = (String) moduleContext.getContextData("Count"); //Custom DC Count
                              obj = inputModuleData.getPrincipalData();
                              msg = (Message) obj;
                              xiMsg = (XIMessage) obj;
                              if(msg.getMessageDirection() == MessageDirection.INBOUND)
                                        amk = new AuditMessageKey(msg.getMessageId(), AuditDirection.INBOUND);
                              else
                                        amk = new AuditMessageKey(msg.getMessageId(), AuditDirection.OUTBOUND);
                              msgProp = xiMsg.getMessageProperties();
                              childMsgSequenceNumber = msgProp.sequenceNumber; //AFW generate Sequence Number for Child message
                              action = msg.getAction().getName(); //Inbound Interface Name for Adapter
                              bulkMsgID = (String) msg.getMessageProperty(customHeaderNS, "BulkMsgID"); //Bulk Message ID from custom DC header
                              Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "BulkMessageID from custom DC header: " + bulkMsgID);
                              Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Action(Inbound interface name): " + msg.getAction().getName());
                              Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "AFW generated child message sequence number: " + childMsgSequenceNumber);
                              Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Total Custom DC Keys COUNT for the interface '" + msg.getAction().getName() + "': " + msg.getMessageProperty(customHeaderNS, customeHeaderCount));
//                              Enumeration ctxDataKeys = moduleContext.getContextDataKeys();
//                              while(ctxDataKeys.hasMoreElements())
//                              {
//                                        String key = (String)ctxDataKeys.nextElement();
//                                        Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "getContextDataKey: " + key);
//                                        Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "getContextDataKey&Value: " + key + "=" + moduleContext.getContextData(key));
//                              }
//################ JCO Client Code for FM ZBULKMSGDETAILS which genarates logical Sequence Number #######################
                              JCO.Repository mRepository = null;
                              JCO.Client mConnection = JCO.createClient("<SAPCLIENTNUMBER>", "<USER>", "<PASSWORD>", "EN", "<PISERVERHOSTNAME>", "<SAPSYSTEMNUMBER>"); //Better provide this information from Module configuration. Password will be exposed.
                              //JCO.Client mConnection = JCO.createClient("100", "<USER>", "<PASSWORD>", "EN", System.getProperty("SAPDBHOST").toString(), System.getProperty("SAPSYSTEM").toString());
                              mConnection.connect();
                              mRepository = new JCO.Repository( "JCORepository", mConnection );
                              JCO.Function function = null;
                              IFunctionTemplate ft = mRepository.getFunctionTemplate("ZBULKMSGDETAILS");
                              function = ft.getFunction();
                              JCO.ParameterList input = function.getImportParameterList();
                              input.setValue(bulkMsgID, "IV_BULKMSGID");
                              input.setValue(new Integer(childMsgSequenceNumber), "IV_SEQNO");
                              input.setValue(action, "IV_ACTION");
                              input.setValue("", "IV_FLAG");
                              JCO.ParameterList output = function.getExportParameterList();
                              mConnection.execute( function );
                              logicalSequenceNumber = output.getValue("EV_COUNT").toString();
                              if ( ( msg.getMessageProperty(customHeaderNS, customeHeaderCount) ).equals(logicalSequenceNumber ) && ( moduleContext.getContextData("DEL").toString() ).compareToIgnoreCase("x") == 0)
                              {
                                        input.setValue(bulkMsgID,"IV_BULKMSGID");
                                        input.setValue("X","IV_FLAG");
                                        input.setValue(new Integer(childMsgSequenceNumber),"IV_SEQNO");
                                        input.setValue(action, "IV_ACTION");
                                        mConnection.execute( function );
                                        if ( (output.getValue("EV_COUNT").toString()).equals("0"))
                                        {
                                                  Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Bulk Msg ID '"+ bulkMsgID + "' record deleted from the SAP DB table 'ZBULKMSGCOUNT'");
                                        }
                              }
                              mConnection.disconnect();
//################ JCO Client Code for FM ZBULKMSGDETAILS which genarates logical Sequence Number #######################
                                Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Child message logical sequence Number generated by the adpater module: " + logicalSequenceNumber);
                              StringTokenizer st = new StringTokenizer(customHeaderTNS, ",");
                              String[] customHeaderTN = new String[st.countTokens()]; //Custom DC Technical Names array
                              //String[] ASMANameSpace = new String[st.countTokens()]; //Standard ASMA NameSpace array
                              //String[] ASMATechnicalName = new String[st.countTokens()]; //Standard ASMA Technical Name array
                              String[] payloadFieldForStandardDC = new String[st.countTokens()]; //payload field from DC header for Standard ASMA Technical Object
                              for (int j = 0; st.hasMoreTokens(); j++)
                              {
                                        customHeaderTN[j] = st.nextToken();
                                        payloadFieldForStandardDC[j] = (String) msg.getMessageProperty(customHeaderNS, customHeaderTN[j]+logicalSequenceNumber);
                                        Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Inserting ASMA Key: " + "(\"" + moduleContext.getContextData("key." + Integer.toString(j)) + "\"" + " " + "\"" +  moduleContext.getContextData("value." + Integer.toString(j))+ "\")" + " with value: " + payloadFieldForStandardDC[j]);
                                        //Audit.addAuditLogEntry(amk, AuditLogStatus.SUCCESS, "Child message logical sequence Number generated by the adpater module: " + logicalSequenceNumber);               
                                        //msg.setMessageProperty("http://sap.com/xi/XI/System/File","FileName",payloadFieldForStandardDC);
                                        try
                                        {
                                                  msg.setMessageProperty((String) moduleContext.getContextData("key." + Integer.toString(j)), (String) moduleContext.getContextData("value." + Integer.toString(j)),payloadFieldForStandardDC[j]);
                                        }
                                        catch (InvalidParamException e)
                                        {
                                                  throw new ModuleException("Unable to set Message Property", e);
                                        }
                              }
                    }
                    catch(Exception e)
                    {
                              Audit.addAuditLogEntry(amk, AuditLogStatus.ERROR, e.toString());
                              ModuleException me = new ModuleException(e);
                              throw me;
                    }
                    return inputModuleData;
          }
          private SessionContext myContext;
}

Sample Audit Log of the adapter with custom module

image

Download the EAR file @ http://www.box.net/shared/hpdxrem5pf

I created an Idea to SAP to support this feature by default instead of custom development.

Please have a look at my idea and if you like please VOTE: https://cw.sdn.sap.com/cw/ideas/10218 (SAP PI should support dynamic configuration for 1:n multi-mapping scenarios)

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

Leave a Reply