Skip to Content

Introduction

In a recent partner workshop I was asked how one can add annotations to an OData service when your system runs on top of SAP NetWeaver 740.

While with SAP NetWeaver 750 and higher you can add annotations within the CDS DDL source code there is no such support available for SAP NetWeaver 740.

Nevertheless customers and partners can leverage CDS views as a datasource for OData services already in SAP NetWeaver 740 if they are using service implementation using CDS views as mapped datasources.

So you can start building CDS views for your OData services in 740. To add support for annotaions in 740 backends you can used code based implementation.

I found however that there is not much information available around this topic … so I decided to write this blog.

The use case as mentioned above for this scenario are customers and partners that want to build  OData services based on SAP NetWeaver 740 where no support for annotations in CDS views is available.

When upgrading to SAP NetWeaver 750 or higher you can move your implementation into CDS as well.

The following screen shot shows the first screen of the resulting SAP Fiori application which is based on the List Report Page and Object Page from Fiori elements (aka Smart Templates).

This app uses the OData service that I have described in my blog

OData service development with SAP Gateway – code-based service development – Part I

 

 

From the entity set SalesOrderSet we show the columns Salesorder, Customer and Gross amount together with the Currency by default. The header of this list is named SalesOrders.

 

When clicking on on list entry we are using the navigation property ToItems and the details of the selected sales order is displayed on the object page.

Here some information from the sales order header such as Salesorder number, Customer number, lifecycle status and the last change date is shown in the header of the object page (red).

The gross amount is highlighted as a data point (green) whereas the title of the object page is titled as SalesOrder (blue).

 

Coding explained

In order to create these annoations we are going to implement the DEFINE method of the model provider extenstion class (MPC_EXT) of our OData service implementation. In the Service Builder we expand the folder Runtime Artifacts, select the class with the extension MPC_EXT class and select Go to ABAP Workbench from the context menue.

 

This opens the ABAP Development Tools in Eclipse

where we can start to redefine the DEFINE method.

After calling the DEFINE method of the superclass the code starts with some definitions.

 

 
    DATA: lo_ann_target  TYPE REF TO /iwbep/if_mgw_vocan_ann_target.   " Vocabulary Annotation Target
    DATA: lo_ann_target2 TYPE REF TO /iwbep/if_mgw_vocan_ann_target.   " Vocabulary Annotation Target
    DATA: lo_annotation  TYPE REF TO /iwbep/if_mgw_vocan_annotation.   " Vocabulary Annotation
    DATA: lo_collection  TYPE REF TO /iwbep/if_mgw_vocan_collection.   " Vocabulary Annotation Collection
    DATA: lo_function    TYPE REF TO /iwbep/if_mgw_vocan_function.     " Vocabulary Annotation Function
    DATA: lo_fun_param   TYPE REF TO /iwbep/if_mgw_vocan_fun_param.    " Vocabulary Annotation Function Parameter
    DATA: lo_property    TYPE REF TO /iwbep/if_mgw_vocan_property.     " Vocabulary Annotation Property
    DATA: lo_record      TYPE REF TO /iwbep/if_mgw_vocan_record.       " Vocabulary Annotation Record
    DATA: lo_simp_value  TYPE REF TO /iwbep/if_mgw_vocan_simple_val.   " Vocabulary Annotation Simple Value
    DATA: lo_url         TYPE REF TO /iwbep/if_mgw_vocan_url.          " Vocabulary Annotation URL
    DATA: lo_label_elem  TYPE REF TO /iwbep/if_mgw_vocan_label_elem.   " Vocabulary Annotation Labeled Element
    DATA: lo_reference   TYPE REF TO /iwbep/if_mgw_vocan_reference.    " Vocabulary Annotation Reference

 

Next we add references. Please note the alias ‘UI’ which is later reused in the code.

 

 
lo_reference = vocab_anno_model->create_vocabulary_reference( iv_vocab_id = '/IWBEP/VOC_UI' iv_vocab_version = '0001').
lo_reference->create_include( iv_namespace = 'com.sap.vocabularies.UI.v1' iv_alias = 'UI' ).

 

We first specify the target entity type for our annations which is in this case the entity type “Salesorder”. Important here is to set the correct namespace of our OData service.

Next we add the header information which is shown on the first page (‘SalesOrders’) to describe the entries of the list
and the details on the object page (‘SalesOrder’).

