OData_v2: моменты, которые важно знать
В предыдущей главе мы рассмотрели реализацию OData_v2-сервиса в ABAP через композицию. Продолжим разговор о важных деталях.
Содержание
Реализация Navigation Properties для Expand and Deep Insert
Загрузка и выгрузка файла: выгрузка
Загрузка и выгрузка файла: загрузка
Создание OData-сервиса на основе CDS
Использование Complex Entity в SEGW
Создание сущности на основе средства поиска в SAP Gateway
Объединение сервисов SAP Gateway
Расширение OData-сервисов через SAP Gateway
Debug-Mode и Трассировка вызовов OData-запросов
sap-ds-debug=true : расширенный технический режим
Трассировка запрос и логирование ошибок
Использование 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
ЗарегистрироватьсяУ вас уже есть учетная запись?
Войти