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
ЗарегистрироватьсяУ вас уже есть учетная запись?
Войти