HCI – Integrating Salesforce using HCI – Using REST API
This blog post is related to an integrated scenario from ERP to Salesforce using HCI. This content was based on HCI -Integrating SalesForce (SFDC) using HCI -Part 1 and HCI -Integrating SalesForce (SFDC) using HCI -Part 2, that was create using SOAP API.
The following content I will show how to create an integration using the Salesforce REST API.
Accessing objects in Salesforce organization using REST does not require WSDL. Well-suited for browser-based applications, mobile apps, and highly-interactive social applications.
REST API use data format in JSON or XML and works as synchronous communication.
End-To End Flow
The Integration Project was divided in:
- OAuth Token request flow
- Main Flow
- Exception Flow
The better option to create a full scenario is separate the OAuth and Main Flow in 2 iflows to re-use the develop and Salesforce session in multiple calls (explained in Bhavesh Kantilal´s posts). I included in on iflow just to present the end-to-end solution in one post only.
Process Flow
OAuth Token request flow
Process Flow
Content Modifier | Get the inputPayload and provide property variables |
Script | Dynamic query for HTTP request through script |
Content Modifier | Use the property generated on script to pass thought the HTTP |
Request-Reply | HTTP to request a valid session |
Write variables | Write the data on external variables |
Content Modifier | Use the external variables and provide the inputPayload |
The following information should provide by Salesforce to generate a Token/Session ID
- grant_type with a content value “password”
- client_id is the Consumer Key
- client_secret is the Consumer Secret
- username
- password
The OAuth will provide to the Token request:
- redirect_uri is the Callback URL.
- session_id
- token_type
Content Modifier
Properties
Action | Name | Type | Data Type | Value |
Create | grant_type | Constant | java.lang.String | password |
Create | inputPayload | Expression | java.lang.String | ${in.body} |
Provide de grant_type content to be used on groovy script and inputPayload to be used after the token session.
Script
The Secret and User parameters was storage on 2 Security Credentials for security and maintenance reasons.
The script will return a query that should be used at the next step.
// query_http.groovy import com.sap.gateway.ip.core.customdev.util.Message; import java.util.HashMap; 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) { //Message Log def messageLog = messageLogFactory.getMessageLog(message); //Credentials def secret = "SF_Secret"; def user = "SF_User"; //service to get the credentials def service = ITApiFactory.getApi(SecureStoreService.class, null); //get secret credential def secretCred = service.getUserCredential(secret); if (secretCred == null){ throw new IllegalStateException("No credential found for alias " + secret); } //get user credential def userCred = service.getUserCredential(user); if (userCred == null){ throw new IllegalStateException("No credential found for alias " + user); } //HTTP Parameters value String client_id = secretCred.getUsername(); String client_secret = new String(secretCred.getPassword()); String username = userCred.getUsername(); String password = new String(userCred.getPassword()); //Properties def map = message.getProperties(); def grant_type = map.get("grant_type"); //Query def query = ""; query = query + "grant_type=" + grant_type + "&"; query = query + "client_id=" + client_id + "&"; query = query + "client_secret=" + client_secret + "&"; query = query + "username=" + username + "&"; query = query + "password=" + password; message.setProperty("query", query); return message; }
Content Modifier
Header
Action | Name | Type | Data Type | Value |
Create | CamelHttpQuery | Expression | java.lang.String | ${property.query} |
Create | Accept | Constant | java.lang.String | application/xml |
At this point we should provide to the HTTP Communication Channel a query to request a token. We will use a dynamic query created at the previous script an pass trough the CamelHttpQuery on header. This variable changes the HTTP Query on the Communication Channel.
Additionally, I include the header “Accept” to request a “application/xml” at the response, is it possible to request a JSON data also.
Request-Reply
This request provides a POST message as the following:
//separeted parameters curl https://login.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=myclientid" -d "client_secret=myclientsecret" -d "[email protected]" -d "password=mypassword123456" //grouped parameters on query curl https://login.salesforce.com/services/oauth2/token "grant_type=password&" + "client_id=myclientid&" + "client_secret=myclientsecret&" + "[email protected]&" + "password=mypassword123456"
Write variables
The access_token is our Session ID Authorization that we need. Remember at the previous step I use the Accept parameter with “application/xml” value, so, we can set variables reading directly the Channel response.
Name | Data Type | Type | Value | Global Scope |
access_token | java.lang.String | XPath | /OAuth/access_token | |
instance_url | java.lang.String | XPath | /OAuth/instance_url | |
token_type | java.lang.String | XPath | /OAuth/token_type |
Content Modifier
Header
Action | Name | Type | Data Type | Value |
Delete | CamelHttpQuery | |||
Delete | Accept | |||
Delete | Content-Type |
Properties
Action | Name | Type | Data Type | Value |
Create | access_token | Local Variable | access_token | |
Create | instance_url | Local Variable | instance_url | |
Create | token_type | Local Variable | token_type | |
Delete | query | |||
Delete | grant_type |
Using the same integration flow for request the token and provide the update query, I included the Delete commander on the header and properties to delete the previous parameters that do not be used.
Body
${property.inputPayload}
The inputPayload set on body, to request update the Object ID on Salesforce
Main Flow
Process Flow
Process Call | Request a valid session ID on Salesforce |
Content Modifier | Ready the data of inputPayload to use on next script |
Script | Groovy mapping to define the fields to update (JSON) |
Content modifier | Provides URL and Auth details to the HTTP |
Request-Reply | The HTTP request to Salesforce – Update query on body |
Content Modifier | retun in XML format to the sender |
Process Call
Call the previous iflow
Salesforce Request Token OAuth
Content Modifier
Properties
Action | Name | Type | Data Type | Value |
Create | type | Constant | Account | |
Create | ExternalID | XPath | /update/BusinessPartner/ExternalID | |
Create | Status | XPath | /update/BusinessPartner/Status | |
Delete | ServiceURL | Constant | /services/data/v39.0/sobjects/ |
Use the ServiceURL parameter to add a variable string to the URL, the value “/services/data/v39.0/sobjects/” request the REST service data and inform the version of Salesforce API. This information will be used on the next script.
Script
The syntax to provide the access token in your REST requests:
Authorization: Bearer access_token // For example: curl https://instance_name.salesforce.com/services/data/v39.0/ -H 'Authorization: Bearer access_token'
To update a field on a record, the Salesforce accept the HTTP method PATCH, but the HCI does not provide support on this Method until this moment.
The following cURL command retrieves the specified Account object using its ID field, and updates its Status field.
curl https://na1.salesforce.com/services/data/v39.0/sobjects/Account/001D000000IroHJ -H "Authorization: Bearer access_token" -H "X-PrettyPrint:1" -H "Content-Type: application/json" --data-binary '{ "Status__c" : "Inactive" }' -X PATCH
In order to resolve the difference between Salesforce and HCI update method, we just provide a POST method on HCI side but including at the URL the command “?_HttpMethod=PATCH“.
https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_update_fields.htm
This information will update the method on calling the Communication Channel request.
The information included on the following script:
//query_update.groovy import com.sap.gateway.ip.core.customdev.util.Message; import java.util.HashMap; def Message processData(Message message) { def fieldName = "Status__c"; // field [s] to be change the value def map = message.getProperties(); //XML Properties def type = map.get("type"); //sObject type to change (E.g. "Account") def id = map.get("ExternalID"); def status = map.get("Status"); // map the value that should be change //Auth Properties def access_token = map.get("access_token"); def instance_url = map.get("instance_url"); def token_type = map.get("token_type"); //URL def serviceURL = map.get("ServiceURL"); def url = ""; url = instance_url + serviceURL + type + "/Id/" + id + "?_HttpMethod=PATCH"; //Header def auth = token_type + " " + access_token; //Query def query = ""; query = query + "{ "" + fieldName + "" : "" + status + "" }"; //Set Properties message.setProperty("url", url); message.setProperty("auth", auth); message.setProperty("query", query); return message; }
Content modifier
Header
Action | Name | Type | Data Type | Value |
Create | CamelHttpUri | Expression | ${property.url} | |
Create | Content-Type | Constant | application/json | |
Create | Authorization | Expression | ${property.auth} | |
Delete | Accept |
Dynamic change the HTTP URL using the Camel parameter CamelHttpUri
Body
${property.query}
Request-Reply
Note that the Address provided at the Request-Reply is just to have some information, because the correct URL will be change dynamically with CamelHttpUri.
Content Modifier
Body
I just include a return tag on the final content modifier to return back a XML response. the reason is that the response provided by Salesforce is in JSON format. There is better way to convert the JSON to XML format to get the correctly response.
When successful, return an empty message, this is the tag I found to does not return a error for all responses on HCI.
Exception Flow
Process Flow
Content Modifier | Provide the exception message on error |
In any error message from Salesforce, the error return only the message monitoring on HCI. In order to revert back to the Sender system, I included the exception flow just with the exception.message log on the body.
Content Modifier
${exception.message}
Test the Interface
Finally, to test the interface we are using the SOAP UI to provider the Sender message with the External ID and the field to update on Salesforce.
0017000000abCDe Inactive
This interface is just an example how to use the REST API from Salesforce based that HCI does not support PATCH method and how to resolve it.
The interface should have mode fields and is possible to work with a message mapping and groovy mapping to create the complete JSON Query to update the Salesforce fields.
I hope this integration process bring how to use the REST API and help in any new integration with Salesforce system.
Other patterns and models can be added into the comment sections!
Reference:
- HCI -Integrating SalesForce (SFDC) using HCI -Part 1
- HCI -Integrating SalesForce (SFDC) using HCI -Part 2
- Examples provided by Salesforce for the REST API
- SAP HCP-IS(HCI): Dynamically configure the Address field of the HTTP adapter
New NetWeaver Information at SAP.com
Very Helpfull