Skip to Content
Technical Articles

Calling external API from ABSL Script in SAP Cloud Application Studio

Introduction:

SAP Cloud for Customer(C4C) is SAP’s cloud-based SaaS CRM solution which offers a market-leading User Interface, features & functions to complement the need of Intelligent Enterprise. C4C is part of the “SAP Customer Experience” line of business of SAP. C4C is also referred to as SAP Sales Cloud or SAP Service Cloud depending upon in which context it is being referred for.

In the growing digital/cloud era, it is no secret that integrations are becoming more and more relevant, so is true for SAP C4C as well. Therefore, SAP C4C offers out-of-the-box integration (to SAP & Non-SAP systems) for Business Transaction as well as Master Data replication. But there are still some grey areas where customers cannot leverage the standard integration and look for a custom solution to fulfill the business requirement.

For e.g. you have created a Custom Business Object in C4C that you want to transfer the data for it to an external system like Salesforce or even SAP ERP. For such a case, SAP cannot offer standard integration as use cases can differ from customer to customer. Therefore, a customer/partner consultant can leverage the power of SAP Cloud Application Studio and build a custom solution that can communicate with an external system. Here they can create an “External Web Service Integration” to communicate with 3rd party using SOAP/REST protocol.

Before we begin, I would like to point out that the following document does not replace the official studio documentation. Hence official documentation should be always referred for up-to-date information from SAP.

 

Challenge:

As we all know that REST APIs, which are exposed to the internet, always have a security mechanism for e.g., Bearer Token, X-CSRF-Token etc. to ensure that only authorized users can perform read/modification operations into the system. But while implementing it with SAP Cloud Applications Studio, I could not find any relatable example in the studio guide therefore I decided to share my experience here with the community.

Further, there are very few to no practical examples on community/help.sap.com that can explain how to use WebServiceUtilities.ExecuteRESTService with different endpoints like batch or filter etc.

Use Case and Assumption:

Assumption:

Since I don’t have any external service that can be invoked to update the data to 3rd party system, therefore, I will be using SAP C4C OData(V2) i.e. C4CODATAAPI to walk you through the steps.

Another assumption is, you have a basic understanding of C4C Custom Business Object, UI Components, ABSL syntax as well as REST protocol. If not then please take a pause, have a look at these topics on the internet and then continue reading.

 

Use Case:

My use case for demonstration is very simple. Upon save of a custom BO instance, I would like to read employee data using C4C OData API endpoints and then update the same employee instance using ABSL script in SAP Cloud Applications Studio.

 

Implementation Steps:

DISCLAIMER: All the images shown here are from the internal demo system.

  • Create a custom BO as follow:
import AP.Common.GDT as apCommonGDT;

businessobject ZEmpBO raises Succ_Emp_Crt, Succ_Emp_Upd, Err_Emp{

	message Succ_Emp_Crt text "Employee ID &1 has been created." : EmployeeID;
	message Succ_Emp_Upd text "Employee ID &1 has been updated.": EmployeeID;
	message Err_Emp text "Error: &1": LONG_Description;

	element EmpID : ID;
	element FirstName : LONG_Name;
	element LastName : LONG_Name;
	element CreateEmp:Indicator;
	element UpdateEmp:Indicator;
	action EmpBOViaAPICall;
}

Field EmpID gets the ID of the employee that will be created/updated based on the indicator CreateEmp/UpdateEmp. FirstName and LastName values will be taken as user input from UI and then sent to OData API to update Employee Information based on a button in UI that will in result trigger a call to backend action EmpBOViaAPICall.

  • Create Script files as well as Generate & finetune the UI. Finally, your solution structure will look like this.

  • Now create a new “External Web Service Integration” which will define the endpoints for communication:

  • Select WebService type as REST/SOAP-based on your scenario and populate the endpoints to be called from C4C. In my case, I want to call the C4C REST API /sap/c4c/odata/v1/c4codataapi endpoint. Therefore, the same is configured as shown:

  • Upon clicking “Finish”, 2 artifacts (WebService Integration with .wsid extension as well as Communication Scenario with .csd extension) will be generated. Save and activate the generated artifacts.

  • Select .csd file and right-click & select “Maintain Communication Arrangement” to maintain the service to be communicated with. In my case, it is the C4CODATAAPI from the same C4C system.
  • A new browser window will open.Tip: Make sure you are logging in to the browser window using the same credentials that were used for SAP Cloud Applications Studio login.
  • Now select the scenario which was generated from SAP Cloud Applications Studio(.csd file name). In my case it was ZC4CODATA_REST_SRV.

  • Go to the next step and select Communication System. This also the same name as the .csd file with the solution as suffix.

  • Select the required authentication and make sure outbound service is shown as “Enabled” in the “Service Used” section as highlighted.

