Monday, 30 July 2018

WstxUnexpectedCharException: Unexpected character in prolog; expected '<'

Are you getting this below error:

Could not generate the XML stream caused by: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character 'C' (code 67) in prolog; expected '<'
 at [row,col {unknown-source}]: [1,1]., cause: com.ctc.wstx.exc.WstxUnexpectedCharException: Unexpected character 'C' (code 67) in prolog; expected '<'
 at [row,col {unknown-source}]: [1,1]

Solution:

Note: below comment on this error is not exact solution always, depend on the steps it may vary. This is one of the cause.

1. This is due to structure data mismatch to the next step.

If it is mapping step then source wsdl and the input data structure/namespace to it is different. It is expecting <xml version ........ at 1st line but that is not present. 

2.   Operation specification at header field in mapping level is  mentioned constant( instead of Create/Update etc.)



Count row/ line from csv file in SAP HCI/CPI

We had a requirement to count the lines in CSV file and return the value at end of that payload.

For this, we had written a groovy script to count the lines and return it to store in property . By that we retrieved it in the content modifier.


Code:

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

def Message processData(Message message) {
def lineNo = 0;
def lines = message.getBody(java.lang.String) as String;
    lines.eachLine {
    lineNo++} 
message.setProperty("count1",lineNo);
return message;
}

Content Modifier:

Add this in body to print the count of the lines

${property.count1}



Tuesday, 3 July 2018

Collecting a field from different input structure and pass it to SuccessFactor Query

I have a requirement, where we need to pick all the personIdExternal fields from the source data (may be different structure as well) and combine all then pass it to the query at the receiver channel.

From all the values, If any of the personIdExternal value is matching at SuccessFactor need to be fetch as response.

Groovy Script:

import com.sap.gateway.ip.core.customdev.util.Message;
import groovy.json.*;
import groovy.util.logging.*;
import com.sap.gateway.ip.core.customdev.util.Message;
import org.codehaus.*;

def Message processData(Message message)
{

 def body = message.getBody(java.lang.String) as String;
 def XmlDataObject = new XmlSlurper().parseText(body)

    def xmldata = []
    def s="";
    XmlDataObject.'**'.findAll { it.name()== 'personIdExternal'}.each { a ->
    xmldata << a.text() }

//Removed duplicates

    xmldata.unique()   
    def last = xmldata.last()

//Need to find the last value to remove "or" from it

    for (i in xmldata){
     
       if (last == i){
      
          s= s.concat("(personIdExternal eq ${i}) ");
          }
       else{
             s= s.concat("(personIdExternal eq ${i}) or ");
           }
               }
    message.setBody(s);
    return message; 
    }

Output:


(personIdExternal eq 101) or (personIdExternal eq 112) or (personIdExternal eq 123) or (personIdExternal eq 144) or (personIdExternal eq 115) or (personIdExternal eq 167) or (personIdExternal eq 179) or (personIdExternal eq 189) or (personIdExternal eq 136) or (personIdExternal eq 182)


Happy Learning!!!



File Counter in FileName Implementation - SAP CPI/HCI

We have a requirement to add the sequence number at target filename. In this we need to append the counter sequence number at the end of filename.

Whenever the file is processed at CPI, the file number need to generated and appended as below.
Output_(FileName)_0001.xml
Output_(FileName)_0002.xml
Output_(FileName)_0003.xml


Main Iflow process:


 File counter logic process:



This is SOAP to SFTP scenario, whenever the data received, it triggers the Counter sub process in parallel.

Content modifier:

Add the Global variable name in Property tab to retrieve the stored value (file count) in data store. 

Note: Add the Default value to 1.
Below Body tab shows the input data send from SOAP (C4C) with retrieved value in P_OPERATION field


Mapping :

Mapping is used to add the logic of increment from the retrieved global value.
P_OPERATION field value is  5 then it adds 1 to it and output will be 6 now.


Content modifier:

Frame the file in the content modifier itself.
Read the FileCount value from the mapping step and frame it in CamelFileName expression with value: /IncidentOUT/Output_POC_${header.FileCount}.xml