Then we are creating a collection of entries that specify the columns of the entity set that are shown in the list by default. These are the UI.LineItem annotations.

    "annotations for entity type Sales Order
    lo_ann_target = vocab_anno_model->create_annotations_target( 'SalesOrder' ).
    lo_ann_target->set_namespace_qualifier( 'ZE2E100_XX_3_SRV' ).    "change the namespace to the SRV namespace

    " Header Info
    lo_annotation = lo_ann_target->create_annotation( iv_term = 'UI.HeaderInfo' ).
    lo_record = lo_annotation->create_record( ).
    lo_record->create_property( 'TypeName' )->create_simple_value( )->set_string('SalesOrder').
    lo_record->create_property( 'TypeNamePlural' )->create_simple_value( )->set_string( 'SalesOrders').

    " Columns to be displayed by default
    lo_annotation = lo_ann_target->create_annotation( iv_term = 'UI.LineItem' ).
    lo_collection = lo_annotation->create_collection( ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Salesorder' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Salesorder' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Customer' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Customer' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Gross amount' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Grossamountintransaccurrency' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Currency' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Transactioncurrency' ).

 

The columns that are displayed in the header of the object page are annotated using the ‘UI.Identification’ annotation.

 

    " Columns to be displayed in the object page
    lo_annotation = lo_ann_target->create_annotation(
          EXPORTING
            iv_term       = 'UI.Identification' ).
    lo_collection = lo_annotation->create_collection( ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Salesorder' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Salesorder' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Customer' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Customer' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Lifecycle status' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Salesorderlifecyclestatus' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Last changed at' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Lastchangeddatetime' ).

 

The gross amount that shall be shown as a data point is annotated next.

 

    " Gross amount to be displayed as a data point
    lo_annotation = lo_ann_target->create_annotation(
          EXPORTING
            iv_term      = 'UI.DataPoint'
            iv_qualifier = 'Grossamountintransaccurrency' ).
    lo_record = lo_annotation->create_record( iv_record_type = 'UI.DataField' ).
    lo_record->create_property( 'Value' )->create_simple_value( )->set_path('Grossamountintransaccurrency').
    lo_record->create_property( 'Title' )->create_simple_value( )->set_string( 'Gross amount' ).

 

Then we specify the facets for the header and the details of the object page using the annoation path ‘@UI.Identification’ .

