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: 
Andre_Fischer
Product and Topic Expert
Product and Topic Expert

Updates



  • 12.05.2017

    • added code to lock the data before comparing the time stamp of the last change date with the time stamp read from the etag in the update method.



  • 16.05.2017

    • fixed MPC_EXT code that creates the function import

    • added code to lock the data before comparing the time stamp of the last change date with the time stamp read from the etag in the function import




Introduction


SAP Gateway offers an out of the box support for etags if a property of an entity type is marked as an etag. When using the generic framework support the following happens when using eTags.

Before an update is performed the Gateway framework on the hub performs a READ request and compares the Etag send by the client with the Etag retrieved from the backend. The Etag handling offered by the framework does not require any coding but it requires that the GET_ENTITY method has to be implemented. The generic SAP Gateway framework support is offered for read, update and delete requests.

Instead of using the generic framework support it is possible to perform a code based implementation in the SAP Business Suite backend system.

This has some advantages since it allows the developer to lock the data while checking the etag before the update is performed. When using the out of the box framework support this is not possible and you cannot be 100% sure that another user has updated the data in the short timeframe between the read request of the framework and your update request.

The second advantage is that a code based implementation also offers etag support for function imports.

The handling of etags in the backend is available as of SAP Gateway 2.0 SP09 and SAP NetWeaver 740 SP08. You have to implement some methods where you have to add code to tell the framework that the data provider class has implemented ETag handling.

Prerequisites


This blog is based on an OData service that has been desrbied in my following blog:

https://blogs.sap.com/2016/06/02/odata-service-development-with-sap-gateway-using-cds-via-referenced...

What are etags?


Etags are usually timestamps that are updated when an entity is updated. For more complex scenarios however timestamps might not be the best choice and a hashtag is the better approach. How to implement a hash tag based etag has been described in the following blog https://blogs.sap.com/2016/03/18/maintain-data-concurrency-in-odata/ by thiru.siva

 

How to annotate a property as an etag?


When doing code based development you can select the property that should be used as an etag on entity type level.



If you have used the referenced data source approach you have to perform these changes in the DEFINE method of the model provider extension class.

In the define method of the OData service that we used in the following blog

we will have to add to lines to set the property 'LastChangedDateTime' as an etag for the entity type   'Zsepm_C_Salesorder_TplType'.

 
  method define.

data lo_entity_type type ref to /iwbep/if_mgw_odata_entity_typ.
data lo_property type ref to /iwbep/if_mgw_odata_property.

super->define( ).

lo_entity_type = model->get_entity_type( iv_entity_name = 'Zsepm_C_Salesorder_TplType' ).
lo_property = lo_entity_type->get_property( iv_property_name = 'SalesOrder_Text' ).
lo_property->set_updatable( abap_true ).

lo_property = lo_entity_type->get_property( iv_property_name = 'LastChangedDateTime' ).
lo_property->set_as_etag( ).

endmethod.

 

Testing the out of the box ETag framework support


After you have implemented these changes you will get the following error message when trying to perform an update with the http status code 428 Value "Precondition Required".
<message xml:lang="en">The Data Service Request is required to be conditional. Try using the "If-Match" header.</message>

This check is done by the SAP Gateway framework on the hub.

We have to add an if-match HTTP header with a value like this:

W/"datetimeoffset'2017-05-05T09%3A15%3A05.6050000Z'"

In the Gateway Client you can easily add this header as shown in the following screen shot.



If we now perform the update request again we will receive an empty http response with the http returncode 204 which indicates that the update was successful.



If you try to perform the update without changing the value of the etag you will receive the error message Precondition Failed with an http return code 428.


Code based implementation of ETag support for updates


/IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_IS_CONDITIONAL_IMPLEMENTED


We first have to implement the method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_IS_CONDITIONAL_IMPLEMENTED by adding the following code
  method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_IS_CONDITIONAL_IMPLEMENTED.
