Меню

OData_v2: моменты, которые важно знать

|

В предыдущей главе мы рассмотрели реализацию OData_v2-сервиса в ABAP через композицию. Продолжим разговор о важных деталях.

Часть 4.

Содержание

Реализация Function Imports

Реализация Navigation Properties для Expand and Deep Insert

Загрузка и выгрузка файла: выгрузка

Загрузка и выгрузка файла: загрузка

Создание OData-сервиса на основе CDS

Использование Complex Entity в SEGW

Создание сущности на основе средства поиска в SAP Gateway

Объединение сервисов SAP Gateway

Расширение OData-сервисов через SAP Gateway

Сброс кэша в OData и UI5

Debug-Mode и Трассировка вызовов OData-запросов

sap-ds-debug=true : расширенный технический режим

Трассировка запрос и логирование ошибок

Пример OData v4

Использование deltatoken в SAP Gateway

Реализация Function Imports

Function Imports (Actions) предполагаются к использованию там, где требуется выполнить определенную функцию, но которая не подошла к использованию в сущностях. Это может быть действие над сущностью или над группой сущностью; получение агрегированных данных или запуск специальных заданий (фоновых заданий, заданий по WorkFlow).

Function Imports определяются на уровне сервиса и не относятся напрямую к какой-либо отдельной сущности (но могут возвращать ее тип).

Для определения Function Import нужно выбрать Сервис -> Data Model -> Create -> Function Import:

Создадим функцию на включение для переменной режима Debug и назовем ее SwitchOnVarDebug.

Укажем, чтобы структуру возвращала заголовочные данные по переменной в ответ.

В поле Return Type Kind – укажем Entity Type и Return Type укажем VarH.

В качестве метода укажем метод GET. В данном случае GET и POST отличаются тем, что POST является более защищенным через cross-site. Если мы хотим просто считать данные, то предпочтительнее GET; если изменять данные, то POST.

В нашем случае мы хотим обновить данные, поэтому нужно ставить POST.

В Function Import можно добавлять параметры. В нашем случае в качестве параметра добавим имя переменной. Переходим в узел Function Import Parameters через двойной клик по узлу.

Нажимаем добавить параметр и вводим его свойства:

Сохраняем и пере-генерируем сервис.

Следующим шагом – нам нужно реализовать (переопределить) метод /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION в классе сервиса (в нашем случае – класс ZCL_ZWEB_ABAP_DEMO3_DPC_EXT).

Реализация метода /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION
Обратить внимание на разбор параметра IT_PARAMETER и использование метода copy_data_to_ref

METHOD /iwbep/if_mgw_appl_srv_runtime~execute_action.
*    importing
*      !IV_ACTION_NAME type STRING optional
*      !IT_PARAMETER type /IWBEP/T_MGW_NAME_VALUE_PAIR optional
*      !IO_TECH_REQUEST_CONTEXT type ref to /IWBEP/IF_MGW_REQ_FUNC_IMPORT optional
*    exporting
*      !ER_DATA type ref to DATA
*    raising
*      /IWBEP/CX_MGW_BUSI_EXCEPTION
*      /IWBEP/CX_MGW_TECH_EXCEPTION .

    DATA lr_parameter TYPE REF TO /iwbep/s_mgw_name_value_pair.
    DATA ls_var_h TYPE zcl_zweb_abap_demo3_mpc=>ts_varh.
    DATA ls_var_h_out TYPE zcl_zweb_abap_demo3_mpc=>ts_varh.

    CASE iv_action_name.
      WHEN 'SwitchOnVarDebug'.
        LOOP AT it_parameter REFERENCE INTO lr_parameter.
          CASE lr_parameter->name.
            WHEN 'VarID'.
              ls_var_h-name = lr_parameter->value.
          ENDCASE.
        ENDLOOP.

        DATA lt_varid_db TYPE ztwa001_varid_tab.
        DATA lt_var_val4db TYPE ztwa001_varval_tab.
        FIELD-SYMBOLS <fs_varid> TYPE ztwa001_varid.

        SELECT * FROM ztwa001_varid
            INTO TABLE lt_varid_db
            WHERE var_name = ls_var_h-name.

        LOOP AT lt_varid_db ASSIGNING <fs_varid>.
          <fs_varid>-is_debug_on = abap_true.
          MOVE-CORRESPONDING <fs_varid> TO ls_var_h_out.
          ls_var_h_out-name         = <fs_varid>-var_name.
          ls_var_h_out-description  = <fs_varid>-var_desc.
        ENDLOOP.
        IF sy-subrc NE 0.
          RETURN.
        ENDIF.

        NEW zcl_wa001_save_var_n_rng(  )->sh(  EXPORTING it_varid =  lt_varid_db
                                                         it_varval = lt_var_val4db ).

        copy_data_to_ref( EXPORTING is_data = ls_var_h_out
                          CHANGING  cr_data = er_data ).

      WHEN OTHERS.
    ENDCASE.
  ENDMETHOD.

