Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
kenichi_unnai
Advisor
Advisor

Please read this introductory page explaining why we need to know how to implement OData $batch processing with Content ID.


In this H2G, we're going through step-by-step implementation of building OData $batch services with a a set of simple header & item ABAP database tables.


Table of Contents (Estimated Time)


  1. Define simple header & item tables (10 mins.)
  2. Creating association (= navigation resource path) (5 mins.)
  3. Build CRUD services for each entity (20 mins.)
  4. Enable $batch (changeset_begin/changeset_end) (10 mins.)
  5. The $batch implementation to handle Content ID (changeset_process) (20 mins.)
  6. Test it
  7. Appendix: HeaderSet & ItemSet code
  8. Appendix: changeset_process code

Define simple header & item tables


Let's create a set of header & item database tables in SE11. For the sake of the simplicity, here's a very simple design to demonstrate the idea.


(Note: In a real use case, add "MANDT" field for both zheader and zitem)

ZHEADER - ID is a key and TEXT is a small text value.

ZITEM - PARENT_ID (= ID of ZHEADER) and ID are the keys and TEXT is a small text value.

Once you created the ZHEADER and ZITEM database tables, go to the tx SEGW and create a project (this case the name is Z_CONTENTID). Right click on the Data Model node and select Import > DDIC Structure.

Type in the Name field - "Header" - this is the exact name of the OData entity. And ABAP Structure is zheader.

Select all the field to expose as OData.

Tick in ID row as "Is Key".

You created Header entity, the next one is Item. Right click on the Data Model node and select Import > DDIC Structure.

Type in the Name field - "Item" - And ABAP Structure is zitem.

Select all the field to expose as OData.

Tick in PARENT_ID and ID row as "Is Key".

You have created HeaderSet and ItemSet entities!

Back to Top

Creating association (= navigation resource path)


We're going to create a navigation resource path from Header to Item. More precisely - once we configure it, a Header entity (parent) can follow its Item entities (children)  by using the navigation path link such as HeaderSet('0000000001')/ToItems .

Right click on Associations > Create.

Name it as "HeaderItemAssociation" and select Entity Type Name for Header and Item. Be sure to set the Cardinality value as 1:n. You set the Navigation Property value as "ToItems" that will be displayed in the OData payload.

Select Dependent Property value as "ParentId" - now Header and Item is linked together with the key fields.

Confirm the default value and save it.

Now you have done with the Navigation Properties that navigates from Header to Item(s). The relationship is going to be calculated by the GW framework and rendered in an OData payload. (ex. HeaderSet('0000000001')/ToItems will return relevant ItemSet entities)

Back to Top

Build CRUD services for each entity


Time to build CRUD operations for each entity. First off, let's generate the base code by selecting Generate Runtime.

And - register the OData services by clicking on Service Maintenance > (Your GW) > Register.

Now your OData services became callable from HTTP client.


As described in the prerequisite section, you should have a skillset of building basic CRUD OData services with SAP Gateway Workbench (tx code: SEGW) as explained here: #1 - OData CRUD Crash Course - Getting ready with offline store .


At least we need Query & Read operation for header and item respectively  - we will use them during the test step later. The code is written in the appendix.


Back to Top

Enable $batch (changeset_begin/changeset_end)


Note: - the official SAP Gateway documentation about $batch processing about changeset_begin/end/process.


Let's implement $batch specific methods. Redefine CHANGESET_BEGIN of "/IWBEP/IF_MGW_APPL_SRV_RUNTIME".

01 METHOD /iwbep/if_mgw_appl_srv_runtime~changeset_begin.
02
03   DATA ls_operation_info LIKE LINE OF it_operation_info.
04
05* set cv_defer_mode as TRUE to call changeset_process method
06    LOOP AT it_operation_info INTO ls_operation_info.
07      IF ls_operation_info-content_id IS NOT INITIAL OR
08         ls_operation_info-content_id_ref IS NOT INITIAL.
09        cv_defer_mode = abap_true.
10      ENDIF.
11    ENDLOOP.
12
13 ENDMETHOD.

The very important thing we need to understand here is once you redefine the changeset_begin, it will start the Logical Unit of Work (LUW) in ABAP that essentially means a transaction scope. And by setting the cv_defer_mode as TRUE, it will call the changeset_process method that will be explained in the next section.


The transaction scope means that in case of any failure happens during the changeset_process, all the database update will be rolled back.


