HCI -Integrating SalesForce (SFDC) using HCI -Part 2
Background
In Part 1 of this blog series, a Login Integration Flow was created that persisted the sessionID, serverURL and sessionTime to the Global Variables of HCI.
- How is this Integration Flow#1 used when the IDoc from SAP arrives to HCI?
- How can a Global Variable be read from the HCI DataStore?
- How easy is it to add SOAP Headers to your SOAP Message?
This blog covers this and the end to end flow. Read on!
Update#1: Currently ( As of Apr 12, 2016 ) HCI does not support the ability to set the SOAP URL Dynamically in the SOAP Receiver Adapter. Hence, at this moment, while the SOAP URL is persisted into the Global Variables, this is not used anywhere. The list of parameters supported Dynamically is listed here : HCI Dynamic Parameters
Nov 3, 2016 – The limitation described is no longer applicable. You can set the URL Dynamically as described in this blog : Dynamic address in the SOAP receiver adapter of HANA Cloud Platform, Integration Services by M.Jaspers
Integration Flow#2 – Material Master Replication Process
ProcessFlow
Step Type | Description |
---|---|
Integration Flow Trigger |
|
Content Modifer |
|
Mapping |
|
Content Modifier |
|
Script |
|
Gateway |
|
SessionInValid – Branch 1 Request Reply |
|
Content Modifier |
|
Script |
|
Request – Reply |
|
Content Modifier
Set the Headers to enable monitoring with the corresponding IDoc Headers. The documentation on this can be read in the blog: End2End monitoring of HCI message flow in Standard Content – Made easy
Mapping
Mapping from the Source IDoc to the SFDC Upsert Request. The details of the mapping are not discussed in this blog.
Content Modifier
Content Modifier enables to set the Property sessionID,sessionURL,sessionTime. The values for these properties are read from their corresponding Global Variables.
Script
The below script is used to validate if the session is valid. If session is Valid, property sessionValid is set to “True” else set to “False”
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
import java.util.Date;
import java.text.SimpleDateFormat;
def Message processData(Message message) {
def messageLog = messageLogFactory.getMessageLog(message);
def propertyMap = message.getProperties()
//Read sessionTime from the Properties
String sessionTime = propertyMap.get("sessionTime");
//Set a Log Message for Monitoring purporses
messageLog.setStringProperty("sessionTime ", sessionTime);
// Convert sessionTime to a Calendar Date
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
cal.setTime(sdf.parse(sessionTime));
// Add 2 Hours to sessionTime
Calendar cal1 = Calendar.getInstance();
SimpleDateFormat sdf1 = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
cal1.setTime(sdf1.parse(sessionTime));
cal1.add(Calendar.HOUR_OF_DAY, 2);
// Get Current Time
SimpleDateFormat sdfDate = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");//dd/MM/yyyy
Date now = new Date();
String sessionValid = "";
// Validate if session is Valid
if(now.before(cal1.getTime())){
sessionValid = "true";
}else{
sessionValid = "false";
}
message.setProperty("sessionValid",sessionValid);
messageLog.setStringProperty("sessionValid ", sessionValid);
return message;
}
Parallel MultiCast & Gateway
Parallel MultiCast is used to enable have a Join Step to merge the multiple branches of the Gateway Step. If you would like to understand these step types further would suggest reading the blog : Multicast Pattern in Integration Flows (HCI-PI)
Gateway Properties are as below
- If sessionValid = ‘False’ -> Make a Request/Reply call to HCI Integration Flow #1 – Join Step
- If sessionValid = ‘True’ -> Default Branch as no call to SFDC for Login is required – Join Step
Request Reply
As mentioned previously, this Request Reply step uses the SOAP Adapter to make a call back to Integration Flow#1.
The SOAP Adapter treats this like any other Webservice and the Proxy-Type used has been Set to “Internet”.
At this moment, I am not aware of any other means to trigger an Integration Flow of HCI and hence this approach has been documented.It would be great if a inbuilt feature is provided to do this to avoid traffic over the internet.
Content Modifier
This content modifier step performs the same tasks as one of the previous content modifier steps,i.e, enables to set the Property sessionID, sessionURL, sessionTime. The values for these properties are read from their corresponding Global Variables.
Script
This script is used to insert SOAP Headers into your Message. In this case, we insert the sessionID into the SOAP Header.
Note: This script also performs some rudimentary XMLNamespace manipulations for SFDC to address the WSDL Incompatibilities into your PI Graphical Mapping editor. There are better ways to do this including a XSLT / Parsing Technique. This blog does not delve into the details of the same.
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;
import groovy.util.XmlSlurper;
def Message processData(Message message) {
def body = message.getBody();
def messageLog = messageLogFactory.getMessageLog(message);
// Read SessionID from the Property
def propertyMap = message.getProperties()
String sessionID = propertyMap.get("sessionID");
// Perform certain XMLNamespace manipulations. Note this is a very rudimentary mode of performing the same.
// An XSLT or a Parser are better modes to do the same.
String payload = message.getBody(java.lang.String);
payload = payload.replaceAll('type','xsi:type');
payload = payload.replaceAll('xmlns:ns0="urn:enterprise.soap.sforce.com"','xmlns:ns0="urn:enterprise.soap.sforce.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"');
payload = payload.replaceAll('xsi:type="Product2"','xsi:type="urn1:Product2"');
message.setBody(payload);
// Set SOAP Heeader sessionID
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("urn:enterprise.soap.sforce.com", "SessionHeader");
doc.appendChild(authHeader);
Element clientId = doc.createElement("sessionId");
clientId.setTextContent(sessionID);
authHeader.appendChild(clientId);
SoapHeader header = new SoapHeader(new QName(authHeader.getNamespaceURI(), authHeader.getLocalName()), authHeader);
List headersList = new ArrayList<SoapHeader>();
headersList.add(header);
message.setHeader("org.apache.cxf.headers.Header.list", headersList);
messageLog.setStringProperty("SessionID ", sessionID);
return message;
}
Request Reply
Now that the SOAP Message is formed with the correct SessionID in the SOAP Header, the request reply is aimed at making the SOAP Call to SFDC to perform the corresponding operation on SFDC. Unlike in the PI world, where a Do Not Use SOAP Envelope mode is required, so such mode is required in HCI as HCI natively provides a mode to manipulate the SOAP Header.
Testing Your Flow
- Prior to testing this Integration Flow, make sure that the Integration Flow#1 Login has been triggered atleast once manually through SOAP UI. Reason : This Integration Flow assumes that the Global Variables sessionID, sessionURL and sessionTime exist in the SAP Data Store.
- Trigger the IDoc from SAP.
Results
- IDoc Triggered at 8:53:56 AM, Processing Time ~ 6 Seconds
- This Integration Flow makes the Call to the Login Integration Flow.
- The Logs also show the call to the Login Integration Flow ( Once )
- IDoc’s triggered at 8:53:58 and 8:54:00, Procesing Time ~ 1 Second
- This Integration Flow re-uses the SessionID as the sessionID is valid.
Global Data Store Updated via the Login Integration Flow
Additional Details
- Search using IDoc# works as required. Note as the variable was declared as a Integer only Number is to be used.
- Likewise if variables is declared as a String, the same needs to be appended with zeroes or wild card prefix is to be used.
Logs Show the sessionID Status as per the Groovy Script
Final Note
The above 2 blogs show a means to implement a Integration with SFDC. Like in the PI world, there are multiple design options for this integration requirement. The idea behind this blog was not to delve into the various other options but show users how the most common approach used for persistence of sessionID’s can be implemented using HCI.
Other patterns / models can be added into the comment sections!
Thanks bhavesh...before this blog i got stucked up at creation of the soap headers.... now i will understand from your blog and i will try ... Regards, Vijay
Thanks Bhavesh for sharing the information.
Thanks for the great work, did you thought about another blog dealing with the UPDATE operation on Salesforce using HCI ?
Hello Mohammed Amine,
Can you explain more in terms of what you mean by a UPDATE operation?
In this blog, i have illustrated a case where the data from SAP is replicated to SFDC using UPSERT operation.
Do you mean to say a case where HCI Queries SFDC, pushes the data to SAP and then updates back to SFDC? Technically its just another iFlow. Is there some challenge you are facing there?
Regards,
Bhavesh
Yes Mr Bhavesh this is almost what I need to do. Let me explain more :
I nees to achieve two main steps :
1 - Getting opportunities from Salesforce and send them to SAP ByD
2 - Update the status for those opportunities in Salesforce
But for testing purposes I am dealing with Account instead of Opportunity, the query is working fine, I am selecting the account Name and I want to change it in HCI and update it back to Salesforce.
So I will need your help to figure out who can I update the account name that I selected previously.
I am using the enterprise WSDL which contain an update operation I have no idea about what is required before calling this update operation via a request-reply .
Thanks,
BR.
Mohamed Amine
Dear Bhavesh, I am currently reproducing your flow, and I am getting an error after trying to login again, is it a problem with the first login flow ?
This is the error :
Processing exchange ID-vsa1129456-od-sap-biz-56095-1462637259951-477-6 in cxf:bean:HCI_Login_{
Error = org.apache.camel.InvalidPayloadException: No body available of type: org.apache.camel.component.cxf.CxfPayload on: Message: [Body is not logged]. Caused by: No type converter available to convert from type: null to the required type: org.apache.camel.component.cxf.CxfPayload with value null. Exchange[ID-vsa1129456-od-sap-biz-56095-1462637259951-477-6][Message: [Body is not logged]]. Caused by: [org.apache.camel.NoTypeConversionAvailableException - No type converter available to convert from type: null to the required type: org.apache.camel.component.cxf.CxfPayload with value null], cause: org.apache.camel.NoTypeConversionAvailableException: No type converter available to convert from type: null to the required type: org.apache.camel.component.cxf.CxfPayload with value null
Any idea about this error ?
Thanks.
BR.
Mohamed Amine.
Hello Mohammed Amine,
The error seems to be because you have triggered a Login call as your session ID has expired and the Login Integration flow has failed.
I would suggest you raise this in the SCN Forum as this could be a long topic of discussion but for a quick check this is what I would do,
- Check which step is failing by enabling Trace.
- Check what is the input to that step and if the previous step provided the right input.
- I think somewhere the contentModifer step is incorrect where the Body is set and that is causing the issue.
Regards,
Bhavesh
It was because there was no message inside the body payload, I added some content and I got a new error :
Cxf.EndpointAddress = https://XXXX/cxf/SF/Login
Error = Inbound processing in endpoint at https://XXXX/cxf/SF/Login failed with message "Fault:Could not send Message.", caused by "HTTPException:HTTP response '401: Unauthorized' when communicating with https://XXXX/cxf/SF/Login"
I added the tenant certificate to the systems.jks. But still getting the same error Unauthorized' when communicating with https://XXXX/cxf/SF/Login
Thanks a lot Bhavesh, for such a useful piece of information.
Dear Bhavesh,
Excellent blog post! Really boosted my implementation within CPI!
I added one variable called sessionSecondsValid (XPath: /loginResponse/result/userInfo/sessionSecondsValid). Great to set the validity dynamically. In our case, it's 24 hours at the moment.
One question:
How exactly does the IFlow behave after the join step? I can see in trace, that multiple messages reach the content modifier step. Does it just take the first arrival?
I did not find any information about this in the documentation.
Many thanks in advance!
Best regards
Markus
I have a requirement for OAuth 2.0 based authentication. I have to send the session_id as part of the message to get the authorization code. The authorization code is then sent to the token URL to fetch the OAuth token. What I am not able to do is to grab the cookie from the response and set it to the outgoing message. Below is the requirement. Please, can you tell me how I can achieve it.
you need to send a HTTP POST request to Auth Server.
Url format:
Request headers MUST include:
Kind regards,
Anand S
Excellent blog post! Really appreciated
@bhavesh.kantilal,
Wonderful Blog. I have developed the IFLOW as mentioned in the blog but while doing the upsert operation I am getting Unknown Host exception.
I have used the same URL which had been returned during Login process. Looking for any helpful inputs.
Thanks in advance,
Aswini.