Теперь протестируем Function Import через транзакцию /IWFND/GW_CLIENT - SAP Gateway Client.

Укажем метод POST
/sap/opu/odata/SAP/ZWEB_ABAP_DEMO3_SRV/SwitchOnVarDebug?VarID='ZFI_N11'

Система вернет статус 200.

В таблице увидим установленную отладку.

Иногда может возникать необходимость сделать параметры в FunctionImport необязательными для заполнения. Тогда в файл модели нужно внести дополнительные правки.

Сделаем необязательный параметр в нашем FunctionImport.

Тогда дополнение к *MPC_EXT будет выглядеть так.

Метод для вставки в переопределенный DEFINE

  METHOD correct_model4function_import.
    " https://blogs.sap.com/2017/12/14/implementing-optional-parameters-in-the-function-import/

    " Data Declarations
    DATA lo_entity_type TYPE REF TO /iwbep/if_mgw_odata_entity_typ.
    DATA lo_property    TYPE REF TO /iwbep/if_mgw_odata_property.
    DATA lo_action      TYPE REF TO /iwbep/if_mgw_odata_action.

    lo_action = model->get_action( iv_action_name = 'SwitchOnVarDebug' ).
    lo_property = lo_action->get_input_parameter( iv_name = 'OptionalPar' ).
    lo_property->set_nullable( iv_nullable = abap_true ).

  ENDMETHOD.

Реализация Navigation Properties для Expand and Deep Insert

Для того, чтобы иметь возможность делать Expand (читать связанные сущности) и Deep Insert (обновлять связанные сущности) – нужно сделать Navigation Properties.

Чтобы Navigation Property появилось – создадим Association в сервисе.

Появится первое окно Wizard и введем данные.

Затем указываем связь сущностей по полям.

Подтверждаем ввод:

Затем генерим сервис.

После этого появилось Navigation Property - VarID2Range.

Обратим внимание, что теперь при возврате данных по VarHSet в ответе появляется также указание на NavigationProperty:

Теперь считаем заголовок + range, который входит в этот заголовок.

Считанную вложенную структуру через Expanded Entity можно использовать в качестве образца в методе POST и тогда у нас будет вызван метод CREATE_DEEP_ENTITY (/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_DEEP_ENTITY) с вложенной структурой.

Реализация метода /IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_DEEP_ENTITY в классе ZCL_ZWEB_ABAP_DEMO3_DPC_EXT.
Обратим внимание, как собирается тип deep_entity, что в нем в качестве вложенного параметра имя NavigationProperty.
Также обратим внимание на параметр IO_EXPAND и возможность доступа к типам сущностей (их может быть множество).

Для возврата ответа используется метод COPY_DATA_TO_REF.

