This is a test message to test the length of the message box.
Login
ABAP RAP Unmanaged
Created by Software-Heroes

RAP - Unmanaged (Remote)

1882

Here you can learn more about the unmanaged RAP object with a remote data source and how you can implement such scenarios.



In the first article of the series we mainly talked about the theory and in the second article we presented a local scenario. In this article, we'll take a look at an application based on remote data.

 

Introduction

So what does it look like if the data is not in the system, how do we get it and how can we implement a working application for it? The example shown will use a custom entity and connect it to the unmanaged approach. As a basis we use the OData Service, which we have already connected and used in this article. This supports all CRUDQ operations and can therefore be easily implemented in our application.

 

Custom Entity

The first step is to create the custom entity, for this we can use the abstract CDS view as a template, which was created through the interface in the system. The custom entity defines the fields and the data types in an entity. Since we want to work with RAP here, we still need a ROOT entity, which we can also define in the view. Since we also want to provide data, we also need to provide a Query class. After creating the entity, we get the following result:

@EndUserText.label: 'Custom Entity with Unmanaged'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_COMPANY_QRY'
define root custom entity ZBS_R_RAPCustomCompanyNames
{

  key CompanyName        : abap.char( 60 );
      Branch             : abap.char( 50 );
      CompanyDescription : abap.char( 255 );

}

 

Since we want to define a UI application, we still have to create appropriate UI annotations to shape the interface. After creating the annotations, we now get the complete custom entity:

@EndUserText.label: 'Custom Entity with Unmanaged'
@ObjectModel.query.implementedBy: 'ABAP:ZCL_BS_DEMO_CUSTOM_COMPANY_QRY'
@UI: {
  headerInfo: {
    typeName: 'Company name',
    typeNamePlural: 'Company names',
    title: { value: 'CompanyName' }
  }
}
define root custom entity ZBS_R_RAPCustomCompanyNames
{
      @UI.facet          : [
        {
          id             : 'FacetDetailPage',
          label          : 'General',
          type           : #IDENTIFICATION_REFERENCE,
          targetQualifier: 'DETAIL'
        }
      ]

      @UI                : {
        lineItem         : [{ position: 10 }],
        selectionField   : [ { position: 10 } ],
        identification   : [{ position: 10, qualifier: 'DETAIL' }]
      }
      @EndUserText.label: 'Company name'
  key CompanyName        : abap.char( 60 );
      @UI                : {
        lineItem         : [{ position: 20 }],
        identification   : [{ position: 20, qualifier: 'DETAIL' }]
      }
      @EndUserText.label: 'Branch'
      Branch             : abap.char( 50 );
      @UI                : {
        identification   : [{ position: 30, qualifier: 'DETAIL' }]
      }
      @EndUserText.label: 'Description'
      @UI.multiLineText: true
      CompanyDescription : abap.char( 255 );

}

 

Hint: If we use a custom entity as the starting point of our application, we cannot carry out data modeling and therefore cannot create a projection. Also, no metadata extension is offered for this type of entity, so we have to pack all the required components into the view.

 

More objects

In order to get an executable application, we still have to implement the remaining objects. To do this, we create the query class that will later forward our queries to the backend. Since we have defined a field as a search field, we should also implement a corresponding query logic. As in the article about the on-premise connection, we use a corresponding destination, call the proxy and convert the request accordingly. The finished class now at a glance:

CLASS zcl_bs_demo_custom_company_qry DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC.

  PUBLIC SECTION.
    INTERFACES if_rap_query_provider.

    CONSTANTS c_destination TYPE string                                         VALUE `<destination-service-id>`.
    CONSTANTS c_entity      TYPE /iwbep/if_cp_runtime_types=>ty_entity_set_name VALUE 'COMPANYNAMES'.

    CLASS-METHODS get_proxy
      RETURNING VALUE(ro_result) TYPE REF TO /iwbep/if_cp_client_proxy.

  PRIVATE SECTION.
    TYPES tt_result TYPE STANDARD TABLE OF ZBS_I_RAPCustomEntityCNames WITH EMPTY KEY.

    METHODS read_data_by_request
      IMPORTING io_request TYPE REF TO if_rap_query_request
      EXPORTING et_result  TYPE tt_result
                ed_count   TYPE int8.
ENDCLASS.


