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:
- How to consume an API in SAP ABAP
- Basic understanding of Web services / API
- How to use POSTMAN to check the APIs
- 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.
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:
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:
- 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
- After analyzing we need to check the APIs from POSTMAN software and simulate the API call.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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.