Click ‘Next’ and ‘Finish’. So that’s all from the configuration point of view… Let’s code now.

  • Open the script file and paste the following code.
import ABSL;
import AP.PDI.bo;
import AP.Common.GDT;
import AP.PDI.Utilities;
var urlParams : collectionof NameAndValue;
var headerParams : collectionof NameAndValue;
var jsonKey: collectionof String;
var headerParam: NameAndValue;
var resourceName : String;
var msg : LONG_Description;
var empID: EmployeeID;
var key: String;
var result_get: RESTCallResult;
var firstName; var lastName; var startDate; var endDate; var body;

// Define header to Fetch CSRF Token
headerParam.Name = "x-csrf-token";
headerParam.Value = "fetch";
headerParams.Add(headerParam);

if( this.CreateEmp == true) // Create employee Case
{
// Make a call to C4C OData API to fetch CSRF Token
	result_get = WebServiceUtilities.ExecuteRESTService("ZC4CODATA_REST_SRV","ZC4CODATA_REST","GET","",urlParams,headerParams,"","");
	// Status code 200 means call was successfull
	if (result_get.Code == "200 ")
	{
        headerParams.Clear();
        headerParam.Name = "x-csrf-token";
        headerParam.Value = result_get.HeaderParameters.Where(n=>n.Name == "x-csrf-token").GetFirst().Value; // Read CSRF Token value from response header
        headerParams.Add(headerParam);
		headerParam.Name = "Accept";
		headerParam.Value = "application/json"; // To get the response in JSON format
		headerParams.Add(headerParam);

		// Now make a POST call to EmployeeCollection 
		resourceName = "EmployeeCollection";  // Specify resource collection/entityset
		firstName = "\"" + this.FirstName.content + "\""; 
		lastName = "\"" + this.LastName.content + "\"";
		startDate =  "\"" + Context.GetCurrentSystemDateTime().content.ToString() + "\"";
		endDate =  "\"" + DateTime.ParseFromString("9999-12-12T00:00:00").content.ToString() + "\""; 
		body = "{ \"FirstName\":" + firstName + ", \"LastName\":" + lastName + ", \"EmployeeValidityStartDate\":" + startDate + ", \"EmployeeValidityEndDate\":" + endDate + "}";
		var result_post = WebServiceUtilities.ExecuteRESTService("ZC4CODATA_REST_SRV","ZC4CODATA_REST","POST",resourceName,urlParams,headerParams,"application/json",body,result_get.Cookies);   // This is the JSON payload which will be sent to the backend 
		if( result_post.Code == "200 " || result_post.Code == "201 " || result_post.Code == "202 " || result_post.Code == "204 ") // Any code of 2XX will be considered as successfull hence checking all the possible http codes
		{
			key = "d.results.EmployeeID";   // Specify Json path to be read
			jsonKey.Add(key);
			empID.content = Json.ParseKeyValues(jsonKey,result_post.Content).KeyValue.GetFirst().Value; // Parse JSON Key value and read Employee created ID.
			Succ_Emp_Crt.Create("S",empID);
			return;
		}
	}
	else
	{
		// Raise error message and return
		msg.content = "CSRF Token couldnot be fetched.";
		Err_Emp.Create("E",msg);
		return;
	}
}

if(this.UpdateEmp == true) // Update Employee
{
	resourceName = "EmployeeCollection?$filter=EmployeeID eq '"+ this.EmpID +"'";  // Get the employee record to be updated
	headerParam.Name = "Accept";
	headerParam.Value = "application/json"; // Get the employee record in JSON format
	headerParams.Add(headerParam);
	// Execute the employee read odata call
	result_get = WebServiceUtilities.ExecuteRESTService("ZC4CODATA_REST_SRV","ZC4CODATA_REST","GET",resourceName,urlParams,headerParams,"","");
	if (result_get.Code == "200 " ) // HTTP 200 means call to backend was successfull
	{
		headerParams.Clear();
        headerParam.Name = "x-csrf-token";
        headerParam.Value = result_get.HeaderParameters.Where(n=>n.Name == "x-csrf-token").GetFirst().Value; // Read CSRF token value from the response header
        headerParams.Add(headerParam);
		headerParam.Name = "Accept";
		headerParam.Value = "application/json";
		headerParams.Add(headerParam);

		key = "d.results[1].ObjectID"; // Define key to read the JSON payload
		jsonKey.Add(key);
		resourceName = "EmployeeCollection('" + Json.ParseKeyValues(jsonKey,result_get.Content).KeyValue.GetFirst().Value + "')"; // Parse JSON payload and format the resourcename
		firstName = "\"" + this.FirstName.content + "\""; 
		lastName = "\"" + this.LastName.content + "\"";
		startDate =  "\"" + Context.GetCurrentSystemDateTime().content.ToString() + "\"";
		endDate =  "\"" + DateTime.ParseFromString("9999-12-12T00:00:00").content.ToString() + "\""; 
		body = "{ \"FirstName\":" + firstName + ", \"LastName\":" + lastName + ", \"EmployeeValidityStartDate\":" + startDate + ", \"EmployeeValidityEndDate\":" + endDate + "}";
		var result_patch = WebServiceUtilities.ExecuteRESTService("ZC4CODATA_REST_SRV","ZC4CODATA_REST","PATCH",resourceName,urlParams,headerParams,"application/json",body,result_get.Cookies); // Payload to be sent top the backend with API call
		if( result_patch.Code == "200 " || result_patch.Code == "201 " || result_patch.Code == "202 " || result_patch.Code == "204 " )
		{
			empID.content = this.EmpID;
			Succ_Emp_Crt.Create("S",empID);
			return;
		}
	}
	else
	{
		// Raise error message and return
		msg.content = "CSRF Token couldnot be fetched.";
		Err_Emp.Create("E",msg);
		return;
	}
}

