EDUCAÇÃO E TECNOLOGIA

Consume Get Content API of Open text to read the Xstring content of the attachments in Open text

Introduction:

This article serves as a guide or an example with code snippet of how to consume Open text content server REST APIs in SAP using ABAP.

Preface

Being an ABAPER for more than four years, I had worked in various WRICEF objects under different SAP modules. I recently worked on an Integration project where I had a requirement to send and receive the attachments of Purchase orders between SAP and third party.

The uniqueness in this requirement is that, the attachments in SAP are not stored in Generic Object services instead they had implemented Open text content server and linked it with SAP.

So whenever I receive attachments from 3rd party, I need to upload it to Open text and whenever the 3rd party requests attachments from SAP, I need to download the content and send them back in XSTRING format.

There were few standard web services already in place implemented by means of standard class and method, through which I was able to upload attachments to Open text. However the get content method which is responsible for getting the content of the attachments was not working and we couldn’t find the cause, as these were from Open text SOAP services.

Unfortunately, the customer did not have a dedicated consultant for Open text and SAP ticket suggested to use Open text REST APIs which are available in the Open text support website. Finally using the Open text support user from the customer, I went through Open text support blogs to find out the REST APIs implementation procedure.

It was very difficult for me initially since I neither had prior work experience in integrations nor had the open text consultant from customer side to clarify on open text part; Also. there were hardly any blogs written in this topic, so I thought I could write one based on my experience.

Requirement:

Need to get the XSTRING content of attachments stored in open text against an SAP Business object like PR, PO etc.

Pre-requisites:

  1. How to consume an API in SAP ABAP
  2. Basic understanding of Web services / API
  3. How to use POSTMAN to check the APIs
  4. Open text support ID to access open text API implementation guide and other related blogs

Basic Architecture

SAP S4 HANA was integrated with Open text Content server. The customer had enabled Business workspace in SAP where they can store attachments in open text rather than SAP GOS.

Business%20Workspace%20in%20SAP

Business Workspace in SAP

In Open text there are options to create a folder or a document. Each of the folder or a document is identified by unique NODE ID’s. For example, let’s assume we have a Purchase order XYZ and there are multiple attachments added to it.

This is how it will be stored in Open text:

Nodes%20in%20Open%20text

Nodes in Open text

Using standard class and methods which are readily available in SAP which internally calls open text SOAP services, we can get the folders and their Node ID’s under each Purchase order or in that case any Business object in SAP.

Approach:

1. Refer the open text support website / API guide and understand the API’s expected inputs and outputs.

Content Server 20.3 REST API | Store & Manage | OpenText APIs | Developer | OpenText

2. After analyzing we need to check the APIs from POSTMAN software and simulate the API call.

3. As per the API documentation, we need to have an OTCS ticket to be used in API calls as an authentication mechanism, which is obtained by passing the open text server credentials to an API.

Token%20Authentication

Token Authentication

4. As per the above screenshot, we need to pass the content server domain in the API URL and in the body, we need to pass ‘userName‘ and ‘password‘ parameters to the API. As a response we get a ticket. We will be using this ticket in Get content API call.

5. Now lets call the Get Content API. For this, in the body of the API call, we need to pass two parameters ‘id‘ and ‘action‘ where id is the Node id of the attachment in Content server and action is ‘download‘. Also in the header part of the API request, we need to pass the above obtained OTCS ticket.

OTCS%20Ticket%20in%20Header%20part%20of%20Request

OTCS Ticket in Header part of Request

Body%20parameters%20of%20the%20API%20call

Body parameters of the API call

6. Now we know that the API’s are working, we just need to use some standard ABAP classes and methods to consume in our ABAP program.

7. I have created a class and method to consume the API, so that wherever necessary we just need to pass the Node ID to this method and get the Xstring content of the attachment.

8. I have created a Table Maintainence to  maintain the username and password of the open text content server and also the domain name of the open text content server. This table is used inside the method to read the same and dynamically form the URL’s.

ABAP%20method%20parameters

ABAP method parameters

Below is the code snippet:

 METHOD node_get_cont. *&---------------------------------------------------------------------------*