* conditional handling set for Employee update and patch operations
IF iv_entity_set_name EQ 'Zsepm_C_Salesorder_Tpl' AND
( iv_operation_type = /iwbep/if_mgw_appl_types=>gcs_operation_type-update_entity OR
iv_operation_type = /iwbep/if_mgw_appl_types=>gcs_operation_type-patch_entity ).
rv_conditional_active = abap_true.
ELSE.
rv_conditional_active = abap_false.
ENDIF.
endmethod.

Please note that the name of the enity set of our service that we have created using referenced data source and Service Builder is called 'Zsepm_C_Salesorder_Tpl'. For other entity sets the framework would handle the Etag support.

Since the SAP Gatway Hub framework does now not perform the etag handling we have to add the follwing code into our update method because otherwise all updated request would be performed without checking the content of the etag.

Add Etag check to the update method


We now have to code to the update method that first checks whether the sales order has not changed by comparing the value of the last changed date with the value provided by the update request in the if-match header.

For this we use the get_conditional_info method of the  io_tech_request_context object. This method returns if_match as well as if_non_match headers in a deep structure that we have to evaluate.

In addition we are locking the sales order that we want to update before performing the update.

The lock is released if not updated is performed if the check of the etag fails.

If you add a break-point into the code you can see that a lock is aquired in transaction SM12.


Source code ZSEPM_C_SALESORD_UPDATE_ENTITY


    method ZSEPM_C_SALESORD_UPDATE_ENTITY.

data: lt_keys type /iwbep/t_mgw_tech_pairs,
ls_key type /iwbep/s_mgw_tech_pair,
ls_so_id type bapi_epm_so_id,
ls_headerdata_update type bapi_epm_so_header,
ls_headerdatax type bapi_epm_so_headerx,
ls_headerdata_key type zcl_ze2e100_xx_2_mpc=>ts_zsepm_c_salesorder_tpltype,
ls_headerdata_payload type zcl_ze2e100_xx_2_mpc=>ts_zsepm_c_salesorder_tpltype,
lt_return type table of bapiret2,
ls_return type bapiret2,
err_msg type string,
ls_message type scx_t100key.

* data definitions for etag handling
data ls_conditions type /iwbep/if_mgw_appl_types=>ty_s_conditions.
data ls_etag type /iwbep/if_mgw_appl_types=>ty_s_etag.
data lv_matched type abap_bool.
data lv_etag_value type c length 22 .
data ls_entity type sepm_isoe.

* Lock argument for table SNWD_LOCK

data: begin of ls_snwd_lock,
* node_key(000032) type c,
node_key type snwd_lock-NODE_KEY,
bo_name type snwd_lock-bo_name,
bo_node_name type snwd_lock-bo_node_name,
client type snwd_lock-client,
end of ls_snwd_lock.

data ls_so type snwd_so.


* " Initialization of lock argument:
call 'C_ENQ_WILDCARD' id 'HEX0' field ls_snwd_lock.

* " assign lock parameters to lock fields
ls_snwd_lock-bo_name = 'IF_EPM_SO'.
ls_snwd_lock-bo_node_name = 'IF_EPM_SO_HEADER'.
ls_snwd_lock-client = sy-mandt.


call method io_tech_request_context->get_converted_keys
importing
es_key_values = ls_headerdata_key.


" determine value of node_key.
select single * from snwd_so
where SO_ID = @ls_headerdata_key-salesorder
into @ls_so.

" add check whether product exist or not
if sy-subrc <> 0.
" implement suitable error handling here
ls_message-msgid = 'SY'.
ls_message-msgno = '002'.
concatenate 'Update for ' ls_headerdata_key-salesorder ' failed' into ls_message-attr1.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
textid = ls_message.
else.
ls_snwd_lock-node_key = ls_so-node_key.
endif.

