SAP BLOG How to post SAC API output into BW/4HANA using ABAP

SAP Blog

Kayıtlı Üye
Katılım
22 Ara 2017
Mesajlar
1,925
Tepki puanı
7
Puanları
6

1. Introduction​


This simple tutorial should give you an idea of how to consume SAC APIs in ABAP and post the result to BW ADSO.

SAP exposes a few data-export APIs. API documentation can be found in the following links;

2. Use-case​

  • Store changelog for SAC metadata
  • Enhance existing BW models with SAC metadata
  • Consume SAC APIs through ABAP using OAuth 2.0

3. Implementation​

3.1 SAC​


Create an OAuth 2.0 client in SAC as described in https://help.sap.com/docs/…Manage OAuth Clients. Make sure to tick the Interactive Usage and API Access.

This client will provide an authorization token for BW based on the Agent secret.

3.2 ABAP / OAuth​


Follow the tutorial in the following blog to configure the OAuth2.0 Client Configuring OAuth 2.0 and Creating an ABAP Program That Uses OAuth 2.0 Client API | SAP Blogs. You will find all the details that you need to provide in part 3.1 of this Blog.

The Postman part is optional but recommended to test your API before consuming it from ABAP.

Remarks:

  • Leave the Scope empty.
  • Do not add https/ where it’s already provided.
  • If SAML 2.0 Audience is greyed out, change Grant Type from Client Credentials to Current user related. Fill in the OA2C_CONFIG and change it back to Client Credentials.
  • Fill in the proxy Host and Port. If you do not know, ask your BASIS administrator.

Official documentation: https://help.sap.com/docs/SAP_NETWEAVER_750/…Configuring OAuth 2.0 for AS ABAP.

Make sure you have the following authorizations assigned: Configuring the Role of the Resource Owner for OAuth 2.0.

3.3 SAML​


Enter the STRUST transaction and upload the certificates of your SAC tenant. Note later on SSL_ID defaults to ANONYM and it cannot be changed. It is a hardcoded value in the CREATE_HTTP_CLIENT method. You need to upload the certificates into the folder: SSL client SSL Client (Anonymous). If you do not know how to download missing certificates, go to the SAC page, click the locker icon at the beginning of the search bar, click Connection is secure, Certificate is valid, Details, copy to file. Do the same for the API page. Upload.

3.4 ABAP​


Create a new class. Add a method to set the SAC URL (me->lv_url) based on the BW system.
Add a method to set the OAuth. Change the ##INPUT to your settings. Add error handling class, the code below does not include it.

Kod:
    " Create HTTP client and set up the proxy, use DFAULT SSL certificates (added in STRUST)
    cl_http_client=>create_by_url(
        EXPORTING
            url = me->lv_url
            ssl_id = 'DFAULT'
            proxy_host = '##INPUT'
            proxy_service = '##INPUT'
        IMPORTING
            client = lo_http_client  ).


    lo_http_client->propertytype_logon_popup = 0.
    lo_http_client->request->set_method( EXPORTING method = 'GET' ).


    " Add headers required by SAC API for oAuth connection
    lo_http_client->request->set_header_field( name = 'x-sap-sac-custom-auth' value = 'true' ).
    lo_http_client->request->set_header_field( name = 'x-csrf-token' value = 'fetch' ).


    " Set oAuth 2.0
    TRY.
        cl_oauth2_client=>create(
          EXPORTING
            i_profile        = '##INPUT'
            i_configuration  = '##INPUT'
          RECEIVING
            ro_oauth2_client = DATA(lo_a2c_client)
        ).
      CATCH cx_oa2c INTO DATA(lx_oa2c).
        WRITE: 'Error calling create.'.
        WRITE: / lx_oa2c->get_text( ).
        RETURN.
    ENDTRY.


    TRY.


        lo_a2c_client->set_token(
          EXPORTING
            io_http_client = lo_http_client
            i_param_kind   = lc_param_kind
        ).


      CATCH cx_oa2c INTO lx_oa2c.


        " Execute Client Credentials Flow before Token
        TRY.
            lo_a2c_client->execute_cc_flow( ).
          CATCH cx_oa2c INTO lx_oa2c.
            WRITE: 'Error calling create.'.
            WRITE: / lx_oa2c->get_text( ).
            RETURN.
        ENDTRY.


        " Initiate the token
        TRY.
            lo_a2c_client->set_token(
              EXPORTING
                io_http_client = lo_http_client
                i_param_kind   = lc_param_kind
            ).
          CATCH cx_oa2c INTO lx_oa2c.
            WRITE: 'Error calling create.'.
            WRITE: / lx_oa2c->get_text( ).
            RETURN.
        ENDTRY.


    ENDTRY.