Redefine CHANGESET_END of "/IWBEP/IF_MGW_APPL_SRV_RUNTIME".

01 METHOD /iwbep/if_mgw_appl_srv_runtime~changeset_end.
02
03 ENDMETHOD.

Once the logic reaches the changeset_end, the COMMIT WORK will be automatically issued by the SAP Gateway framework.


Back to Top

The $batch implementation to handle Content ID (changeset_process)


Note: - the official SAP Gateway documentation about $batch processing about changeset_begin/end/process.


Redefine CHANGESET_PROCESS of "/IWBEP/IF_MGW_APPL_SRV_RUNTIME".

What the implementation needs to do is as follows:


  1. Obtain a changeset as an internal table "it_changeset_request".
  2. Loop it - and pick up each change request (= create request of each entity).
  3. When it is Header entity - create a header entity.
  4. When it is Item entity - create an item entity.

That's all. It is fairly simple - and I need to add the detailed explanation during the step 4. As we know the parent has Content ID, so the child needs to look up the Content ID and replace it with the finalized parent key. Here's the code to implement the step 4:


Note: - the entire code is in the appendix.


Note: - this is demonstrating the parent-child relationship, however you can implement any level of relationship (such as parent-child-grandchild-..) by using Content ID.


056            WHEN 'Item'.
057* Item entity
058              ls_changeset_request-entry_provider->read_entry_data( IMPORTING es_data = ls_item ).
059
060              IF ls_changeset_request-content_id_ref IS NOT INITIAL.
061* look up the parent by content id
062                READ TABLE it_changeset_request INTO ls_changeset_req_parent WITH KEY content_id = ls_changeset_request-content_id_ref.
063
064                IF ( sy-subrc = 0 ).
065                  READ TABLE ct_changeset_response INTO ls_changeset_resp_parent WITH TABLE KEY operation_no = ls_changeset_req_parent-operation_no.
066                  IF ( sy-subrc = 0 ).
067                    ASSIGN ls_changeset_resp_parent-entity_data->* TO .
068                    IF <ls_header>-id IS NOT INITIAL.
069* header key value is obtained for items
070                      ls_item-parent_id = <ls_header>-id.
071
072                      INSERT INTO zitem VALUES ls_item.
073
074                      IF ( sy-subrc = 0 ).
075* entity inserted
076                        copy_data_to_ref(
077                           EXPORTING
078                             is_data = ls_item
079                           CHANGING
080                             cr_data = ls_changeset_response-entity_data ).
081                      ELSE.
082* entity alredy exists - $batch will be rolled back
083                        CONCATENATE lv_entity_type
084                                    '('''
085                                    ls_item-parent_id
086                                    ','''
087                                    ls_item-id
088                                    ''')'
089                          INTO lv_error_entity.
090                        RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
091                          EXPORTING
092                            textid      = /iwbep/cx_mgw_busi_exception=>resource_duplicate
093                            entity_type = lv_error_entity.
094                      ENDIF.
095
096                      ls_changeset_response-operation_no = ls_changeset_request-operation_no.
097                      INSERT ls_changeset_response INTO TABLE ct_changeset_response.
098
099                    ENDIF.
100                  ENDIF.
101                ENDIF.
102              ENDIF.
103          ENDCASE. "end of lv_entity_type

#056 - case block if the entity is "Item".

#058 - read the payload data of Item entity.

#060 - check if the Item entity has "Content ID Reference" value (= the value should exist if the POST request is done with the $ ID)

#062 - if the Item has the reference value - the parent should have the value too.

#064 - should be TRUE if a related parent is found.

#065 - ct_changeset_response should contain a parent. Pick up the parent value - it should contain the finalized key.

#067 - load the parent data onto the ls_header structure.

#068 - the Header entity should contain the finalized key named "id".

#070 - set the parent's id to the child's "parent_id" field. This links both the parent and the child together.

#072 - insert the value in the zitem ABAP database table.

#074 to 080 - insert goes fine - returning the OData entity for "HTTP 201 Created".

#081 to 094 - insert failed - throw the Duplicate exception. The entire $batch request will roll back.

#096 to 097 - prepare the changeset response.


I believe you got a very clear idea how Content ID and Content ID reference value work in the $batch processing to glue the parent and children together.


Back to Top

Test it


Are you ready for testing the $batch service? That's the next step.


Back to Top


Appendix: HeaderSet & ItemSet code


HeaderSet entity CRUD implementation:

   Naming convention:
   l - local scope
   t - table
   s - structure
   v - variable
   o - object

01 METHOD headerset_get_entityset.
02
03   DATA: lt_entityset TYPE TABLE OF zheader.
04
05   SELECT * FROM zheader INTO TABLE lt_entityset.
06
07   et_entityset = lt_entityset.
08
09 ENDMETHOD.
01 METHOD headerset_get_entity.
02
03   DATA: ls_key_tab   TYPE /iwbep/s_mgw_name_value_pair,
04         lv_id        TYPE vbeln,
05         ls_entityset TYPE zheader.
06
07   READ TABLE it_key_tab INTO ls_key_tab INDEX 1.
08   UNPACK ls_key_tab-value TO lv_id. "Converts 10 digits value
09
10   SELECT SINGLE * FROM zheader INTO ls_entityset WHERE id = lv_id.
11
12   er_entity = ls_entityset.
13
14  ENDMETHOD.
01 METHOD headerset_create_entity.
02
03   DATA: ls_entityset    TYPE zheader,
04         lv_error_entity TYPE string.
05
06   io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ).
07
08   INSERT INTO zheader VALUES ls_entityset.
09
10   IF ( sy-subrc = 0 ).
11* entity inserted
12     er_entity = ls_entityset.
13   ELSE.
14* entity alredy exists
15     CONCATENATE iv_entity_name
16                 '('''
17                 ls_entityset-id
18                 ''')'
19       INTO lv_error_entity.
20     RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
21       EXPORTING
22         textid      = /iwbep/cx_mgw_busi_exception=>resource_duplicate
23         entity_type = lv_error_entity.
24   ENDIF.
25
26 ENDMETHOD.
01 METHOD headerset_update_entity.
02
03   DATA: ls_entityset    TYPE zheader,
04         ls_key_tab      TYPE /iwbep/s_mgw_name_value_pair,
05         lv_id           TYPE vbeln,
06         lv_error_entity TYPE string.
07
08   io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ).
09
10* key is id
11   READ TABLE it_key_tab INTO ls_key_tab INDEX 1.
12   UNPACK ls_key_tab-value TO lv_id.
13
14* make sure the value matches with the one in OData payload
15   IF lv_id EQ ls_entityset-id.
16
17* new data
18     UPDATE zheader FROM ls_entityset.
19
20     IF ( sy-subrc = 0 ).
21* entity found and updated
22       er_entity = ls_entityset.
23     ELSE.
24* entity not found
25       CONCATENATE iv_entity_name
26                   '('''
27                   ls_key_tab-value
28                   ''')'
29         INTO lv_error_entity.
30       RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
31         EXPORTING
32           textid      = /iwbep/cx_mgw_busi_exception=>resource_not_found
33           entity_type = lv_error_entity.
34     ENDIF.
35   ENDIF.
36
37 ENDMETHOD.
01 METHOD headerset_delete_entity.
02
03   DATA: ls_entityset    TYPE zheader,
04         ls_key_tab      TYPE /iwbep/s_mgw_name_value_pair,
05         lv_id           TYPE vbeln,
06         lv_error_entity TYPE string.
07
08* key is id
09   READ TABLE it_key_tab INTO ls_key_tab INDEX 1.
10   UNPACK ls_key_tab-value TO lv_id.
11
12   DELETE FROM zheader WHERE id = lv_id.
13
14   IF ( sy-subrc = 0 ).
15* delete completed
16     ELSE.
17* entity not found
18     CONCATENATE iv_entity_name
19                 '('''
20                 ls_key_tab-value
21                 ''')'
22       INTO lv_error_entity.
23     RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
24       EXPORTING
25         textid      = /iwbep/cx_mgw_busi_exception=>resource_not_found
26         entity_type = lv_error_entity.
27   ENDIF.
28
29 ENDMETHOD.

ItemSet entity CRUD implementation:

01 METHOD itemset_get_entityset.
02
03   DATA: lt_entityset TYPE TABLE OF zitem.
04
05   SELECT * FROM zitem INTO TABLE lt_entityset.
06
07   et_entityset = lt_entityset.
08
09 ENDMETHOD.
01 METHOD itemset_get_entity.
02
03   DATA: ls_key_tab   TYPE /iwbep/s_mgw_name_value_pair,
04         lv_parent_id TYPE vbeln,
05         lv_id        TYPE posnr,
06         ls_entityset TYPE zitem.
07
08   READ TABLE it_key_tab INTO ls_key_tab INDEX 1.
09   UNPACK ls_key_tab-value TO lv_parent_id.
10   READ TABLE it_key_tab INTO ls_key_tab INDEX 2.
11   UNPACK ls_key_tab-value TO lv_id.
12
13   SELECT SINGLE * FROM zitem INTO ls_entityset WHERE parent_id = lv_parent_id AND id = lv_id.
14
15   er_entity = ls_entityset.
16
17 ENDMETHOD.
01 METHOD itemset_create_entity.
02
03   DATA: ls_entityset    TYPE zitem,
04         lv_error_entity TYPE string.
05
06   io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ).
07
08   INSERT INTO zitem VALUES ls_entityset.
09
10   IF ( sy-subrc = 0 ).
11* entity inserted
12     er_entity = ls_entityset.
13   ELSE.
14* entity alredy exists
15     CONCATENATE iv_entity_name
16                 '('''
17                 ls_entityset-parent_id
18                 ','''
19                 ls_entityset-id
20                 ''')'
21       INTO lv_error_entity.
22     RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
23       EXPORTING
24         textid      = /iwbep/cx_mgw_busi_exception=>resource_duplicate
25         entity_type = lv_error_entity.
26   ENDIF.
27
28 ENDMETHOD.
01 METHOD itemset_update_entity.
02
03   DATA: ls_entityset    TYPE zitem,
04         ls_key_tab      TYPE /iwbep/s_mgw_name_value_pair,
05         lv_parent_id    TYPE vbeln,
06         lv_id           TYPE posnr,
07         lv_error_entity TYPE string.
08
09   io_data_provider->read_entry_data( IMPORTING es_data = ls_entityset ).
10
11* key is parent_id and id
12   READ TABLE it_key_tab INTO ls_key_tab INDEX 1.
13   UNPACK ls_key_tab-value TO lv_parent_id.
14   READ TABLE it_key_tab INTO ls_key_tab INDEX 2.
15   UNPACK ls_key_tab-value TO lv_id.
16
17* make sure the value matches with the one in OData payload
18   IF lv_parent_id EQ ls_entityset-parent_id AND lv_id EQ ls_entityset-id.
19
20* new data
21     UPDATE zitem FROM ls_entityset.
22
23     IF ( sy-subrc = 0 ).
24* entity found and updated
25       er_entity = ls_entityset.
26     ELSE.
27* entity not found
28       CONCATENATE iv_entity_name
29                   '('''
30                   lv_parent_id
31                   ','''
32                   lv_id
33                   ''')'
34            INTO lv_error_entity.
35       RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
36         EXPORTING
37           textid      = /iwbep/cx_mgw_busi_exception=>resource_not_found
38           entity_type = lv_error_entity.
39     ENDIF.
40   ENDIF.
41
42 ENDMETHOD.
01 METHOD itemset_delete_entity.
02
03   DATA: ls_entityset    TYPE zitem,
04         ls_key_tab      TYPE /iwbep/s_mgw_name_value_pair,
05         lv_parent_id    TYPE vbeln,
06         lv_id           TYPE posnr,
07         lv_error_entity TYPE string.
08
09* key is parent_id and id
10   READ TABLE it_key_tab INTO ls_key_tab INDEX 1.
11   UNPACK ls_key_tab-value TO lv_parent_id.
12   READ TABLE it_key_tab INTO ls_key_tab INDEX 2.
13   UNPACK ls_key_tab-value TO lv_id.
14
15   DELETE FROM zitem WHERE parent_id = lv_parent_id AND id = lv_id.
16
17   IF ( sy-subrc = 0 ).
18* delete completed
19   ELSE.
20* entity not found
21     CONCATENATE iv_entity_name
22                 '('''
23                 lv_parent_id
24                 ','''
25                 lv_id
26                 ''')'
27        INTO lv_error_entity.
28     RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
29       EXPORTING
30         textid      = /iwbep/cx_mgw_busi_exception=>resource_not_found
31         entity_type = lv_error_entity.
32   ENDIF.
33
34 ENDMETHOD.

Back to Top


Appendix: changeset_process code

001  METHOD /iwbep/if_mgw_appl_srv_runtime~changeset_process.
002
003    DATA:
004      ls_changeset_request     TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_request,
005      ls_changeset_req_parent  TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_request,
006      lo_create_context        TYPE REF TO /iwbep/if_mgw_req_entity_c,
007      lv_entity_type           TYPE string,
008      ls_changeset_response    TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_response,
009      ls_changeset_resp_parent TYPE /iwbep/if_mgw_appl_types=>ty_s_changeset_response,
010      ls_header                TYPE zcl_zcontentid_mpc=>ts_header, "replace the mcp class name for your own
011      ls_item                  TYPE zcl_zcontentid_mpc=>ts_item, "replace the mcp class name for your own
012      lv_error_entity          TYPE string.
013
014    FIELD-SYMBOLS:
015      <ls_header>              TYPE zcl_zcontentid_mpc=>ts_header. "replace the mcp class name for your own
016
017    LOOP AT it_changeset_request INTO ls_changeset_request.
018
019      lo_create_context ?= ls_changeset_request-request_context.
020      lv_entity_type = lo_create_context->get_entity_type_name( ).
021
022      CASE ls_changeset_request-operation_type.
023        WHEN /iwbep/if_mgw_appl_types=>gcs_operation_type-create_entity.
024* create (HTTP POST)
025          CASE lv_entity_type.
026* which entity? The name is case sensitive
027            WHEN 'Header'.
028* Header entity
029              ls_changeset_request-entry_provider->read_entry_data( IMPORTING es_data = ls_header ).
030
031              INSERT INTO zheader VALUES ls_header.
032
033              IF ( sy-subrc = 0 ).
034* entity inserted
035                copy_data_to_ref(
036                   EXPORTING
037                     is_data = ls_header
038                   CHANGING
039                     cr_data = ls_changeset_response-entity_data ).
040              ELSE.
041* entity alredy exists - $batch will be rolled back
042                CONCATENATE lv_entity_type
043                            '('''
044                            ls_header-id
045                            ''')'
046                  INTO lv_error_entity.
047                RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
048                  EXPORTING
049                    textid      = /iwbep/cx_mgw_busi_exception=>resource_duplicate
050                    entity_type = lv_error_entity.
051              ENDIF.
052
053              ls_changeset_response-operation_no = ls_changeset_request-operation_no.
054              INSERT ls_changeset_response INTO TABLE ct_changeset_response.
055
056            WHEN 'Item'.
057* Item entity
058              ls_changeset_request-entry_provider->read_entry_data( IMPORTING es_data = ls_item ).
059
060              IF ls_changeset_request-content_id_ref IS NOT INITIAL.
061* look up the parent by content id
062                READ TABLE it_changeset_request INTO ls_changeset_req_parent WITH KEY content_id = ls_changeset_request-content_id_ref.
063
064                IF ( sy-subrc = 0 ).
065                  READ TABLE ct_changeset_response INTO ls_changeset_resp_parent WITH TABLE KEY operation_no = ls_changeset_req_parent-operation_no.
066                  IF ( sy-subrc = 0 ).
067                    ASSIGN ls_changeset_resp_parent-entity_data->* TO <ls_header>.
068                    IF <ls_header>-id IS NOT INITIAL.
069* header key value is obtained for items
070                      ls_item-parent_id = <ls_header>-id.
071
072                      INSERT INTO zitem VALUES ls_item.
073
074                      IF ( sy-subrc = 0 ).
075* entity inserted
076                        copy_data_to_ref(
077                           EXPORTING
078                             is_data = ls_item
079                           CHANGING
080                             cr_data = ls_changeset_response-entity_data ).
081                      ELSE.
082* entity alredy exists - $batch will be rolled back
083                        CONCATENATE lv_entity_type
084                                    '('''
085                                    ls_item-parent_id
086                                    ','''
087                                    ls_item-id
088                                    ''')'
089                          INTO lv_error_entity.
090                        RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
091                          EXPORTING
092                            textid      = /iwbep/cx_mgw_busi_exception=>resource_duplicate
093                            entity_type = lv_error_entity.
094                      ENDIF.
095
096                      ls_changeset_response-operation_no = ls_changeset_request-operation_no.
097                      INSERT ls_changeset_response INTO TABLE ct_changeset_response.
098
099                    ENDIF.
100                  ENDIF.
101                ENDIF.
102              ENDIF.
103          ENDCASE. "end of lv_entity_type
104      ENDCASE."end of create_entity
105    ENDLOOP.
106  ENDMETHOD.

Back to Top

13 Comments