* lock the sales order entity by enqueue lock
call function 'ENQUEUE_EPM_LOCK'
exporting
mode_snwd_lock = 'E'
node_key = ls_snwd_lock-node_key
bo_name = ls_snwd_lock-bo_name
bo_node_name = ls_snwd_lock-bo_node_name
client = ls_snwd_lock-client
* X_NODE_KEY = ' '
* X_BO_NAME = ' '
* X_BO_NODE_NAME = ' '
_scope = '2'
_wait = ' '
_collect = ' '
exceptions
foreign_lock = 1
system_failure = 2
others = 3.

if sy-subrc <> 0.
" implement suitable error handling here
ls_message-msgid = 'SY'.
ls_message-msgno = '002'.
concatenate 'Could not aquire lock entry for ' ls_headerdata_key-salesorder into ls_message-attr1.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
textid = ls_message.
endif.

* read the value of the timestamp from the database
select single * from sepm_isoe into ls_entity where salesorder = ls_headerdata_key-salesorder.
move ls_entity-lastchangeddatetime to lv_etag_value.

* read the etag value provided in the update request
io_tech_request_context->get_conditional_info(
importing
es_conditions = ls_conditions
).

*check if both values match
read table ls_conditions-if_match transporting no fields
with key any_value = abap_true.

if sy-subrc ne 0.
loop at ls_conditions-if_match into ls_etag.
read table ls_etag-tag_values transporting no fields
with key name = 'LASTCHANGEDDATETIME' value = lv_etag_value.
if sy-subrc eq 0.
lv_matched = abap_true.
exit.
endif.
endloop.
else.
lv_matched = abap_false.
endif.


if lv_matched eq abap_false.
* release the lock that we have aquired beforehand
call function 'DEQUEUE_EPM_LOCK'
exporting
mode_snwd_lock = 'E'
node_key = ls_snwd_lock-node_key
bo_name = ls_snwd_lock-bo_name
bo_node_name = ls_snwd_lock-bo_node_name
client = ls_snwd_lock-client
_scope = '2'
" _synchron = space
_collect = 'X'
exceptions
others = 3.

if sy-subrc <> 0.
" implement suitable error handling here
endif.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
http_status_code = /iwbep/cx_mgw_busi_exception=>gcs_http_status_codes-precondition_failed
textid = /iwbep/cx_mgw_busi_exception=>precondition_failed.
endif.


* perform the update

io_data_provider->read_entry_data( importing es_data = ls_headerdata_payload ).

ls_so_id-so_id = ls_headerdata_key-salesorder.

" Salesorder header data (non-key) fields that can be updated
" via the BAPI are marked with an 'X'

ls_headerdatax-so_id = ls_headerdata_key-salesorder.
ls_headerdatax-note = 'X'.

" move content of the fields that should be
" updated from payload to the corresponding
" field of the BAPI

move ls_headerdata_key-salesorder to ls_headerdata_update-so_id.
move ls_headerdata_payload-t_salesorder to ls_headerdata_update-note.

call function 'BAPI_EPM_SO_CHANGE'
exporting
so_id = ls_so_id " EPM: SO Id
soheaderdata = ls_headerdata_update " EPM: so header data of BOR object
soheaderdatax = ls_headerdatax
tables
return = lt_return. " Return Parameter

if lt_return is not initial.

loop at lt_return into ls_return.
err_msg = ls_return-message .
endloop.

ls_message-msgid = 'SY'.
ls_message-msgno = '002'.
ls_message-attr1 = err_msg.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
textid = ls_message.

endif.


endmethod.

 

Code based implementation of ETag support for function imports


Adding a function import


Since the service we are using has been generated using the referenced data source approach we cannot create a function import usingt the service builder. Reason is that the dialogue to create function imports does not find the entity types and entity sets of the CDS view.

We thus have to create a function import 'ConfirmOrder' by adding code to our DEFINE method of the modle provider extension class.
******************************************************************
* ACTION - ConfirmOrder
******************************************************************
data lo_action type ref to /iwbep/if_mgw_odata_action.
data lo_parameter type ref to /iwbep/if_mgw_odata_parameter.