METHOD /iwbep/if_mgw_appl_srv_runtime~create_deep_entity.
**TRY.
*CALL METHOD SUPER->/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_DEEP_ENTITY
*  EXPORTING
**    iv_entity_name          =
**    iv_entity_set_name      =
**    iv_source_name          =
*    IO_DATA_PROVIDER        =
**    it_key_tab              =
**    it_navigation_path      =
*    IO_EXPAND               =
**    io_tech_request_context =
**  IMPORTING
**    er_deep_entity          =
*    .
** CATCH /iwbep/cx_mgw_busi_exception .
** CATCH /iwbep/cx_mgw_tech_exception .
**ENDTRY.

    TYPES: BEGIN OF ts_entry_deep.
            INCLUDE TYPE zcl_zweb_abap_demo3_mpc=>ts_varh.
    TYPES: varid2range TYPE STANDARD TABLE OF zcl_zweb_abap_demo3_mpc=>ts_vari WITH DEFAULT KEY
          , END OF ts_entry_deep.

    DATA ls_entry_deep TYPE ts_entry_deep.


    DATA lt_children TYPE /iwbep/if_mgw_odata_expand=>ty_t_node_children.
    DATA lv_child_entity TYPE string.
    FIELD-SYMBOLS <fs_child> TYPE /iwbep/if_mgw_odata_expand=>ty_s_node_child.
    lt_children = io_expand->get_children( ).
    LOOP AT lt_children ASSIGNING <fs_child>.
      lv_child_entity = <fs_child>-node->get_tech_entity_type( ).
    ENDLOOP.


    io_data_provider->read_entry_data( IMPORTING es_data = ls_entry_deep ).

    copy_data_to_ref(
      EXPORTING
        is_data = ls_entry_deep
      CHANGING
        cr_data = er_deep_entity
    ).

  ENDMETHOD.

Когда запустим с копированной expanded-структурой через метод POST:

POST
/sap/opu/odata/SAP/ZWEB_ABAP_DEMO3_SRV/VarHSet

Увидим, что данные корректно пришли и считались:

Теперь мы можем использовать вложенную структуру так, как нам нужно.

Загрузка и выгрузка файла: выгрузка

Для работы с бинарными данными существует специальный тип Media Type, который позволяет загружать и выгружать данные.

Вначале нам нужно создать новую сущность или скорректировать существующую. В нашем случае создадим новую сущность.

Создадим структуру для получения данных файла.

Сделаем сущность VarFile на основе созданной структуры.

Отметим все поля структуры для сущности:

В качестве ключа укажем VAR_NAME (в нашем случае: одна переменная – один файл) и CONTENT_DISPOSITION (чтобы иметь возможность варьировать отображение файла).

В созданной структуре отметим признак Media Type:

После этого сохраним и сгенерируем сервис.

Затем идем в класс модели (маска *MPC_EXT) – в нашем случае ZCL_ZWEB_ABAP_DEMO3_MPC_EXT.

Необходимо переопределить метод DEFINE:

Переопределение метода DEFINE в классе ZCL_ZWEB_ABAP_DEMO3_MPC_EXT:

METHOD define.
    super->define( ).

    set_property_as_content_type(  ).

  ENDMETHOD.

  METHOD set_property_as_content_type.
    DATA lo_entity_fs   TYPE REF TO /iwbep/if_mgw_odata_entity_typ.
    DATA lo_property_fs TYPE REF TO /iwbep/if_mgw_odata_property.

    lo_entity_fs = model->get_entity_type( iv_entity_name = 'VarFile' ).
    IF lo_entity_fs IS BOUND.
      lo_property_fs = lo_entity_fs->get_property( iv_property_name = 'FileContent' ).
      lo_property_fs->set_as_content_type( ).
    ENDIF.
  ENDMETHOD.

После этого идем в класс *DPC_EXT (в нашем случае ZCL_ZWEB_ABAP_DEMO3_DPC_EXT) и переопределяем два метода /IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_STREAM и /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM.

Сначала реализуем и протестируем метод-чтение файла.

/IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_STREAM