*& ZCL_CONT_SER_API_CALL *
*& *
*&---------------------------------------------------------------------------*
*& HEADER *
*&---------------------------------------------------------------------------*
*& Module : MM *
*& Package : MM *
*& Program type : Class and Method *
*& Program Name : NODE_GET_CONT *
*& Program Title : Get content API method call from SAP to Opentext *
*& Description : This method first calls the auth API to get the OTCS*
*& token by passing the credentials in Body of the POST method. Once we get *
*& OTCSTicket we use get content API method with read operation to fetch the *
*& XSTIRNG of the attachment by passing node id and the OTCSTicket in header * *
*& Transport Request No: <<TR number>> *
*&---------------------------------------------------------------------------*
*&---------------------------------------------------------------------------*
*& Modification History (Include changes after production transport) *
*&---------------------------------------------------------------------------*
*& Request | Date | Developer | Description
*&---------------------------------------------------------------------------*
*& | | |
*&---------------------------------------------------------------------------* DATA lv_url TYPE string. DATA lv_url1 TYPE string. DATA: lif_element TYPE REF TO if_sxml_open_element, lif_element_close TYPE REF TO if_sxml_close_element, lif_value_node TYPE REF TO if_sxml_value, l_val TYPE string, l_attr TYPE if_sxml_attribute=>attributes, l_att_val TYPE string. DATA int_fields TYPE tihttpnvp. DATA wa_fields TYPE ihttpnvp. DATA r_name TYPE TABLE OF rsdsselopt. DATA wa_name TYPE rsdsselopt. CONSTANTS c_usrn TYPE zsap_cs_link_api-name VALUE 'userName'. CONSTANTS c_pwd TYPE zsap_cs_link_api-name VALUE 'password'. CONSTANTS c_host TYPE zsap_cs_link_api-name VALUE 'hostname'. CONSTANTS c_id(2) TYPE c VALUE 'id'. CONSTANTS c_action(6) TYPE c VALUE 'action'. CONSTANTS c_download(8) TYPE c VALUE 'download'. wa_name-low = c_usrn. wa_name-sign = 'I'. wa_name-option = 'EQ'. APPEND wa_name TO r_name. CLEAR wa_name. wa_name-low = c_pwd. wa_name-sign = 'I'. wa_name-option = 'EQ'. APPEND wa_name TO r_name. CLEAR wa_name. wa_name-low = c_host. wa_name-sign = 'I'. wa_name-option = 'EQ'. APPEND wa_name TO r_name. CLEAR wa_name. "" Fetch the credential data from Maintainance SELECT * FROM zsap_cs_link_api INTO TABLE @DATA(int_link) WHERE name IN @r_name. IF sy-subrc = 0. READ TABLE int_link INTO DATA(wa_link) WITH KEY name = c_host. IF sy-subrc = 0. "" URL to get the OTCSTicket CONCATENATE 'http://' wa_link-value '/otcs/cs.exe/api/v1/auth' INTO lv_url. "" Get Content API URL CONCATENATE 'http://' wa_link-value '/otcs/cs.exe/api/v1/nodes/{id}/content' INTO lv_url1. ENDIF. ENDIF. "" Create a HTTP Client object cl_http_client=>create_by_url( EXPORTING url = lv_url " URL IMPORTING client = DATA(lr_http_client) " HTTP Client Abstraction EXCEPTIONS argument_not_found = 1 " Communication Parameters (Host or Service) Not Available plugin_not_active = 2 " HTTP/HTTPS communication not available internal_error = 3 " Internal Error (e.g. name too long) OTHERS = 4 ). IF sy-subrc <> 0. CASE sy-subrc. WHEN 1. status = 'E'. msg = 'Error in creating HTTP Request -argument_not_found'(008). WHEN 2. status = 'E'. msg = 'Error in creating HTTP Request -plugin_not_active'(009). WHEN 3. status = 'E'. msg = 'Error in creating HTTP Request -internal_error'(010). WHEN OTHERS. ENDCASE. RETURN. ELSE. "" Disable authentication pop-up lr_http_client->propertytype_logon_popup = lr_http_client->co_disabled. "" Set the POST method operation lr_http_client->request->set_method( method = 'POST' ). REFRESH int_fields[]. CLEAR wa_link. READ TABLE int_link INTO wa_link WITH KEY name = c_usrn. IF sy-subrc = 0. wa_fields-name = wa_link-name. wa_fields-value = wa_link-value. APPEND wa_fields TO int_fields. CLEAR wa_fields. ENDIF. CLEAR wa_link. READ TABLE int_link INTO wa_link WITH KEY name = c_pwd. IF sy-subrc = 0. wa_fields-name = wa_link-name. wa_fields-value = wa_link-value. APPEND wa_fields TO int_fields. CLEAR wa_fields. ENDIF. "" Set the body or form fields of the request lr_http_client->request->set_form_fields( EXPORTING fields = int_fields " Form fields ). "" Set the request URI cl_http_utility=>set_request_uri( request = lr_http_client->request uri = lv_url ). "" Send request lr_http_client->send( EXPORTING timeout = 15 " Timeout of Answer Waiting Time EXCEPTIONS http_communication_failure = 1 " Communication Error http_invalid_state = 2 " Invalid state http_processing_failed = 3 " Error when processing method http_invalid_timeout = 4 " Invalid Time Entry OTHERS = 5 ). IF sy-subrc <> 0. CASE sy-subrc. WHEN 1. status = 'E'. msg = 'http_communication_failure while sending Request to Open text'(007). WHEN 2. status = 'E'. msg = 'http_invalid_state while sending Request to Open text'(006). WHEN 3. status = 'E'. msg = 'http_processing_failed while sending Request to Open text'(005). WHEN 4. status = 'E'. msg = 'http_invalid_timeout while sending Request to Open text'(004). WHEN 5. WHEN OTHERS. ENDCASE. RETURN. ELSE. "" Ask for Response lr_http_client->receive( EXCEPTIONS http_communication_failure = 1 " Communication Error http_invalid_state = 2 " Invalid state http_processing_failed = 3 " Error when processing method OTHERS = 4 ). ENDIF. IF sy-subrc <> 0. CASE sy-subrc. WHEN 1. status = 'E'. msg = 'http_communication_failure while receiving response from Open text'(003). WHEN 2. status = 'E'. msg = 'http_invalid_state while receiving response from Open text'(002). WHEN 3. status = 'E'. msg = 'http_processing_failed while receiving response from Open text'(001). WHEN 4. WHEN OTHERS. ENDCASE. RETURN. ELSE. DATA(lr_response) = lr_http_client->response. "" Get the status code of HTTP response CALL METHOD lr_response->get_status IMPORTING code = DATA(lv_code) " HTTP Status Code reason = DATA(lv_desc). " HTTP status description IF lv_code = '200'. "" 200 /OK "" Get the data DATA(lr_result) = lr_http_client->response->get_cdata( ). "" Traverse through the response to get the TICKET DATA(reader) = cl_sxml_string_reader=>create( cl_abap_codepage=>convert_to( lr_result ) ). DATA(lo_node) = reader->read_next_node( ). " { IF lo_node IS INITIAL.
* EXIT."" Set some error and return RETURN. ENDIF. lif_element ?= reader->read_next_node( ). l_attr = lif_element->get_attributes( ). LOOP AT l_attr ASSIGNING FIELD-SYMBOL(<attr>). l_att_val = <attr>->get_value( ). ENDLOOP. lif_value_node ?= reader->read_next_node( ). l_val = lif_value_node->get_value( ). "" Ticket Value lif_element_close ?= reader->read_next_node( ). "" Get Content API URL "" Create a HTTP Client object cl_http_client=>create_by_url( EXPORTING url = lv_url1 " URL IMPORTING client = DATA(lr_http_client1) " HTTP Client Abstraction EXCEPTIONS argument_not_found = 1 " Communication Parameters (Host or Service) Not Available plugin_not_active = 2 " HTTP/HTTPS communication not available internal_error = 3 " Internal Error (e.g. name too long) OTHERS = 4 ). IF sy-subrc <> 0. CASE sy-subrc. WHEN 1. status = 'E'. msg = 'Error in creating HTTP Request -argument_not_found'(008). WHEN 2. status = 'E'. msg = 'Error in creating HTTP Request -plugin_not_active'(009). WHEN 3. status = 'E'. msg = 'Error in creating HTTP Request -internal_error'(010). WHEN OTHERS. ENDCASE. ELSE. "" Disable authentication pop-up lr_http_client1->propertytype_logon_popup = lr_http_client1->co_disabled. "" Set the GET operation lr_http_client1->request->set_method( method = 'GET' ). REFRESH int_fields[]. wa_fields-name = c_id. wa_fields-value = node_id. APPEND wa_fields TO int_fields. CLEAR wa_fields. wa_fields-name = c_action. wa_fields-value = c_download. APPEND wa_fields TO int_fields. CLEAR wa_fields. "" Set the Query parameters lr_http_client1->request->set_form_fields( EXPORTING fields = int_fields ). " Form fields "" Set the URI cl_http_utility=>set_request_uri( request = lr_http_client1->request uri = lv_url1 ). "" Set the header with the OTCSTicket lr_http_client1->request->set_header_field( EXPORTING name = 'OTCSTicket' " Name of the header field value = l_val ). " HTTP header field value "" Send request lr_http_client1->send( EXPORTING timeout = 15 " Timeout of Answer Waiting Time EXCEPTIONS http_communication_failure = 1 " Communication Error http_invalid_state = 2 " Invalid state http_processing_failed = 3 " Error when processing method http_invalid_timeout = 4 " Invalid Time Entry OTHERS = 5 ). IF sy-subrc <> 0. CASE sy-subrc. WHEN 1. status = 'E'. msg = 'http_communication_failure while sending Request to Open text'(007). WHEN 2. status = 'E'. msg = 'http_invalid_state while sending Request to Open text'(006). WHEN 3. status = 'E'. msg = 'http_processing_failed while sending Request to Open text'(005). WHEN 4. status = 'E'. msg = 'http_invalid_timeout while sending Request to Open text'(004). WHEN OTHERS. ENDCASE. ELSE. "" Ask for Response lr_http_client1->receive( EXCEPTIONS http_communication_failure = 1 " Communication Error http_invalid_state = 2 " Invalid state http_processing_failed = 3 " Error when processing method OTHERS = 4 ). IF sy-subrc <> 0. CASE sy-subrc. WHEN 1. status = 'E'. msg = 'http_communication_failure while receiving response from Open text'(003). WHEN 2. status = 'E'. msg = 'http_invalid_state while receiving response from Open text'(002). WHEN 3. status = 'E'. msg = 'http_processing_failed while receiving response from Open text'(001). WHEN 4. WHEN OTHERS. ENDCASE. ELSE. DATA(lr_response1) = lr_http_client1->response. "" Get the status code of HTTP response CALL METHOD lr_response1->get_status IMPORTING code = DATA(lv_code1) " HTTP Status Code reason = DATA(lv_desc1). " HTTP status description IF lv_code1 = '200'. "" 200 /OK DATA(lr_result1) = lr_http_client1->response->get_data( ). "" Set the content content_in_xstring = lr_result1. status = 'S'. "" Success http_res_code = lv_code1. http_res_desc = lv_desc1. ELSE. "" Set error message "" Set Error response http_res_code = lv_code1. http_res_desc = lv_desc1. status = 'E'. ENDIF. ENDIF. ENDIF. ENDIF. ELSE. "" Set Error response http_res_code = lv_code. http_res_desc = lv_desc. status = 'E'. ENDIF. ENDIF. ENDIF. ENDMETHOD.

This brings me to the conclusion of this blog. Please feel free to comment if you require any clarifications regarding the code.

Acronyms

PR- Purchase Requisition

PO- Purchase Order

GOS – Generic Object Services

Thank You.

Regards,

Sri Sathya Sai Anirudh K

SAP Technical Consultant

ABAP | PI/PO