lo_action = model->create_action( 'ConfirmOrder' ).
*Set return entity type
lo_action->set_return_entity_type( 'Zsepm_C_Salesorder_TplType' ).
*Set HTTP method GET, PUT or POST
lo_action->set_http_method( 'PUT' ). "#EC NOTEXT
* change 16.5.
*Set the action for entity type (not the entity set)
lo_action->set_action_for( 'Zsepm_C_Salesorder_TplType' ).
* Set return type multiplicity
lo_action->set_return_multiplicity( '0' ).

******************************************************************
* Parameters
******************************************************************

lo_parameter = lo_action->create_input_parameter( iv_parameter_name = 'SalesOrder' iv_abap_fieldname = 'SALESORDER' ).
lo_parameter->/iwbep/if_mgw_odata_property~set_type_edm_string( ).
lo_parameter->set_maxlength( iv_max_length = 10 ).
lo_parameter->BIND_DATA_ELEMENT(
exporting
IV_ELEMENT_NAME = 'SNWD_SO_ID'
IV_BIND_CONVERSIONS = ABAP_TRUE
).

Activate etag support through the backend


If you want to add Etag support for function modules you have to implement the following method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_IS_CONDI_IMPLE_FOR_ACTION. In this case the code will inform the framework that conditional handling is done by the service implementation in the backend for a function import 'ConfirmOrder'.
method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_IS_CONDI_IMPLE_FOR_ACTION.

if iv_action_name = 'ConfirmOrder'.
rv_conditional_active = abap_true.
else.
rv_conditional_active = abap_false.
endif.

endmethod.

 

Implement function import and add etag support


By implementating the method /iwbep/if_mgw_appl_srv_runtime~execute_action we can add business logic to the function import.

Like in the update method code has beend added that locks the sales order before checking the etag. Only after having checked that the sales order. hasn't been changed by another user  the status is updated.

 
    method /IWBEP/IF_MGW_APPL_SRV_RUNTIME~EXECUTE_ACTION.

data: ls_parameter type /iwbep/s_mgw_name_value_pair.
data: lv_soid type snwd_so_id.
data: ls_so_id type bapi_epm_so_id.
data: lt_return type standard table of bapiret2, ls_return type bapiret2.
data: ls_headerdata type bapi_epm_so_header.
data: ls_message type scx_t100key.
data: ls_entity type sepm_isoe.
types: begin of lty_import_parameter,
salesorder type snwd_so_id,
end of lty_import_parameter.
data: ls_parameter_values type lty_import_parameter,
lv_function_import_name type /iwbep/mgw_tech_name.
data: ls_conditions type /iwbep/if_mgw_appl_types=>ty_s_conditions.
data: ls_etag type /iwbep/if_mgw_appl_types=>ty_s_etag.
data: lv_matched type abap_bool.
data : lv_etag_value type c length 22 .


* Lock argument for table SNWD_LOCK

data: begin of ls_snwd_lock,
* node_key(000032) type c,
node_key type snwd_lock-NODE_KEY,
bo_name type snwd_lock-bo_name,
bo_node_name type snwd_lock-bo_node_name,
client type snwd_lock-client,
end of ls_snwd_lock.

data ls_so type snwd_so.


lv_function_import_name = io_tech_request_context->get_function_import_name( ).

io_tech_request_context->get_converted_parameters(
importing
es_parameter_values = ls_parameter_values ).

case lv_function_import_name.

when 'ConfirmOrder'.

* " Initialization of lock argument:
call 'C_ENQ_WILDCARD' id 'HEX0' field ls_snwd_lock.

* " assign lock parameters to lock fields
ls_snwd_lock-bo_name = 'IF_EPM_SO'.
ls_snwd_lock-bo_node_name = 'IF_EPM_SO_HEADER'.
ls_snwd_lock-client = sy-mandt.

" determine value of node_key.
select single * from snwd_so
where SO_ID = @ls_parameter_values-SalesOrder
into @ls_so.

