Skip to end of metadata
Go to start of metadata
  *"* use this source file for the definition and implementation of
*"* local helper classes, interface definitions and type
*"* declarations

CLASS lcx_rest DEFINITION
  INHERITING FROM /iwcor/cx_ds_error.
  PUBLIC SECTION.

    CONSTANTS:
      /s4ppm/cx_odata_client TYPE sotr_conc VALUE '40F2E9AFBE791ED7B9AC8C623B5055BD' ##NO_TEXT.
    DATA:
      text_token TYPE string.
    METHODS:
      constructor
        IMPORTING
          textid     LIKE textid OPTIONAL
          previous   LIKE previous OPTIONAL
          text_token TYPE string OPTIONAL.

ENDCLASS.

CLASS lcx_rest IMPLEMENTATION.

  METHOD constructor.
    CALL METHOD super->constructor
      EXPORTING
        textid   textid
        previous previous.
    IF textid IS INITIAL.
      me->textid /s4ppm/cx_odata_client .
    ENDIF.
    me->text_token text_token .

  ENDMETHOD.

ENDCLASS.

CLASS lcx_rest_noauth DEFINITION INHERITING FROM lcx_restENDCLASS.
CLASS lcx_rest_nocust DEFINITION INHERITING FROM lcx_restENDCLASS.
CLASS lcx_oauth_grant_required DEFINITION INHERITING FROM lcx_restENDCLASS.