METHOD /iwbep/if_mgw_appl_srv_runtime~get_stream .
*          !IV_ENTITY_NAME type STRING optional
*      !IV_ENTITY_SET_NAME type STRING optional
*      !IV_SOURCE_NAME type STRING optional
*      !IT_KEY_TAB type /IWBEP/T_MGW_NAME_VALUE_PAIR optional
*      !IT_NAVIGATION_PATH type /IWBEP/T_MGW_NAVIGATION_PATH optional
*      !IO_TECH_REQUEST_CONTEXT type ref to /IWBEP/IF_MGW_REQ_ENTITY optional
*    exporting
*      !ER_STREAM type ref to DATA
*      !ES_RESPONSE_CONTEXT type /IWBEP/IF_MGW_APPL_SRV_RUNTIME=>TY_S_MGW_RESPONSE_ENTITY_CNTXT
*    raising
*      /IWBEP/CX_MGW_BUSI_EXCEPTION
*      /IWBEP/CX_MGW_TECH_EXCEPTION .

    DATA lr_key_tab_line TYPE REF TO /iwbep/s_mgw_name_value_pair.
    DATA ls_file_stream TYPE zswa003_var_file_stream.
    CONSTANTS lc_dispo_attachment TYPE zewa003_disposition VALUE '2'.

    """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    LOOP AT it_key_tab REFERENCE INTO  lr_key_tab_line.
      CASE lr_key_tab_line->name.
        WHEN 'VarName'.
          ls_file_stream-var_name = lr_key_tab_line->value.
        WHEN 'ContentDisposition'.
          ls_file_stream-content_disposition = lr_key_tab_line->value.
        WHEN OTHERS.
      ENDCASE.
    ENDLOOP.
    IF ls_file_stream-var_name IS INITIAL.
      RETURN.
    ENDIF.
    """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""


    """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    "" Откуда-то читаем файл{{{{
    DATA lv_file_xstring TYPE xstring.
    DATA lv_file_original_name TYPE string.
    DATA lv_file_mime TYPE string.

    """""~~~~~~~~~~~~"""~~~~~~~~~~~~"""~~~~~~~~~~~~"""~~~~~~~~~~~~"""
    DATA lo_file_mngr TYPE REF TO zcl_wa001_mngvar_file.
    lo_file_mngr = NEW #( ls_file_stream-var_name ).

    lo_file_mngr->get_file_with_info(
      IMPORTING
        ev_file_xstring       = lv_file_xstring
        ev_file_original_name = lv_file_original_name
        ev_file_mime          = lv_file_mime
    ).

    "" Откуда-то читаем файл }}}
    """""~~~~~~~~~~~~"""~~~~~~~~~~~~"""~~~~~~~~~~~~"""~~~~~~~~~~~~"""
    """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

    DATA ls_stream TYPE ty_s_media_resource.
    DATA ls_http_header TYPE ihttpnvp.

    DATA lv_disposition TYPE string.
    ls_http_header-name = 'Content-Disposition'.

    " " https://developer.mozilla.org/ru/docs/Web/HTTP/Headers/Content-Disposition
    IF ls_file_stream-content_disposition EQ lc_dispo_attachment.
      lv_disposition = 'attachment'.
    ELSE.
      lv_disposition = 'inline'.
    ENDIF.
    " escape актуально для кириллицы и умлаутов и прочего специфического не ASCII
    ls_http_header-value = |{ lv_disposition }; filename="{ escape( val = lv_file_original_name format = cl_abap_format=>e_url ) }"|.

    ls_stream-mime_type = lv_file_mime.
    ls_stream-value = lv_file_xstring.

    """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    "" отправляем ответ в OData
    set_header( is_header = ls_http_header ).
    copy_data_to_ref(  EXPORTING is_data = ls_stream
                       CHANGING  cr_data = er_stream ).
  ENDMETHOD.

А теперь проверим метод.

Обратим внимание на опцию $value
/sap/opu/odata/SAP/ZWEB_ABAP_DEMO3_SRV/VafFileSet(VarName='ZDMS_N12',ContentDisposition='2')/$value

Система отобразит файл:

Загрузка и выгрузка файла: загрузка

Реализуем метод /IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_STREAM. Обратим внимание, что CREATE_STREAM будет вызываться при HTTP-методе POST, а UPDATE_STREAM при PUT. Покажем на примере POST.

/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CREATE_STREAM.
Через параметр SLUG передается любая «полезная» информация для загрузки (как правило, имя файла). В нашем случаем передадим [VAR_ID]$[имя файла] и считаем эти данные.
Также доп.параметры можно передавать через Header-Parameters и считывать их внутри (пример add_param).

METHOD /iwbep/if_mgw_appl_srv_runtime~create_stream .
*    importing
*      !IV_ENTITY_NAME type STRING optional
*      !IV_ENTITY_SET_NAME type STRING optional
*      !IV_SOURCE_NAME type STRING optional
*      !IS_MEDIA_RESOURCE type TY_S_MEDIA_RESOURCE
*      !IT_KEY_TAB type /IWBEP/T_MGW_NAME_VALUE_PAIR optional
*      !IT_NAVIGATION_PATH type /IWBEP/T_MGW_NAVIGATION_PATH optional
*      !IV_SLUG type STRING
*      !IO_TECH_REQUEST_CONTEXT type ref to /IWBEP/IF_MGW_REQ_ENTITY_C optional
*    exporting
*      !ER_ENTITY type ref to DATA
*    raising
*      /IWBEP/CX_MGW_BUSI_EXCEPTION
*      /IWBEP/CX_MGW_TECH_EXCEPTION .

    DATA ls_var_file_response TYPE zswa003_var_file_stream.
    DATA ls_varfile_db TYPE ztwa001_varfile.

    DATA lv_var_name TYPE zewa001_var_name.
    DATA lv_var_name_add_param TYPE zewa001_var_name.
    DATA lv_file_name TYPE string.

    SPLIT iv_slug AT '$' INTO lv_var_name lv_file_name.

    IF lv_var_name IS INITIAL.
      RETURN.
    ENDIF.

    DATA lo_file_mngr TYPE REF TO zcl_wa001_mngvar_file.
    DATA lv_rc TYPE sysubrc.
    lo_file_mngr = NEW #( lv_var_name ).
    lo_file_mngr->upload_file_from_odata(
      EXPORTING
        iv_file_orig_name = lv_file_name
        iv_file_xstring   = is_media_resource-value
        iv_mime_type      = is_media_resource-mime_type
      IMPORTING
        ev_rc             = lv_rc ).

    IF lv_rc IS INITIAL.
      RETURN.
    ENDIF.

    """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
    DATA ls_technical_request TYPE /iwbep/if_mgw_core_srv_runtime=>technical_request_s.
    ls_technical_request = me->mr_request_details->technical_request.
    lv_var_name_add_param = VALUE #( ls_technical_request-request_header[ name = 'add_param' ]-value OPTIONAL ).
    """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

    ls_var_file_response-file_name = lv_file_name.

    copy_data_to_ref( EXPORTING is_data = ls_var_file_response
                       CHANGING cr_data = er_entity  ).

  ENDMETHOD.

Ссылка
POST
/sap/opu/odata/SAP/ZWEB_ABAP_DEMO3_SRV/VafFileSet

Нажимаем Add File и выбираем файл.

Система определит его Content-Type:

Также добавим параметры, а затем нажмем Execute:

SLUG: ZSD_N10$FileNameOriginal.jpeg
add_param: any_value_in_param

После этого в методе CREATE_STREAM сможем выполнять нужные действия с файлом:

В случае успешной обработки – получим ответ 201.

Если хотите прочитать статью полностью и оставить свои комментарии присоединяйтесь к sapland

У вас уже есть учетная запись?

Войти