/IncidentOUT   -Directory

Output_POC_${header.FileCount}.xml    - Filename

${header.FileCount}  - 6 for our case


Write Variable:

Here we update the current file sequence number into the global variable at data store.


Receiver channel:

No need to specify directory and filename in the receiver SFTP channel



Happy Learning !!!



Friday, 4 May 2018

Exception Sub Process-How to create Alert body for Error Notification

This blog describes how to create and handle dynamic alert notification inside the Exception Sub Process in SAP HCI.

About Exception Sub Process:Exception Sub Process can catch any exceptions thrown in the integration process and process them.Below parameters will give more details of the the exception raised.
${exception.message} : Short description of the Exception.
${exception.stacktrace} : Complete trace of the Exception raised.

Use Case:

Lets create Custom integration where we will pull the File from SFTP server and save it in data store.




1. Sender: Add Sender component from integration designer pallete.

2. Channel: Configure SFTP sender channel which picks file from SFTP server.



3. Content Modifier: To read the key details of incoming message.In our case we are capturing input file name.4. Script: To read the tenant details in run time( in our case tenant id is of 5 character length like X1234 ,L1234 ) and stores it in property.

5. Script: Erroneous script to forcefully fail the message and trigger exception.

6. Data Store: Store the output ( In the above case its just added to have an end to end flow ).

7. Content Modifier: Content Modifier [ Body ] to build the email body.
Content Modifier [ Header ] to set the exception stack trace into header which is later sent as a attachment.
8. Send: Send step is used to send the message to External Systems Asynchronously.

9. Channel: Mail adapter to send the exception details to mail receiver.

Once you are done with above configuration save and deploy the Integration Project.

Place a test file in SFTP share and you will be able to find the below Error Alert Mail triggered from Exception Sub Process.




Further Improvements and Limitations:1. XSLT mapping with HTML can be used in addition to above use case inside the Exception Sub Process to improvise the look and feel of the email body.

2. Custom Exception can be created and raised using Groovy Script.

3. You cannot catch exceptions of local integration process in the main integration process.

4. As of now Exception Sub Process is not available in WEBUI( may be it is present in the SAP road map ).

Conclusion:Exception Sub Process is very useful for handling Exceptions raised in Integration process although it requires further improvements.


Thanks Sriprasad for knowledge share.



Clearing/Resetting the Headers Parameters in SAP HCI/CPI

To clear the headers from the outgoing message request.

On many occasions we have a business scenario where, we need to do multiple lookup calls to an external system with some message transformation and then make some external HTTP call to post the transformed message.

One of business requirement was wherein we had to look up from S/4 HANA system and subsequently post the transformed message to an external third party system using HTTP Post(method).

While we were making individual calls to the third party system, it was working perfectly fine but once we joined all the pieces and completed the i-flow, we were unable to post the message resulting in error 406.

Hence we decided to log all the headers and see if there were any additional headers which would have been carried from the previous SOAP call to S/4 HANA.import com.sap.gateway.ip.core.customdev.util.Message; import java.util.HashMap; def Message processData(Message message) { def headers = message.getHeaders() def messageLog = messageLogFactory.getMessageLog(message) for (header in headers) { messageLog.setStringProperty("header." + header.getKey().toString(), header.getValue().toString()) } return message; }

Above code helped us to get the list of header values from the current message.

On further analysis we could understand that, there could be various reasons why the message header could get appended with different values which might not be relevant in preceding calls. After reading the below paragraph from standard documentation we had a clue of possible error.Note that data written to the message header during a processing step (for example, in a Content Modifier or Script step) will also be part of the outbound message addressed to a receiver system (whereas properties will remain within the integration flow and will not be handed over to receivers). Because of this, it is important to consider the following header size restriction if you are using an HTTP-based receiver adapter: If the message header exceeds a certain value, the receiver may not be able to accept the inbound call (this applies to all HTTP-based receiver adapters). The limiting value depends on the characteristics of the receiver system, but typically ranges between 4 and 16 KB. To overcome this issue, you can use a subsequent Content Modifier step to delete all headers that are not supposed to be part of the outbound message.