CLASS lcl_rest_client DEFINITION.

  PUBLIC SECTION.

    CLASS-METHODS:
      refresh_successful
        RETURNING
          VALUE(rv_successTYPE abap_bool,
      get_redirect_url
        IMPORTING
          iv_oauth2_profile TYPE oa2c_profile
        RETURNING
          VALUE(rv_url)     TYPE string,
      get_client
        IMPORTING
          iv_obl_type       TYPE dpr_tv_obl_type
          iv_oauth2_profile TYPE oa2c_profile OPTIONAL
          iv_username       TYPE string OPTIONAL
          iv_password       TYPE string OPTIONAL
        EXPORTING
          ev_xsrf_token     TYPE string
        RETURNING
          VALUE(rr_client)  TYPE REF TO if_http_client
        RAISING
          lcx_rest_nocust
          lcx_rest_noauth
          lcx_oauth_grant_required,
      initialize.

  PRIVATE SECTION.

    CLASS-DATA:
      mo_http_client TYPE REF TO if_http_client,
      mo_oa2c_client TYPE REF TO if_oauth2_client,
      mv_xsrf_token  TYPE string.

ENDCLASS.

CLASS lcl_rest_client IMPLEMENTATION.

  METHOD get_redirect_url.

    DATA(lo_oa2c_config_readNEW cl_oa2c_config_readeri_profile iv_oauth2_profile ).
    DATA(lv_redirect_urllo_oa2c_config_read->if_oa2c_config_reader~get_redirect_uri).
    SPLIT lv_redirect_url  AT '/sap'  INTO lv_redirect_url DATA(lv_restIN CHARACTER MODE.
    CONCATENATE lv_redirect_url '/sap/bc/sec/oauth2/client/grant/authorization?profile=' iv_oauth2_profile '&SAP-CLIENT=' sy-mandt
      INTO rv_url IN CHARACTER MODE.

  ENDMETHOD.

  METHOD initialize.

    CLEAR:
      mo_http_client,
      mv_xsrf_token.

  ENDMETHOD.

  METHOD refresh_successful.

    DATA:
      lx_oa2c         TYPE REF TO cx_oa2c.

    TRY.
        mo_oa2c_client->execute_refresh_flow).
        mo_oa2c_client->set_tokenEXPORTING io_http_client mo_http_client i_param_kind 'H' ).
        rv_success abap_true.
      CATCH cx_oa2c INTO lx_oa2c.
        rv_success abap_false.
    ENDTRY.

  ENDMETHOD.

  METHOD get_client.

    DATA:
      lx_oa2c         TYPE REF TO cx_oa2c,
      lv_service_url  TYPE string,
      lv_message_text TYPE string,
      lv_redirect_url TYPE string.

    IF mo_http_client IS INITIAL.

      "*----------------------------------------------------------------------------------------------------
      "* Get Object link customizing - please note, three different JIRA object links point to the same
      "* system, their system alias should therefore be the same!
      "*----------------------------------------------------------------------------------------------------

      CALL METHOD cl_dpr_obl_repository=>get_object_capabilities
        EXPORTING
          iv_object_type iv_obl_type
        IMPORTING
          es_obl_obtyp   DATA(ls_obtype)
        EXCEPTIONS
          not_found      1.
      IF sy-subrc IS NOT INITIAL.
        RAISE EXCEPTION TYPE lcx_rest_nocust.
      ENDIF.

      IF ls_obtype-destination IS NOT INITIAL.
        cl_http_client=>create_by_destinationEXPORTING destination ls_obtype-destination IMPORTING client mo_http_client ).
      ELSEIF ls_obtype-web_server IS NOT INITIAL.
        cl_dpr_olr3_repository=>get_url_webserver_par(
          EXPORTING
            iv_web_server     =                  ls_obtype-web_server
          IMPORTING
            ev_web_server_url =                  DATA(lv_ws_url)
          EXCEPTIONS
            OTHERS            ).
        lv_service_url lv_ws_url.
        TRANSLATE lv_service_url TO LOWER CASE.
        CONCATENATE lv_service_url 'OData/Reporting.svc/projects?$top=10' INTO lv_service_url.
        cl_http_client=>create_by_urlEXPORTING url lv_service_url IMPORTING client mo_http_client ).
      ELSE.
        RETURN.
      ENDIF.

      IF iv_oauth2_profile IS NOT INITIAL.
        TRY.
            mo_oa2c_client cl_oauth2_client=>createiv_oauth2_profile ).
            mo_oa2c_client->set_tokenEXPORTING io_http_client mo_http_client  i_param_kind  'H' ).
            " DATA(lv_token) = mo_http_client->request->get_header_field( name = /iwcor/if_rest_request=>gc_header_csrf_token ).

          CATCH cx_oa2c_at_expired INTO DATA(lx_oa2c_at_expired).
            " Token expired -> trigger refresh flow.
            TRY.
                mo_oa2c_client->execute_refresh_flow).
              CATCH cx_oa2c_rt_expired INTO DATA(lx_oa2c_rt_expired).
                RAISE EXCEPTION TYPE lcx_oauth_grant_required EXPORTING previous lx_oa2c_rt_expired text_token get_redirect_urliv_oauth2_profile ).
            ENDTRY.
            TRY.
                mo_oa2c_client->set_tokenEXPORTING io_http_client mo_http_client i_param_kind 'H' ).
              CATCH cx_oa2c INTO lx_oa2c.
                " Error during refresh flow: Configuration issue?
                RAISE EXCEPTION TYPE lcx_rest_nocust EXPORTING previous lx_oa2c text_token lx_oa2c->get_text).
            ENDTRY.

          CATCH cx_oa2c_at_not_available INTO DATA(lx_oa2c_at).

            TRY.

                RAISE EXCEPTION TYPE lcx_oauth_grant_required EXPORTING previous lx_oa2c_at text_token get_redirect_urliv_oauth2_profile ).

              CATCH cx_oa2c INTO lx_oa2c.
                " Configuration issue
                RAISE EXCEPTION TYPE lcx_rest_nocust EXPORTING previous lx_oa2c text_token lx_oa2c->get_text).
            ENDTRY.

          CATCH cx_oa2c INTO lx_oa2c.
            lv_message_text lx_oa2c->get_text).
            RAISE EXCEPTION TYPE lcx_rest_nocust
              EXPORTING
                previous   lx_oa2c
                text_token lx_oa2c->get_text).
        ENDTRY.
      ENDIF.

      " Dark side of the force: named user authentication. Use for test purposes only.
      IF iv_username IS NOT INITIAL AND iv_password IS NOT INITIAL.
        mo_http_client->authenticateusername iv_username password iv_password ).
      ENDIF.

      " Use method HEAD for REST calls, try GET with $metadata for oData calls to save roundtrips
      mo_http_client->request->set_method/iwcor/if_rest_request=>gc_method_head ).
      mo_http_client->propertytype_logon_popup 0.
      mo_http_client->request->delete_header_fieldif_http_header_fields=>if_match ).
      mo_http_client->request->delete_header_field/iwcor/if_rest_request=>gc_header_method_override ).
      mo_http_client->request->set_methodif_http_request=>co_request_method_get ).
      mo_http_client->request->set_versionif_http_request=>co_protocol_version_1_1 ).
      DATA lv_language TYPE string.
      CALL FUNCTION 'CONVERSION_EXIT_ISOLA_OUTPUT'
        EXPORTING
          input  sy-langu
        IMPORTING
          output lv_language.
      mo_http_client->request->set_header_fieldEXPORTING name  if_http_header_fields=>accept_language value lv_language ).
      " mo_http_client->request->set_header_field( EXPORTING name  = /iwcor/if_rest_request=>gc_header_csrf_token value = 'Fetch' ).
      mo_http_client->propertytype_accept_cookie if_http_client=>co_enabled.

      mo_http_client->send).
      mo_http_client->receiveEXCEPTIONS http_communication_failure ).
      IF sy-subrc IS NOT INITIAL.
        RAISE EXCEPTION TYPE lcx_rest_noauth .
      ENDIF.

      mo_http_client->response->get_statusIMPORTING code DATA(lv_status_code).
      IF lv_status_code <> 200.
        DATA(lv_responsemo_http_client->response->get_cdata).
        RAISE EXCEPTION TYPE lcx_rest_noauth
          EXPORTING
            text_token lv_response.
      ENDIF.
      mv_xsrf_token mo_http_client->response->get_header_fieldname /iwcor/if_rest_request=>gc_header_csrf_token ).

    ENDIF.

    ev_xsrf_token mv_xsrf_token.
    rr_client mo_http_client.

  ENDMETHOD.