Create a method to get the data.

Kod:
    " Send / Receive request
    lo_http_client->send(
      EXPORTING
        timeout                    = 9999
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3
        http_invalid_timeout       = 4
        OTHERS                     = 5
    ).
    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
        WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.


    lo_http_client->receive(
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3
        OTHERS                     = 4
    ).
    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
        WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.


    "Display result
    lo_http_client->response->get_status(
          IMPORTING
            code   = lv_status_code
            reason = lv_reason
    ).


    " Request response received OK
    IF lv_status_code = 200.
      CALL METHOD lo_http_client->response->get_cdata
        RECEIVING
          data = lv_response_data.
    ENDIF.


    " Close
    lo_http_client->close(
      EXCEPTIONS
        http_invalid_state = 1
        OTHERS             = 2
    ).
    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
        WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

Create a method to parse the result. This is an old-school way. Follow up [ABAP] JSON to ABAP with dereferencing – nocin.eu if you would like to do it in a more modern way.

Kod:
   FIELD-SYMBOLS:
      <fs_table>      TYPE ANY TABLE,
      <fs_models_tab> TYPE ANY TABLE,
      <fs_data>       TYPE data,
      <field_value>   TYPE data.


    " Add header so that the JSON can be parsed via dynamic ABAP
    lv_response_data = |\{"d":{ lv_response_data }\}|.


    CALL METHOD /ui2/cl_json=>deserialize
      EXPORTING
        json         = lv_response_data
        pretty_name  = /ui2/cl_json=>pretty_mode-user
        assoc_arrays = abap_true
      CHANGING
        data         = lr_data.


    " Process the parsed result into ABAP table
    IF lr_data IS BOUND.
      ASSIGN lr_data->* TO <fs_data>.


      " ---------------------
      " Lookup story metadata
      " ---------------------
      ASSIGN COMPONENT 'd' OF STRUCTURE <fs_data> TO FIELD-SYMBOL(<fs_results>).
      ASSIGN <fs_results>->* TO <fs_table>.


      LOOP AT <fs_table> ASSIGNING FIELD-SYMBOL(<fs_table_row>).
        ASSIGN <fs_table_row>->* TO FIELD-SYMBOL(<data>).


        ASSIGN COMPONENT 'NAME' OF STRUCTURE <data> TO FIELD-SYMBOL(<field>).
        IF <field> IS ASSIGNED.
          lr_data = <field>.
          ASSIGN lr_data->* TO <field_value>.
          ls_parsed_result_story-name = <field_value>.
        ENDIF.
        UNASSIGN: <field>, <field_value>.


        ASSIGN COMPONENT 'DESCRIPTION' OF STRUCTURE <data> TO <field>.
        IF <field> IS ASSIGNED.
          lr_data = <field>.
          ASSIGN lr_data->* TO <field_value>...

   
        " --------------
        " Lookup models
        " --------------
        ASSIGN COMPONENT 'MODELS' OF STRUCTURE <data> TO FIELD-SYMBOL(<fs_models>).
        ASSIGN <fs_models>->* TO <fs_models_tab>.


        LOOP AT <fs_models_tab> ASSIGNING FIELD-SYMBOL(<fs_models_row>).
          ASSIGN <fs_models_row>->* TO FIELD-SYMBOL(<data_models>).


          " Add story metadata
          ls_parsed_result_stor_x_models = CORRESPONDING #( ls_parsed_result_story ).


          " Assign elements of the model table
          ASSIGN COMPONENT 'ID' OF STRUCTURE <data_models> TO <field>.
          IF <field> IS ASSIGNED.
            lr_data = <field>.
            ASSIGN lr_data->* TO <field_value>.
            ls_parsed_result_stor_x_models-model_id = <field_value>.
          ENDIF.
          UNASSIGN: <field>, <field_value>.


          ASSIGN COMPONENT 'DESCRIPTION' OF STRUCTURE <data_models> TO <field>.
          IF <field> IS ASSIGNED.
            lr_data = <field>...
 
          UNASSIGN: <field>, <field_value>.


          " --------------
          " Remote connection information is a nested structure
          " --------------
          ASSIGN COMPONENT 'REMOTECONNECTION' OF STRUCTURE <data_models> TO FIELD-SYMBOL(<fs_models_conn>).

          " Some models don't have remote connection information
          IF <fs_models_conn> IS ASSIGNED.


            ASSIGN <fs_models_conn>->* TO FIELD-SYMBOL(<fs_models_conn_struc>).


            ASSIGN COMPONENT 'HOST' OF STRUCTURE <fs_models_conn_struc> TO <field>.
            IF <field> IS ASSIGNED.
              lr_data = <field>.
              ASSIGN lr_data->* TO <field_value>.
              ls_parsed_result_stor_x_models-model_remoteconnection_host = <field_value>.
            ENDIF.
            UNASSIGN: <field>, <field_value>.


            ASSIGN COMPONENT 'NAME' OF STRUCTURE <fs_models_conn_struc> TO <field>.
            IF <field> IS ASSIGNED.
              lr_data = <field>.
              ASSIGN lr_data->* TO <field_value>.
              ls_parsed_result_stor_x_models-model_remoteconnection_name = <field_value>.
            ENDIF.
            UNASSIGN: <field>, <field_value>.


          ENDIF.


          " Add story metadata with current model to the result set and clear for the next model
          APPEND ls_parsed_result_stor_x_models TO lt_parsed_result.
          CLEAR ls_parsed_result_stor_x_models.


          " Clean main structure for the next story after the last model of the current story
          AT LAST.
            CLEAR ls_parsed_result_story.
          ENDAT.


        ENDLOOP.

      ENDLOOP.


    ENDIF.

