SAP BTP, SAP ABAP Environment

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 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 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

  1. After analyzing we need to check the APIs from POSTMAN software and simulate the API call.
  2. 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 Authentication
  1. 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.
  2. 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 Ticket in Header part of Request
Body parameters of the API call
  1. 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.
  2. 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.
  3. 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 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.