BLOGS

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

Dec 23, 2021

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. 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. 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 . CONCATENATE 'http://' wa_link-value '/otcs/cs.exe/api/v1/auth' INTO lv_url. CONCATENATE 'http://' wa_link-value '/otcs/cs.exe/api/v1/nodes/{id}/content' INTO lv_url1. ENDIF . ENDIF . cl_http_client=create_by_url( EXPORTING url = lv_url IMPORTING client = DATA (lr_http_client) EXCEPTIONS argument_not_found = 1 plugin_not_active = 2 internal_error = 3 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 . lr_http_client-propertytype_logon_popup = lr_http_client-co_disabled. 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 . lr_http_client-request-set_form_fields( EXPORTING fields = int_fields ) . cl_http_utility=set_request_uri( request = lr_http_client-request uri = lv_url ) . lr_http_client-send( EXPORTING timeout = 15 EXCEPTIONS http_communication_failure = 1 http_invalid_state = 2 http_processing_failed = 3 http_invalid_timeout = 4 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 . lr_http_client-receive( EXCEPTIONS http_communication_failure = 1 http_invalid_state = 2 http_processing_failed = 3 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. CALL METHOD lr_response-get_status IMPORTING code = DATA (lv_code) reason = DATA (lv_desc) . IF lv_code = '200' . DATA (lr_result) = lr_http_client-response-get_cdata( ) . 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 . 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( ) . lif_element_close ?= reader-read_next_node( ) . cl_http_client=create_by_url( EXPORTING url = lv_url1 IMPORTING client = DATA (lr_http_client1) EXCEPTIONS argument_not_found = 1 plugin_not_active = 2 internal_error = 3 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 . lr_http_client1-propertytype_logon_popup = lr_http_client1-co_disabled. 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. lr_http_client1-request-set_form_fields( EXPORTING fields = int_fields ) . cl_http_utility=set_request_uri( request = lr_http_client1-request uri = lv_url1 ) . lr_http_client1-request-set_header_field( EXPORTING name = 'OTCSTicket' value = l_val ) . lr_http_client1-send( EXPORTING timeout = 15 EXCEPTIONS http_communication_failure = 1 http_invalid_state = 2 http_processing_failed = 3 http_invalid_timeout = 4 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 . lr_http_client1-receive( EXCEPTIONS http_communication_failure = 1 http_invalid_state = 2 http_processing_failed = 3 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. CALL METHOD lr_response1-get_status IMPORTING code = DATA (lv_code1) reason = DATA (lv_desc1) . IF lv_code1 = '200' . DATA (lr_result1) = lr_http_client1-response-get_data( ) . content_in_xstring = lr_result1. status = 'S' . http_res_code = lv_code1. http_res_desc = lv_desc1. ELSE . http_res_code = lv_code1. http_res_desc = lv_desc1. status = 'E' . ENDIF . ENDIF . ENDIF . ENDIF . ELSE . 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

Related Posts