Depending on your need, add a method adding a result timestamp to the output.

Kod:
    MODIFY lt_parsed_result FROM VALUE #(
      request_date = sy-datum
      request_time = sy-uzeit
    )
    TRANSPORTING
      request_date
      request_time
    WHERE
      id IS NOT INITIAL.

Post the data back to BW ADSO. It has to have the exact same structure as your internal table.

Kod:
   CALL FUNCTION 'RSDSO_WRITE_API'
      EXPORTING
        i_adsonm              = lc_adso_sac_metadata
        i_allow_new_sids      = rs_c_true
        i_activate_data       = rs_c_false
        it_data               = lt_parsed_result
      IMPORTING
        e_lines_inserted      = e_lines_inserted
        e_cold_lines_inserted = e_cold_lines_inserted
        et_msg                = et_msg
        e_upd_req_tsn         = e_upd_req_tsn
        et_act_req_tsn        = et_act_req_tsn
      EXCEPTIONS
        write_failed          = 1
        activation_failed     = 2
        datastore_not_found   = 3
        OTHERS                = 4.
    rc = sy-subrc.
    IF sy-subrc <> 0.
      MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
    ENDIF.

3.5 BW​


Depending on the use case, I would recommend posting the data to the staging ADSO with an inbound table only. It is PSA-like, so you can use a full load based on the timestamp and delete old requests. Then process it to the staging ADSO with a snapshot function, and to the time-dependent InfoObjects. Apply custom logic where needed.

You can wrap your class into a SE38 program. Then schedule as the first step of the process chain.

Okumaya devam et...
 
Üst