ENDCLASS.

CLASS lcl_json_parser DEFINITION.

  PUBLIC SECTION.
    CLASS-METHODS get_sydatum_from_json
      IMPORTING
        iv_json        TYPE string
      RETURNING
        VALUE(rv_dateTYPE sydatum .
    CLASS-METHODS get_enumeration_from_json
      IMPORTING
        iv_json             TYPE string
        iv_property         TYPE string
      RETURNING
        VALUE(rt_stringtabTYPE stringtab .
    CLASS-METHODS get_property_from_json
      IMPORTING
        iv_json         TYPE string
        iv_property     TYPE string
      RETURNING
        VALUE(rv_valueTYPE string .

  PRIVATE SECTION.
    CLASS-METHODS get_enum_member_from_json
      CHANGING
        cv_json         TYPE string
      RETURNING
        VALUE(rv_valueTYPE string .

ENDCLASS.

CLASS lcl_json_parser IMPLEMENTATION.

  METHOD get_sydatum_from_json.

    DATA:
      lv_day(2)   TYPE n,
      lv_month(2TYPE n,
      lv_year(4)  TYPE n.

    lv_day   get_property_from_jsoniv_json iv_json  iv_property 'day' ).
    lv_month get_property_from_jsoniv_json iv_json  iv_property 'month' ).
    lv_year  get_property_from_jsoniv_json iv_json  iv_property 'year' ).

    rv_date lv_year && lv_month && lv_day.

  ENDMETHOD.

  METHOD get_enumeration_from_json.

    DATA:
      lv_property   TYPE string,
      lv_trash      TYPE string,
      lv_enumstring TYPE string,
      lv_char       TYPE c,
      lv_counter    TYPE i,
      lv_pointer    TYPE i,
      lv_value      TYPE string,
      lv_is_value   TYPE abap_bool.

    CHECK iv_json IS NOT INITIAL.

    IF iv_property IS NOT INITIAL.
      lv_property '"' && iv_property && '": '.
      SPLIT iv_json AT lv_property INTO lv_trash lv_enumstring.
    ELSE.
      lv_enumstring iv_json.
    ENDIF.

    "Any enumeration starts with [ and ends with ], however, it can contain enumerations.
    IF lv_enumstring(1'['.
      DO.
        CASE lv_enumstring+lv_pointer(1).
          WHEN '['.
            IF lv_is_value abap_false.
              ADD TO lv_counter.
            ENDIF.
          WHEN ']'.
            IF lv_is_value abap_false.
              SUBTRACT FROM lv_counter.
              IF lv_counter 0.
                ADD TO lv_pointer"Make reault one character longer to not to loose content
                EXIT.
              ENDIF.
            ENDIF.
          WHEN '"'.
            data lv_pre_pointer like lv_pointer.
            lv_pre_pointer lv_pointer 1.
            if lv_enumstring+lv_pre_pointer(1<> '\'.
            " Do not count brackets in quotes (but ignore escaped quotes)
              IF lv_is_value abap_true.
                lv_is_value abap_false.
              ELSE.
                lv_is_value abap_true.
              ENDIF.
            endif.
        ENDCASE.
        IF lv_enumstring+lv_pointer IS INITIAL.
          EXIT.
        ENDIF.
        ADD TO lv_pointer.
      ENDDO.
      lv_enumstring lv_enumstring(lv_pointer).
      " Get rid of leading and terminating bracket
      SUBTRACT FROM lv_pointer.
      lv_enumstring lv_enumstring+1(lv_pointer).
    ENDIF.
    DO.
      DATA(lv_enum_memberget_enum_member_from_jsonCHANGING cv_json lv_enumstring ).
      IF lv_enum_member IS NOT INITIAL.
        APPEND lv_enum_member TO rt_stringtab.
      ELSE.
        EXIT.
      ENDIF.
    ENDDO.

  ENDMETHOD.

  METHOD get_enum_member_from_json.

    DATA:
      lv_property       TYPE string VALUE '"project": ',
      lv_trash          TYPE string,
      lv_propertystring TYPE string,
      lv_char           TYPE c,
      lv_counter        TYPE i,
      lv_pointer        TYPE i,
      lv_value          TYPE string.

    CHECK cv_json IS NOT INITIAL.
    lv_propertystring cv_json.

    TRY.
        IF lv_propertystring(1'{'.
          DO.
            CASE lv_propertystring+lv_pointer(1).
              WHEN '{'.
                ADD TO lv_counter.
              WHEN '}'.
                SUBTRACT FROM lv_counter.
                IF lv_counter 0.
                  ADD TO lv_pointer.
                  EXIT.
                ENDIF.
            ENDCASE.
            IF lv_propertystring+lv_pointer IS INITIAL.
              EXIT.
            ENDIF.
            ADD TO lv_pointer.
          ENDDO.
          lv_propertystring lv_propertystring(lv_pointer).
          rv_value lv_propertystring.
          cv_json cv_json+lv_pointer.
          IF cv_json IS NOT INITIAL AND cv_json(1','.
            cv_json cv_json+1.
          ENDIF.
        ENDIF.

      CATCH cx_sy_range_out_of_bounds.
        " Oooops: The enumeration ended unexpectedly. Too much data? Anyhow, for the time being, just exit silently
        CLEAR rv_value.
    ENDTRY.

  ENDMETHOD.

  METHOD get_property_from_json.

    DATA:
      lv_property          TYPE string VALUE '"project": ',
      lv_trash             TYPE string,
      lv_trail             TYPE string,
      lv_propertystring    TYPE string,
      lt_propertystringtab TYPE stringtab,
      lv_counter           TYPE i,
      lv_pointer           TYPE i,
      lv_len               TYPE i,
      lv_level             TYPE i,
      lv_lines             TYPE i.

    CHECK iv_json IS NOT INITIAL.

    lv_property '"' && iv_property && '": '.

    " In case of ambiguity: We want to have the value of the property being closest to root, see for example "key" below.
    " For the time being we assumre that enumerations are no issue.

    SPLIT iv_json AT lv_property INTO TABLE lt_propertystringtab.
    DESCRIBE TABLE lt_propertystringtab LINES lv_lines.
    IF lv_lines 2.
      " easy case: only one occurrence
      READ TABLE lt_propertystringtab INTO lv_propertystring INDEX 2.
    ELSEIF lv_lines 1.
      " rare case: Was the split criterion right at the start of the string? Be careful, normally at least a bracket is there
      IF iv_json CS lv_property.
        READ TABLE lt_propertystringtab INTO lv_propertystring INDEX 1.
      ELSE.
        rv_value ''.
        RETURN.
      ENDIF.
    ELSE.
      " In JSON format, emlement names are only unique when within the same bracket level. That means that a name can occur multiple times.
      " Here, we're only interested in the lowest level of the json, meaning that we ignore the second part of "fieldname":{"fieldname":"XYZ}
      DATA(lv_jsoniv_json.
      lv_pointer lv_level 0.
      DO.
        IF lv_json+lv_pointer IS INITIAL.
          EXIT.
        ENDIF.
        CASE lv_json+lv_pointer(1).
          WHEN '{'.
            ADD TO lv_level.
          WHEN '}'.
            SUBTRACT FROM lv_level.
          WHEN OTHERS.
            IF lv_level > 1.
              " direct write access isn't possible, so we help ourself using a helper variable and concatenation
              ADD TO lv_pointer.
              lv_trail lv_json+lv_pointer.
              SUBTRACT FROM lv_pointer.
              lv_json lv_json(lv_pointer&& '_' && lv_trail.
            ENDIF.
        ENDCASE.
        ADD TO lv_pointer.
      ENDDO.
      SPLIT lv_json AT lv_property INTO lv_json lv_trash.
      IF lv_trash IS INITIAL.
        "Uh, oh, the search string isn't even there on the required level: we can stop now, as this won't get any better
        RETURN.
      ENDIF.
      lv_pointer strlenlv_json strlenlv_property ).
      lv_propertystring iv_json+lv_pointer.
    ENDIF.

    lv_pointer lv_counter 0.
    IF lv_propertystring(1'{'.
      DO.
        CASE lv_propertystring+lv_pointer(1).
          WHEN '{'.
            ADD TO lv_counter.
          WHEN '}'.
            SUBTRACT FROM lv_counter.
            IF lv_counter 0.
              ADD TO lv_pointer.
              EXIT.
            ENDIF.
        ENDCASE.
        IF lv_propertystring+lv_pointer IS INITIAL.
          EXIT.
        ENDIF.
        ADD TO lv_pointer.
      ENDDO.
      lv_propertystring lv_propertystring(lv_pointer).
    ELSE.
      SPLIT lv_propertystring AT ',' INTO lv_propertystring lv_trash.
      SPLIT lv_propertystring AT '}' INTO lv_propertystring lv_trash.
      REPLACE ALL OCCURRENCES OF '"' IN lv_propertystring WITH ''.
    ENDIF.
    rv_value lv_propertystring.

  ENDMETHOD.

ENDCLASS.
  • No labels