CLASS zcl_bs_demo_custom_company_qry IMPLEMENTATION.
  METHOD if_rap_query_provider~select.
    DATA lt_result TYPE STANDARD TABLE OF ZBS_R_RAPCustomCompanyNames.

    read_data_by_request( EXPORTING io_request = io_request
                          IMPORTING et_result  = DATA(lt_company_names)
                                    ed_count   = DATA(ld_count) ).

    IF io_request->is_total_numb_of_rec_requested( ).
      io_response->set_total_number_of_records( ld_count ).
    ENDIF.

    IF io_request->is_data_requested( ).
      lt_result = CORRESPONDING #( lt_company_names ).
      io_response->set_data( lt_result ).
    ENDIF.
  ENDMETHOD.


  METHOD get_proxy.
    TRY.
        DATA(lo_destination) = cl_http_destination_provider=>create_by_cloud_destination(
            i_name       = c_destination
            i_authn_mode = if_a4c_cp_service=>service_specific ).

        DATA(lo_client) = cl_web_http_client_manager=>create_by_http_destination( lo_destination ).

        ro_result = cl_web_odata_client_factory=>create_v2_remote_proxy(
            iv_service_definition_name = 'ZBS_DEMO_RAP_ONPREM_ODATA'
            io_http_client             = lo_client
            iv_relative_service_root   = '/sap/opu/odata/sap/ZBS_API_COMPANY_NAMES_O2' ).

      CATCH cx_root.
    ENDTRY.
  ENDMETHOD.


  METHOD read_data_by_request.
    DATA lo_root_filter_node TYPE REF TO /iwbep/if_cp_filter_node.

    TRY.
        DATA(lo_request) = get_proxy( )->create_resource_for_entity_set( c_entity )->create_request_for_read( ).

        DATA(lt_filter_condition) = io_request->get_filter( )->get_as_ranges( ).
        DATA(lt_requested_elements) = io_request->get_requested_elements( ).
        DATA(lt_sort_elements) = io_request->get_sort_elements( ).
        DATA(ld_skip) = io_request->get_paging( )->get_offset( ).
        DATA(ld_top) = io_request->get_paging( )->get_page_size( ).
        DATA(ld_is_data_requested)  = io_request->is_data_requested( ).
        DATA(ld_is_count_requested) = io_request->is_total_numb_of_rec_requested( ).

        DATA(lo_filter_factory) = lo_request->create_filter_factory( ).
        LOOP AT lt_filter_condition INTO DATA(ls_filter_condition).
          DATA(lo_filter_node) = lo_filter_factory->create_by_range( iv_property_path = ls_filter_condition-name
                                                                     it_range         = ls_filter_condition-range ).

          IF lo_root_filter_node IS INITIAL.
            lo_root_filter_node = lo_filter_node.
          ELSE.
            lo_root_filter_node = lo_root_filter_node->and( lo_filter_node ).
          ENDIF.
        ENDLOOP.

        IF lo_root_filter_node IS NOT INITIAL.
          lo_request->set_filter( lo_root_filter_node ).
        ENDIF.

        IF lt_requested_elements IS NOT INITIAL.
          lo_request->set_select_properties( CORRESPONDING #( lt_requested_elements ) ).
        ENDIF.

        IF lt_sort_elements IS NOT INITIAL.
          lo_request->set_orderby( CORRESPONDING #( lt_sort_elements MAPPING property_path = element_name ) ).
        ENDIF.

        IF ld_is_data_requested = abap_true.
          lo_request->set_skip( CONV #( ld_skip ) ).

          IF ld_top > 0.
            lo_request->set_top( CONV #( ld_top ) ).
          ENDIF.
        ELSE.
          lo_request->request_no_business_data( ).
        ENDIF.

        IF ld_is_count_requested = abap_true.
          lo_request->request_count( ).
        ENDIF.

        DATA(lo_response) = lo_request->execute( ).
        lo_response->get_business_data( IMPORTING et_business_data = et_result ).
        ed_count = lo_response->get_count( ).

      CATCH cx_root.
    ENDTRY.
  ENDMETHOD.
ENDCLASS.

 

Since we have already dealt with the processing in an article, the call and the functionality can be read there. In the next step we define the behavior definition to describe the functions of the RAP object and to be able to express them later:

unmanaged implementation in class zbp_bs_demo_company_names unique;
strict ( 1 );

define behavior for ZBS_R_RAPCustomCompanyNames alias CompanyNames
lock master
authorization master ( instance )
{
  field ( readonly : update ) CompanyName;

  create;
  update;
  delete;
}

 

The object is set to Unmanaged and we set the key to READONLY when updating the instance. We then generate the empty behavior implementation so that the object is consistent. So that we can call the application in the Fiori Elements Preview, we still have to create a service definition and a service binding. After "publishing" the service, in this case an OData v2 service, we can start the application for the first time.

 

Execution

If we now start the application by clicking on "Go", all data sets are retrieved on-premise and displayed to us.

 

We can now use the filter to limit the amount of data and, for example, search for the right company.

 

With a click on the item we navigate to the object page or detail page and also see the long text. With the annotation "UI.multiLineText: true" we transform the simple input field into a text box and can thus see more text.

 

Data management

As with the last unmanaged scenario for the local scenario, we have to implement all operations ourselves here too. To do this, we create a buffer object in the behavior implementation to save our data:

CLASS lcl_data_buffer DEFINITION.
  PUBLIC SECTION.
    TYPES tt_data TYPE STANDARD TABLE OF ZBS_R_RAPCustomCompanyNames WITH EMPTY KEY.

    CLASS-DATA gt_create TYPE tt_data.
    CLASS-DATA gt_update TYPE tt_data.
    CLASS-DATA gt_delete TYPE tt_data.
ENDCLASS.

 

With the CRUD operations, we now have to fill the buffer accordingly, so we have to read the data again remotely during the update, or we implement the READ method to get to the data via EML. Finally, in the SAVE method, we now have to take care of the write access in the direction of the on-premise system. The finished implementation could look like this:

DATA ls_remote_create TYPE zbs_rap_companynames.
DATA ls_remote_update TYPE zbs_rap_companynames.

LOOP AT lcl_data_buffer=>gt_create INTO DATA(ls_create).
  ls_remote_create = CORRESPONDING #( ls_create ).

  DATA(lo_request_create) = zcl_bs_demo_custom_company_qry=>get_proxy( )->create_resource_for_entity_set(
      zcl_bs_demo_custom_company_qry=>c_entity )->create_request_for_create( ).
  lo_request_create->set_business_data( ls_remote_create ).
  lo_request_create->execute( ).
ENDLOOP.

LOOP AT lcl_data_buffer=>gt_update INTO DATA(ls_update).
  ls_remote_update = CORRESPONDING #( ls_update ).
  DATA(ls_key) = VALUE zbs_rap_companynames( companyname = ls_remote_update-CompanyName ).

  DATA(lo_request_update) = zcl_bs_demo_custom_company_qry=>get_proxy( )->create_resource_for_entity_set(
      zcl_bs_demo_custom_company_qry=>c_entity
    )->navigate_with_key( ls_key
    )->create_request_for_update( /iwbep/if_cp_request_update=>gcs_update_semantic-put ).
  lo_request_update->set_business_data( ls_remote_update ).
  lo_request_update->execute( ).
ENDLOOP.

LOOP AT lcl_data_buffer=>gt_delete INTO DATA(ls_delete).
  DATA(ls_key_delete) = VALUE zbs_rap_companynames( companyname = ls_delete-CompanyName ).

  DATA(lo_request_delete) = zcl_bs_demo_custom_company_qry=>get_proxy( )->create_resource_for_entity_set(
      zcl_bs_demo_custom_company_qry=>c_entity
    )->navigate_with_key( ls_key_delete
    )->create_request_for_delete( ).
  lo_request_delete->execute( ).
ENDLOOP.

CLEAR: lcl_data_buffer=>gt_create, lcl_data_buffer=>gt_update, lcl_data_buffer=>gt_delete.

 

Hint: During the implementation, we deliberately avoided error handling in order to map the pure accesses and calls. Proper handling of exceptions and invocation errors should be implemented under all circumstances.

 

Complete example

As always, you can find the complete example in our GitHub repository, via the commit you can access the changes made and new objects.

 

Conclusion

Implementing an unmanaged scenario via a custom entity is slightly different than building a normal RAP object. Thanks to this and the linked article, this should no longer be a problem for you. This approach is also the basis for hybrid applications in the ABAP environment.


Included topics:
RAPBTPUnmanagedRemote
Comments (0)



And further ...

Are you satisfied with the content of the article? We post new content in the ABAP area every Friday and irregularly in all other areas. Take a look at our tools and apps, we provide them free of charge.


RAP - Translation app (example)

Category - ABAP

Let's take a look at a practical example of developing a RAP application in the ABAP environment and how you can create an app with little effort.

08/02/2024

RAP - Custom Entity Value Help (Deep Dive)

Category - ABAP

With the Custom Entity you have the most freedom in RAP when developing ABAP Cloud applications, but what about potential errors?

07/12/2024

RAP - Deep Action in OData v4

Category - ABAP

In this article we will look at actions with deep structures, how we can create them and pass data to an API endpoint.

05/24/2024

BTP - Connect On-Premise (Consumption Model v2)

Category - ABAP

In this article we want to provide another update on the connection of on-premise systems and how this is done with a communication arrangement.

12/15/2023

RAP - Show app count (Tile)

Category - ABAP

This example is about displaying a counter on the tile of a Fiori Elements application and how such a thing can be implemented.

10/06/2023