" add check whether product exist or not
if sy-subrc <> 0.
" implement suitable error handling here
ls_message-msgid = 'SY'.
ls_message-msgno = '002'.
concatenate 'Update for ' ls_parameter_values-SalesOrder ' failed' into ls_message-attr1.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
textid = ls_message.
else.
ls_snwd_lock-node_key = ls_so-node_key.
endif.

* lock the sales order entity by enqueue lock
call function 'ENQUEUE_EPM_LOCK'
exporting
mode_snwd_lock = 'E'
node_key = ls_snwd_lock-node_key
bo_name = ls_snwd_lock-bo_name
bo_node_name = ls_snwd_lock-bo_node_name
client = ls_snwd_lock-client
* X_NODE_KEY = ' '
* X_BO_NAME = ' '
* X_BO_NODE_NAME = ' '
_scope = '2'
_wait = ' '
_collect = ' '
exceptions
foreign_lock = 1
system_failure = 2
others = 3.

if sy-subrc <> 0.
" implement suitable error handling here
ls_message-msgid = 'SY'.
ls_message-msgno = '002'.
concatenate 'Could not aquire lock entry for ' ls_parameter_values-SalesOrder into ls_message-attr1.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
textid = ls_message.
endif.




ls_so_id-so_id = ls_parameter_values-SalesOrder.

* add check for matching etags here
select single * from sepm_isoe into ls_entity where salesorder = ls_so_id-so_id.
move ls_entity-lastchangeddatetime to lv_etag_value.
io_tech_request_context->get_conditional_info(
importing
es_conditions = ls_conditions
).
* check if both values match
read table ls_conditions-if_match transporting no fields
with key any_value = abap_true.
if sy-subrc ne 0.
loop at ls_conditions-if_match into ls_etag.
read table ls_etag-tag_values transporting no fields
with key name = 'LASTCHANGEDDATETIME' value = lv_etag_value.
if sy-subrc eq 0.
lv_matched = abap_true.
exit.
endif.
endloop.
else.
lv_matched = abap_false.
endif.


if lv_matched eq abap_false.

* release the lock that we have aquired beforehand
call function 'DEQUEUE_EPM_LOCK'
exporting
mode_snwd_lock = 'E'
node_key = ls_snwd_lock-node_key
bo_name = ls_snwd_lock-bo_name
bo_node_name = ls_snwd_lock-bo_node_name
client = ls_snwd_lock-client
_scope = '2'
" _synchron = space
_collect = 'X'
exceptions
others = 3.

if sy-subrc <> 0.
" implement suitable error handling here
endif.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
http_status_code = /iwbep/cx_mgw_busi_exception=>gcs_http_status_codes-precondition_failed
textid = /iwbep/cx_mgw_busi_exception=>precondition_failed.
endif.


* perform change of so if etag check was successful


call function 'BAPI_EPM_SO_CONFIRM'
exporting
so_id = ls_so_id
* persist_to_db = abap_true
tables
return = lt_return.


if not lt_return[] is initial.
me->/iwbep/if_sb_dpc_comm_services~rfc_save_log(
exporting
* iv_entity_type = iv_entity_name
it_return = lt_return
it_key_tab = it_parameter ).

else.



select single * from sepm_isoe into ls_entity where salesorder = ls_so_id-so_id.

if ls_entity is initial.
ls_message-msgid = 'SY'.
ls_message-msgno = '002'.
ls_message-attr1 = 'sales order not found'.

raise exception type /iwbep/cx_mgw_busi_exception
exporting
textid = ls_message.
endif.

copy_data_to_ref( exporting
is_data = ls_entity changing
cr_data = er_data
).

endif.
endcase.
endmethod.

 

As a result we are now able to perform a confirmation of a sales order with optimistic locking by executing the following URI.

 
/sap/opu/odata/SAP/ZE2E100_XX_2_SRV/ConfirmOrder?SalesOrderID='0500000017'

 
17 Comments