Now this clearly states that there could be certain irrelevant headers which could be causing a problem.

If we retain some relevant headers and delete the rest we could have three different use cases:
Delete specific header values
Delete all the header values
Delete all the headers except masking a few.

So the next target was to achieve these use cases. Please follow the steps below
Select Content Modifier step before the HTTP call.
Select Message Header section
Click Add button
Select Delete from the Drop down.


Press on Select Button under Column Name which will give you a pop up




Now we have three options to handle the deletion of the headers:
Delete specific header values: In this case we can select the first option Header and mention all the header needs to be deleted. We could add ‘n’ number of header variables with the Action as delete and mention the Header to be deleted.
Delete all the header values: In case we had to delete all the headers we could select the Expression instead of Header. This would clear all the headers before the subsequent calls.

Delete all the headers except masking few: And finally if we want to mask few we could mention them under Exclude with wildcard character.




Thursday, 8 March 2018

Setting SOAP Headers in SAP HCI


You can use Groovy programming language (Groovy script) to set SOAP headers.

The following example shows a SOAP message with a SOAP header.
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Header>
       <AuthHeader soap:actor="actor_test" soap:mustUnderstand="1" xmlns="http://www.Test.com/">
          <ClientId>username</ClientId>mustUnderstand 
          <Password>password</Password>
       </AuthHeader>
   </soap:Header>
   <soap:Body>
      <test:TestMessage xmlns:test="http://hci.sap.com/ifl/test">
         <MessageContent>customer1</MessageContent>
      </test:TestMessage>
   </soap:Body>
</soap:Envelope>
To set such a SOAP header, use the following script.
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.cxf.binding.soap.SoapHeader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.sap.it.api.ITApiFactory;
import com.sap.it.api.securestore.SecureStoreService;
import com.sap.it.api.securestore.UserCredential;

def Message processData(Message message) {

   // First fetch user name and password which must be entered into the SOAP header from the secure store service.
   // This is not necessary if your SOAP header does not require data from the secure store service.
   def service = ITApiFactory.getApi(SecureStoreService.class, null);
   def credential = service.getUserCredential("partner1_credential_alias");
   if (credential == null){
      throw new IllegalStateException("No credential found for alias 'partner1_credential_alias'");
   }
   String userName = credential.getUsername();
   String password = new String(credential.getPassword());

   // Create Soap Header as DOM Element;  the attributes "actor" and "mustUnderstand" must not be added;
   // these attributes can be added later see below.
   // The following lines create the DOM Element.
   //<AuthHeader xmlns=http://www.Test.com/><ClientId>"+userName+"</ClientId><Password>"+password+"</Password></AuthHeader>"
   DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
   dbf.setNamespaceAware(true);
   dbf.setIgnoringElementContentWhitespace(true);
   dbf.setValidating(false);
   DocumentBuilder db = dbf.newDocumentBuilder();
   Document doc = db.newDocument();
   Element authHeader = doc.createElementNS("http://www.Test.com/", "AuthHeader");
   doc.appendChild(authHeader);
   Element clientId = doc.createElementNS("http://www.Test.com/", "ClientId");
   clientId.setTextContent(userName);
   authHeader.appendChild(clientId);
   Element passwordEl = doc.createElementNS("http://www.Test.com/", "Password");
   passwordEl.setTextContent(password);
   authHeader.appendChild(passwordEl);

   // Create SOAP header instance.
   SoapHeader header = new SoapHeader(new QName(authHeader.getNamespaceURI(), authHeader.getLocalName()), authHeader);
   header.setActor("actor_test");
   header.setMustUnderstand(true);

   // Add the SOAP header to the header list and set the list to the message header "org.apache.cxf.headers.Header.list".
   List  headersList  = new ArrayList<SoapHeader>();
   headersList.add(header);
   message.setHeader("org.apache.cxf.headers.Header.list", headersList);
   
   return message;
}