The code snippet given above should help you to Read, Create as well as Update the employee record.

Tip: To read the response body of created employee record or reading the existing employee record a new reuse Library JSON is used. This library can parse the JSON payload to the internal table which can then later be used for reading the data at runtime.

  • In case you want to execute a batch operation where more than 1 API operation you want to trigger from a single API call then it is also feasible to use a similar pattern. For e.g in the following code, I am trying to update 2 employee records(hardcoded values) with a single API call.
// Batch Operation to update nickname of 2 employees in a single API call
result_get = WebServiceUtilities.ExecuteRESTService("ZC4CODATA_REST_SRV","ZC4CODATA_REST","GET",resourceName,urlParams,headerParams,"","");
if (result_get.Code == "200 " ) // HTTP 200 means call to backend was successfull
{
	headerParams.Clear();
    headerParam.Name = "x-csrf-token";
    headerParam.Value = result_get.HeaderParameters.Where(n=>n.Name == "x-csrf-token").GetFirst().Value; // Read CSRF token value from the response header
    headerParams.Add(headerParam);
	resourceName = "$batch";   // Define batch as the nedpoint
	body = "--batch_guid_01\nContent-Type: multipart/mixed; boundary=changeset_guid_01\n\n--changeset_guid_01\nContent-Type: application/http\nContent-Transfer-Encoding: binary\n\nPATCH EmployeeCollection('00163E6BB3EA1EE8B8EDA3DFB0B052AF') HTTP/1.1\n" + "Content-Type: application/json\nContent-Length: 10000\n\n{\n    \"NickName\" : \"BatchMode Update 1\"\n}\n\n--changeset_guid_01\nContent-Type: application/http \nContent-Transfer-Encoding: binary \n\nPATCH " + "EmployeeCollection('00163EAF87791EDB9EA113551554898F') HTTP/1.1\nContent-Type: application/json\nContent-Length: 10000\n\n{\n    \"NickName\" : \"BatchMode Update 2\"\n}\n\n--changeset_guid_01-- \n--batch_guid_01--";  // Hardcoded payload with 2 employee ID
	var result_batch = WebServiceUtilities.ExecuteRESTService("ZC4CODATA_REST_SRV","ZC4CODATA_REST","POST",resourceName,urlParams,headerParams,"multipart/mixed; boundary=batch_guid_01",body,result_get.Cookies);
}

Tip: OData batch payload is space as well as line break sensitive i.e., in case there is an extra line break or payload formatting issue then you might end up with HTTP-400 (bad request) error due to malformed payload. Therefore, it is advisable to first execute the batch request in Postman or any other 3rd party REST client and then copy “data” (string) payload from the code snippet generated by postman as shown below:

  • The coding part is done. Save & Activate your changes and test your code.

Conclusion:

As explained above C4C expose a set of reuse libraries (WebServiceUtilities or Json) as well as “External Web Service Integration” object in SAP Cloud Applications Studio that can help you to trigger a call to any 3rd party system to transfer/enrich/validate the data at runtime using SOAP/REST-based API calls. However, please try to restrict the usage of API calls via ABSL code because such calls are made in the synchronous mode which means that, until the response is received back from the external system, the screen will be frozen. The same is also shown as a warning in the SAP Cloud Applications Studio.

Therefore, calls to an external 3rd party should be sensibly used to avoid major performance degradation on UI.

 

Hope you like the content shared here. Please feel free to drop your reaction via the comment section.

 

#HappyLearning #HappyCoding #SAPSalesCloud #SAPCX

3 Comments
You must be Logged on to comment or reply to a post.
    • Hi Mariusz,

      Definitely, you can(or rather should) use a technical user for authentication, but my one of the aim here in the blog post was to show about how to pass cookies for using CSRF token.

      But in case you already have a technical user as well as have configured the same in communication arrangement for outbound communication you can skip the part where I fetch & Pass CSRF token.