To let the gross amount show up in the header as a data point we have to set the annotation pathe ‘@UI.DataPoint#Grossamountintransaccurrency’.

 

    "Header Facets
    lo_collection = lo_ann_target->create_annotation( iv_term = 'UI.HeaderFacets' )->create_collection( ).

    "Facet for Sales Order Header Details on object page
    lo_record = lo_collection->create_record( iv_record_type = 'UI.ReferenceFacet' ).
    lo_record->create_property( 'ID' )->create_simple_value( )->set_string( 'GeneralInformation' ).
    lo_record->create_property( 'Label' )->create_simple_value( )->set_string( 'General Information' ).
    lo_record->create_property( 'Target')->create_simple_value( )->set_annotation_path( '@UI.Identification' ).

    "Facet for Gross amount
    lo_record = lo_collection->create_record( iv_record_type = 'UI.ReferenceFacet' ).
    lo_record->create_property( 'ID' )->create_simple_value( )->set_string( 'GrossAmount' ).
    lo_record->create_property( 'Label' )->create_simple_value( )->set_string( 'Gross amount' ).
    lo_record->create_property( 'Target')->create_simple_value( )->set_annotation_path( '@UI.DataPoint#Grossamountintransaccurrency' ).

    "Facet for Sales Order Header Details on object page
    lo_collection = lo_ann_target->create_annotation( iv_term = 'UI.Facets' )->create_collection( ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.ReferenceFacet' ).
    lo_record->create_property( 'ID' )->create_simple_value( )->set_string( 'ItemList' ).
    lo_record->create_property( 'Label' )->create_simple_value( )->set_string( 'Item List' ).
    lo_record->create_property( 'Target')->create_simple_value( )->set_annotation_path( 'ToItems/@UI.LineItem' ).

 

We can finally add the annoations that specify the columns of the sales order item list which is shown on the object page.
These are again ‘UI.LineItem’ annotations. But this time the target of our annotations is a new object that points to the ‘SalesOrderItem’ entity type.

 

    "add annotations for sales order line items
    lo_ann_target2 = vocab_anno_model->create_annotations_target( 'SalesOrderItem' ).
    lo_ann_target2->set_namespace_qualifier( 'ZE2E100_XX_3_SRV' ).    "change the namespace to the SRV namespace

    " Columns to be displayed by default
    lo_annotation = lo_ann_target2->create_annotation( iv_term = 'UI.LineItem' ).
    lo_collection = lo_annotation->create_collection( ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Salesorder' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Salesorder' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Item' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Salesorderitem' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Gross amount' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Grossamountintransaccurrency' ).

    lo_record = lo_collection->create_record( iv_record_type = 'UI.DataField' ).
    lo_property = lo_record->create_property( 'Label' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_string( 'Currency' ).
    lo_property = lo_record->create_property( 'Value' ).
    lo_simp_value = lo_property->create_simple_value( ).
    lo_simp_value->set_path( 'Transactioncurrency' ).

 

The  resulting  annotations can be found in the $metadata files:

 

 <Annotations Target="ZE2E100_XX_3_SRV.SalesOrder" xmlns="http://docs.oasis-open.org/odata/ns/edm">
 <Annotation Term="UI.HeaderInfo">
 <Record>
  <PropertyValue Property="TypeName" String="SalesOrder" /> 
  <PropertyValue Property="TypeNamePlural" String="SalesOrders" /> 
  </Record>
  </Annotation>
 <Annotation Term="UI.LineItem">
 <Collection>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Salesorder" /> 
  <PropertyValue Property="Value" Path="Salesorder" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Customer" /> 
  <PropertyValue Property="Value" Path="Customer" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Gross amount" /> 
  <PropertyValue Property="Value" Path="Grossamountintransaccurrency" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Currency" /> 
  <PropertyValue Property="Value" Path="Transactioncurrency" /> 
  </Record>
  </Collection>
  </Annotation>
 <Annotation Term="UI.Identification">
 <Collection>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Salesorder" /> 
  <PropertyValue Property="Value" Path="Salesorder" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Customer" /> 
  <PropertyValue Property="Value" Path="Customer" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Lifecycle status" /> 
  <PropertyValue Property="Value" Path="Salesorderlifecyclestatus" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Last changed at" /> 
  <PropertyValue Property="Value" Path="Lastchangeddatetime" /> 
  </Record>
  </Collection>
  </Annotation>
 <Annotation Term="UI.DataPoint" Qualifier="Grossamountintransaccurrency">
 <Record Type="UI.DataField">
  <PropertyValue Property="Value" Path="Grossamountintransaccurrency" /> 
  <PropertyValue Property="Title" String="Gross amount" /> 
  </Record>
  </Annotation>
 <Annotation Term="UI.HeaderFacets">
 <Collection>
 <Record Type="UI.ReferenceFacet">
  <PropertyValue Property="ID" String="GeneralInformation" /> 
  <PropertyValue Property="Label" String="General Information" /> 
  <PropertyValue Property="Target" AnnotationPath="@UI.Identification" /> 
  </Record>
 <Record Type="UI.ReferenceFacet">
  <PropertyValue Property="ID" String="GrossAmount" /> 
  <PropertyValue Property="Label" String="Gross amount" /> 
  <PropertyValue Property="Target" AnnotationPath="@UI.DataPoint#Grossamountintransaccurrency" /> 
  </Record>
  </Collection>
  </Annotation>
 <Annotation Term="UI.Facets">
 <Collection>
 <Record Type="UI.ReferenceFacet">
  <PropertyValue Property="ID" String="ItemList" /> 
  <PropertyValue Property="Label" String="Item List" /> 
  <PropertyValue Property="Target" AnnotationPath="ToItems/@UI.LineItem" /> 
  </Record>
  </Collection>
  </Annotation>
  </Annotations>
 <Annotations Target="ZE2E100_XX_3_SRV.SalesOrderItem" xmlns="http://docs.oasis-open.org/odata/ns/edm">
 <Annotation Term="UI.LineItem">
 <Collection>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Salesorder" /> 
  <PropertyValue Property="Value" Path="Salesorder" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Item" /> 
  <PropertyValue Property="Value" Path="Salesorderitem" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Gross amount" /> 
  <PropertyValue Property="Value" Path="Grossamountintransaccurrency" /> 
  </Record>
 <Record Type="UI.DataField">
  <PropertyValue Property="Label" String="Currency" /> 
  <PropertyValue Property="Value" Path="Transactioncurrency" /> 
  </Record>
  </Collection>
  </Annotation>
  </Annotations>
To report this post you need to login first.

5 Comments

You must be Logged on to comment or reply to a post.

  1. Umit Coskun Aydinoglu

    Hello,

    We want to extend Fiori Application HR Travel Request Approval. We found out that it’s implemented with UI Annotations and annotations are defined as code based in  DEFINE method of CL_TRV_ATR_MPC_EXT class which is model provider class for TRV_ATR service. So we created our own service since we need additional entities and redefined the service. In the DEFINE method of our model prodiver class we call super->define() which generates the annotations defined in standard service.Then we call create our annotations for the entities we create like you have written in this blog.  When we called the metadata we got error:
    “A row already exists with this keyThe error occurred on the application server .”

    When I get deeper in ST22 I see this error message:

    “An attempt was made to insert an entry into table
    “\CLASS=/IWFND/CL_MED_MDL_PROVIDER\METHOD=CREATE_CLUSTERED_MODEL\DATA=LS_VOCA
    _MODEL-ANNOTATION_TARGETS”. Updating
    unique table key “PRIMARY_KEY” resulted in a duplicate entry however. The key
    in
    question could be either the primary key or a secondary key.”

    I think it’s not the correct way to redefine DEFINE method to extend annotations programatically. What is the best practice to extend the annotations of standard service?

    Kind Regards,
    Ümit Coşkun Aydınoğlu

    (0) 

Leave a Reply