Attila, S/4HANA, BTP Fullstack Developer (EN, DE, HU)
CDS and BOPF Admin Data (create/change user and time) Determination using Annotations
Using the SAP Netweaver Innovation Package 7.51 sp1 you can implement new data models easily with CDS including full CRUD support, where change operations are managed by the BOPF framework. The BOPF Object is generated based on the Object Model annotations you put in the CDS View at activation.
This solution using SADL shows a BOPF adoptation/consumption, but also demonstrates how-to create your own consumer / framework asking SADL for CDS entity metainformation (Annotations). You have no more boundaries, your only blocker is your own creativity 🙂
Let say we have a DB table created already, and we would like to implement CRUD operations which could be extended with custom validation and determination of entry fields. One of the first thing we would achieve is to have the administrative fields maintained, like the last change time/user and the creator user/time. If you ever developed with BOPF previously, you would first try to include DDIC structure /BOBF/S_LIB_ADMIN_DATA into your table maintaining ADMIN_DATA as group ID, plus adding a new determination reusing the well known class /BOBF/CL_LIB_D_ADMIN_DATA_TSM. Unfortunately it dumps immediately, when you add this in eclipse ADT. (You can add it in eclipse only, since in GUI transactions the BOPF Object is locked against any change). This is due at the moment node category assigment “Before Save (Finalize)” cannot be added in eclipse, and might never added due the BOPF Object was generated from CDS, this is known only by the big CDS BOPF SADL Masters internally at SAP :).
You could create a custom determination class and fill the fields manually in every of your custom BO, but to support reusability and maitainability overall at your organization, you can create a reusable deterimination class inheriting from /bobf/cl_lib_d_superclass. This is exactly what I did, after I got some ideas from class /BOBF/CL_LIB_D_ADMIN_DATA_CDS. This class dumped me for the same reason, and the annotations used within were not on the official ABAP CDS annotations list already. I read In the ABAP Documentation, that customers better not use custom annotations in ABAP CDS (which is supported in HANA), but to avoid conflict with partly existing SAP ABAP CDS Annotations I used my custom annotatiosn starting with Z.
Step 1 – Add fields to your DB table for admin data
| Field | Data Element |
| CREA_DATE_TIME | TIMESTAMPL |
| CREA_UNAME | UNAME |
| LCHG_DATE_TIME | TIMESTAMPL |
| LCHG_UNAME | UNAME |
Step 2 – Create a CDS View
Eclipse (ADT): File – New – Other: Data Definition under Core Data Services. Here enter a name like ZCDS_I_MYDOCUMENT
@AbapCatalog.sqlViewName: 'ZV_ANABAPVIEWNAME'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: 'My CDS View Name'
@ObjectModel.modelCategory: #BUSINESS_OBJECT
@ObjectModel.compositionRoot: true
@ObjectModel.transactionalProcessingEnabled: true
@ObjectModel.writeActivePersistence: 'ZT_YOURDBTABLE'
@ObjectModel.createEnabled: true
@ObjectModel.deleteEnabled: true
@ObjectModel.updateEnabled: true
@Search.searchable: true
@OData.publish: true --Comment Out if you do not want an OData Service/Endpoint to be generated
define view Zcds_I_Mydocument
as select from zt_yourdbtable as MyDoc
{
@Search.defaultSearchElement: true
key MyDoc.oneofyourfield,
MyDoc.otherfield,
@ZSemantics.systemDateTime.createdAt: true
MyDoc.crea_date_time,
@ZSemantics.user.createdBy: true
MyDoc.crea_uname,
@ZSemantics.systemDateTime.lastChangedAt: true
MyDoc.lchg_date_time,
@ZSemantics.user.lastChangedBy: true
MyDoc.lchg_uname
}
As you can see we have the same fields like in the structure /BOBF/S_LIB_ADMIN_DATA in the above example, but you can put any name for the admin fields. Here the key information is the DDIC type and the the annotation what you put before the CDS View field name. This is what we analyze in our custom determination class.
Step 3 – Create BOPF Determination class
"! <p class="shorttext synchronized" lang="en">Admin Data determination for CDS Views (Annotation Based)</p>
CLASS zcl_admin_data_cds DEFINITION
PUBLIC INHERITING FROM /bobf/cl_lib_d_superclass
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
METHODS /bobf/if_frw_determination~execute
REDEFINITION .
PROTECTED SECTION.
CONSTANTS co_annot_created_at TYPE string VALUE 'ZSEMANTICS.SYSTEMDATETIME.CREATEDAT' ##NO_TEXT.
CONSTANTS co_annot_created_by TYPE string VALUE 'ZSEMANTICS.USER.CREATEDBY' ##NO_TEXT.
CONSTANTS co_annot_changed_at TYPE string VALUE 'ZSEMANTICS.SYSTEMDATETIME.LASTCHANGEDAT' ##NO_TEXT.
CONSTANTS co_annot_changed_by TYPE string VALUE 'ZSEMANTICS.USER.LASTCHANGEDBY' ##NO_TEXT.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_admin_data_cds IMPLEMENTATION.
METHOD /bobf/if_frw_determination~execute.
DATA:
node_entries TYPE REF TO data,
node_entry TYPE REF TO data,
timestamp_long TYPE timestampl.
FIELD-SYMBOLS:
<node_entry> TYPE any,
<node_entries> TYPE INDEX TABLE,
<node_entry_key> TYPE /bobf/conf_key,
<created_by> TYPE uname,
<created_at> TYPE timestampl,
<changed_by> TYPE uname,
<changed_at> TYPE timestampl.
GET TIME STAMP FIELD timestamp_long.
"After Create, After Update times are supported
IF is_ctx-exectime <> /bobf/if_conf_c=>sc_time_after_create AND is_ctx-exectime <> /bobf/if_conf_c=>sc_time_after_modify.
RAISE EXCEPTION TYPE /bobf/cx_lib
EXPORTING
textid = /bobf/cx_lib=>wrong_determination_time.
ENDIF.
"Retrieve node data
/bobf/cl_frw_factory=>get_configuration( iv_bo_key = is_ctx-bo_key )->get_node(
EXPORTING iv_node_key = is_ctx-node_key
IMPORTING es_node = DATA(ls_node) ).
"Ask SADL to provide CDS annotations for the view fields on which this BO node is based on
TRY.
cl_sadl_entity_factory=>get_instance( )->get_entity(
iv_type = cl_sadl_entity_provider_cds=>gc_type
iv_id = CONV #( ls_node-node_name )
)->get_annotations(
IMPORTING et_element_annotations = DATA(elements_annotations) ).
CATCH cx_sadl_static INTO DATA(lx_sadl_static).
RAISE EXCEPTION TYPE /bobf/cx_conf_cds_link
EXPORTING
textid = /bobf/cx_conf_cds_link=>read_elements
previous = lx_sadl_static
mv_node = CONV #( ls_node-node_name ).
ENDTRY.
"Search for annotated view fields which should be updated
LOOP AT elements_annotations ASSIGNING FIELD-SYMBOL(<element_annotations>).
IF line_exists( <element_annotations>-annotations[ name = co_annot_created_at ] ).
DATA(created_at_fieldname) = <element_annotations>-name.
ELSEIF line_exists( <element_annotations>-annotations[ name = co_annot_created_by ] ).
DATA(created_by_fieldname) = <element_annotations>-name.
ELSEIF line_exists( <element_annotations>-annotations[ name = co_annot_changed_at ] ).
DATA(changed_at_fieldname) = <element_annotations>-name.
ELSEIF line_exists( <element_annotations>-annotations[ name = co_annot_changed_by ] ).
DATA(changed_by_fieldname) = <element_annotations>-name.
ENDIF.
ENDLOOP.
"Create work area and table based on BOPF node metadata used to iterate over the new/changed entries passed by the framework
IF ls_node-data_table_type IS NOT INITIAL.
CREATE DATA node_entries TYPE (ls_node-data_table_type).
ELSE.
CREATE DATA node_entries TYPE STANDARD TABLE OF (ls_node-data_type).
ENDIF.
ASSIGN node_entries->* TO <node_entries>.
CREATE DATA node_entry TYPE (ls_node-data_type).
ASSIGN node_entry->* TO <node_entry>.
"Node Entry Key required for Update
ASSIGN COMPONENT /bobf/if_conf_c=>sc_attribute_name_key OF STRUCTURE <node_entry> TO <node_entry_key>.
"Bind fields of the work area with the annotated field name
IF created_at_fieldname IS NOT INITIAL.
ASSIGN node_entry->(created_at_fieldname) TO <created_at>.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /bobf/cx_lib
EXPORTING
textid = /bobf/cx_lib=>determination_error
mv_fieldname = created_at_fieldname.
ENDIF.
ENDIF.
IF created_by_fieldname IS NOT INITIAL.
ASSIGN node_entry->(created_by_fieldname) TO <created_by>.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /bobf/cx_lib
EXPORTING
textid = /bobf/cx_lib=>determination_error
mv_fieldname = created_by_fieldname.
ENDIF.
ENDIF.
IF changed_at_fieldname IS NOT INITIAL.
ASSIGN node_entry->(changed_at_fieldname) TO <changed_at>.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /bobf/cx_lib
EXPORTING
textid = /bobf/cx_lib=>determination_error
mv_fieldname = changed_at_fieldname.
ENDIF.
ENDIF.
IF changed_by_fieldname IS NOT INITIAL.
ASSIGN node_entry->(changed_by_fieldname) TO <changed_by>.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE /bobf/cx_lib
EXPORTING
textid = /bobf/cx_lib=>determination_error
mv_fieldname = changed_by_fieldname.
ENDIF.
ENDIF.
"Retrieve node entries
io_read->retrieve(
EXPORTING
iv_node = is_ctx-node_key
it_key = it_key
IMPORTING
et_data = <node_entries> ).
"Update administrative data fields
LOOP AT <node_entries> INTO <node_entry>.
"Changed by/at
IF <created_at> IS ASSIGNED AND <created_at> IS INITIAL.
<created_at> = timestamp_long.
ENDIF.
IF <created_by> IS ASSIGNED AND <created_by> IS INITIAL.
<created_by> = sy-uname.
ENDIF.
"Changed by/at
IF is_ctx-exectime = /bobf/if_conf_c=>sc_time_after_modify.
IF <changed_at> IS ASSIGNED.
<changed_at> = timestamp_long.
ENDIF.
IF <changed_by> IS ASSIGNED.
<changed_by> = sy-uname.
ENDIF.
ENDIF.
"Update Entry
io_modify->update(
EXPORTING
iv_node = is_ctx-node_key
iv_key = <node_entry_key>
is_data = node_entry ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.
Step 4 – Add determination using the above class to your BO generated based on the CDS
Double click the generated BO in the ABAP Project Explorer in eclipse, and navigate to the node where you want to fill in the admin data. Here you need to go to the Dereminations, and create a new one like this.

You are ready. Do not be affriad, when Eclipse does not recognize the Z-Annotations maintained in the view, and colors it red.


