This is continuation of the Unmanaged RAP Scenario from the RAP series.

The previous posts – 

ABAP RESTful Application Programming Model [4] – Unmanaged Scenario Part 1 covered creation of CDS View Entities, Projection Entities and Metadata Extensions.

ABAP RESTful Application Programming Model [5] – Unmanaged Scenario Part 2 covered creation of Behavior Definition, Service Definition, Service Binding and Testing the service.

Now comes probably the most important part – Implementing the behavior in the class.

Let us look at the behavior definition one more time.

unmanaged implementation in class zbp_i_jp_travel unique;
//strict;

define behavior for ZI_JP_TRAV_01 alias Travel
//late numbering
lock master
//authorization master(global)
etag master LastChangedAt
{
  field ( readonly ) TravelID, TotalPrice;
  field ( mandatory ) AgencyID, CustomerID, BeginDate, EndDate;
  create;
  update;
  delete;
  action ( features : instance ) set_status_booked result [1] $self;
  association _Booking { create ( features : instance ); }
  mapping for /dmo/travel control /dmo/s_travel_intx
  {
    AgencyID = agency_id;
    BeginDate = begin_date;
    BookingFee = booking_fee;
    CurrencyCode = currency_code;
    CustomerID = customer_id;
    EndDate = end_date;
    Status = status;
    TotalPrice = total_price;
    Description = description;
    TravelID = travel_id;
  }
  //mapping for zjp_rap_trav_01 control
}

define behavior for zi_JP_book_01 alias Booking
implementation in class zbp_i_jp_booking unique
//late numbering
lock dependent by _Travel
//authorization dependent by _Travel
etag dependent by _Travel
{
  field ( readonly ) TravelID, BookingID;
  field ( mandatory ) BookingDate, CustomerID, AirlineID, ConnectionID, FlightDate;
  update;
  delete;
  association _Travel;
  mapping for /dmo/booking control /dmo/s_booking_intx
  {
    BookingID = booking_id;
    AirlineID = carrier_id;
    BookingDate = booking_date;
    ConnectionID = connection_id;
    CurrencyCode = currency_code;
    CustomerID = customer_id;
    FlightDate = flight_date;
    FlightPrice = flight_price;
    TravelID = travel_id;
  }
}

The service has two Entities i.e. Travel and Booking. Each will be implemented in related class. The classes are mentioned in the below statements from the behavior code.

unmanaged implementation in class zbp_i_jp_travel unique;

define behavior for zi_JP_book_01 alias Booking
implementation in class zbp_i_jp_booking unique

Let us take a look at the classes.

ZBP_I_JP_TRAVEL

The global class is created as an abstract class and the actual methods are implemented in the Local Types tab highlighted below.

The Local Types has a behavior implementer and behavior saver classes as shown below. Initially all the methods will be empty.

ZBP_I_JP_BOOKING

The booking class only has behavior handler and not the saver as saver is only in parent entity.

Travel Entity Behavior Implementation

Create Travel

Where the operation is defined in behavior definition?

Method to be implemented – Create

Method definition shows that it imports parameter entities.

METHODS create FOR MODIFY
  IMPORTING entities FOR CREATE Travel.

Now, we come to the code explanation. The code is written using ABAP expressions / new syntax / ABAP 7.4+ syntax. So if you do not understand the syntax head over to page ABAP Expressions (7.4+) for help.

  • Create method is going to be called for one travel at a time, however the data is sent in a table entities so we need to loop through.
  • We then have to map the data to target structure using key word MAPPING FROM ENTITY USING CONTROL
  • After this we can call BAPI / FM or any other custom logic to create the entity. Here FM /DMO/FLIGHT_TRAVEL_CREATE is used.
  • Once the data is created, output is updated in table mapped-travel. Here mapped is going to be constant in all such classes you will implement. This is required so that the Travel Id that is created is passed back to front end and the new Travel Id can be shown on the app.
  • It is important to pass back field %cid from the input and the key fields of the entity. In this case the key is travelid.
  • In case there are errors and the data is not created, the messages can be passed back using tables failed-travel and reported-travel. Similar to mapped, the failed and reported parameters will be constant.
  • This is required to inform the frontend that there is a failure (failed) and to show the actual messages (reported)
  • failed-travel – here, %cid is mandatory. reported-travel additionally has %create parameter where we need to pass ‘X’.

Here is the complete code for the method to try out.

  METHOD create.
    DATA : ls_travel TYPE /dmo/travel,
           lt_msg    TYPE /dmo/t_message.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<lfs_travel_entity>).

      ls_travel = CORRESPONDING #( <lfs_travel_entity> MAPPING FROM ENTITY USING CONTROL ).

      CALL FUNCTION '/DMO/FLIGHT_TRAVEL_CREATE'
        EXPORTING
          is_travel   = CORRESPONDING /dmo/s_travel_in( ls_travel )
        IMPORTING
          es_travel   = ls_travel
          et_messages = lt_msg.
      IF lt_msg IS INITIAL.
        mapped-travel = VALUE #( BASE mapped-travel
                                ( %cid = <lfs_travel_entity>-%cid
                                  travelid = ls_travel-travel_id
                                ) ).
      ELSE.
        LOOP AT lt_msg INTO DATA(ls_msg).
          APPEND VALUE #( %cid = <lfs_travel_entity>-%cid
              travelid = <lfs_travel_entity>-TravelID )
              TO failed-travel.

          APPEND VALUE #( %msg = new_message( id       = ls_msg-msgid
                                              number   = ls_msg-msgno
                                              v1       = ls_msg-msgv1
                                              v2       = ls_msg-msgv2
                                              v3       = ls_msg-msgv3
                                              v4       = ls_msg-msgv4
                                              severity = if_abap_behv_message=>severity-error )
                          %key-TravelID = <lfs_travel_entity>-TravelID
                          %cid =  <lfs_travel_entity>-%cid
                          %create = 'X'
                          TravelID = <lfs_travel_entity>-TravelID )
                          TO reported-travel.
        ENDLOOP.
      ENDIF.

    ENDLOOP.

Once the code is added move to the saver local class towards the end and add below code to method save.

METHOD save.
CALL FUNCTION '/DMO/FLIGHT_TRAVEL_SAVE'.
CALL FUNCTION '/DMO/FLIGHT_TRAVEL_INITIALIZE'.
ENDMETHOD.

Activate the class and then test the Create Travel behavior.

1. Click create.

2. Notice the blank travel id. Enter data in all fields and click Create.

3. The Travel id is created. Currently there are no bookings but the booking section appears where bookings can be added.

4. In case you need to debug, put the breakpoint by double clicking at the highlighted place.

5. The debug perspective is presented in Eclipse.

Note : Don’t forget to add code in saver class in save method – else the data will not be saved.

Now, it is not possible to explain everything in detail in the blog, so here is the rest of the code that you can try out. Do reach out in case explanation is needed. I have tried to highlight important points.

Update Travel

  • Data comes in entities
  • mapped-travel is not required to be filled as all the data is available in frontend already
  • failed-travel & reported-travel to be filled if error occurs
  • Input’s %cid_ref field is to be used to pass data to failed-travel-%cid
  METHOD update.

    DATA : ls_travel  TYPE /dmo/travel,
           ls_travelx TYPE /dmo/s_travel_inx,
           lt_msg     TYPE /dmo/t_message.

    DATA ls_message TYPE REF TO if_abap_behv_message.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<lfs_travel_entity>).

      ls_travel = CORRESPONDING #( <lfs_travel_entity> MAPPING FROM ENTITY ).
      ls_travelx-travel_id = <lfs_travel_entity>-TravelID.
      ls_travelx-_intx = CORRESPONDING #( <lfs_travel_entity> MAPPING FROM ENTITY ).

      CALL FUNCTION '/DMO/FLIGHT_TRAVEL_UPDATE'
        EXPORTING
          is_travel   = CORRESPONDING /dmo/s_travel_in( ls_travel )
          is_travelx  = ls_travelx
        IMPORTING
          et_messages = lt_msg.
      IF lt_msg IS NOT INITIAL.
        LOOP AT lt_msg INTO DATA(ls_msg) WHERE msgty CA 'EA'.
          APPEND VALUE #( %cid = <lfs_travel_entity>-%cid_ref
              travelid = <lfs_travel_entity>-TravelID )
              TO failed-travel.

          APPEND VALUE #( %msg = new_message( id       = ls_msg-msgid
                                              number   = ls_msg-msgno
                                              v1       = ls_msg-msgv1
                                              v2       = ls_msg-msgv2
                                              v3       = ls_msg-msgv3
                                              v4       = ls_msg-msgv4
                                              severity = if_abap_behv_message=>severity-error )
                          %key-TravelID = <lfs_travel_entity>-TravelID
                          %cid =  <lfs_travel_entity>-%cid_ref
                          %update = 'X'
                          TravelID = <lfs_travel_entity>-TravelID )
                          TO reported-travel.

        ENDLOOP.
      ENDIF.

    ENDLOOP.

  ENDMETHOD.

Delete Travel

  • Input is keys table, as for delete you don’t need complete data
  • mapped-travel is not required to be filled as all the data is available in frontend already
  • failed-travel & reported-travel to be filled if error occurs
  • Input’s %cid_ref field is to be used to pass data to failed-travel-%cid
  METHOD delete.
    DATA : lt_msg     TYPE /dmo/t_message.
    LOOP AT keys ASSIGNING FIELD-SYMBOL(<lfs_del_keys>).

      CALL FUNCTION '/DMO/FLIGHT_TRAVEL_DELETE'
        EXPORTING
          iv_travel_id = <lfs_del_keys>-TravelID
        IMPORTING
          et_messages  = lt_msg.
      IF lt_msg IS NOT INITIAL.
        LOOP AT lt_msg INTO DATA(ls_msg) WHERE msgty CA 'EA'.
          APPEND VALUE #( %cid     = <lfs_del_keys>-%cid_ref
                          travelid = <lfs_del_keys>-TravelID
                        ) TO failed-travel.

          APPEND VALUE #( %msg = new_message( id       = ls_msg-msgid
                                              number   = ls_msg-msgno
                                              v1       = ls_msg-msgv1
                                              v2       = ls_msg-msgv2
                                              v3       = ls_msg-msgv3
                                              v4       = ls_msg-msgv4
                                              severity = if_abap_behv_message=>severity-error )
                          %key-TravelID = <lfs_del_keys>-TravelID
                          %cid          =  <lfs_del_keys>-%cid_ref
                          %delete       = 'X'
                          TravelID      = <lfs_del_keys>-TravelID
                        ) TO reported-travel.

        ENDLOOP.
      ENDIF.

    ENDLOOP.

  ENDMETHOD.

Set to book

This is additional button, which needs to be active only for the rows where status is not already booked i.e. ‘B’.

Behavior defines this as below. The action is defined as set_status_booked. Result is [1] i.e. single record which is $self i.e. the record that is passed is updated.

( features : instance ) – Instance Feature Control can be defined for fields, operation or action. This is a way to enable or disable certain field/operation/action based on a state of the entity.

If booking status is other than ‘B’, enable the button else disable the button.

Here is how the Instance Feature Control is implemented in the class.

Implement method read

We need to populate the output table result based on input table keys.

  METHOD read.

    SELECT * FROM zi_rap_trav_01
        FOR ALL ENTRIES IN @keys
        WHERE TravelId = @keys-TravelId
        into CORRESPONDING FIELDS OF table @result.

  ENDMETHOD.

Implement method get_instance_features

Here, we first need to call READ ENTITIES which in turn calls the method read and then populate table result.

%features-%action-set_status_booked is set to enabled or disabled. Similar logic can be used to enable / disable other actions as well.

METHOD get_instance_features.

  READ ENTITIES OF ZI_RAP_TRAV_01 IN LOCAL MODE
    ENTITY travel
       FIELDS (  travelID status )
       WITH CORRESPONDING #( keys )
     RESULT DATA(lt_travel_result)
     FAILED failed.

  result = 
    VALUE #( FOR ls_travel IN lt_travel_result
      ( %key = ls_travel-%key
        %features-%action-set_status_booked = COND #( WHEN ls_travel-status = 'B'
                                                      THEN if_abap_behv=>fc-o-disabled
                                                      ELSE if_abap_behv=>fc-o-enabled )
       ) ).
ENDMETHOD.

Booking Entity Behavior Implementation

Booking Create

The booking operations are split into two classes. The create is implemented in travel related class itself. The create operation for this association _Booking is in the Travel behavior.

Method cba_Booking

  • This is a lot of code. In short, we get a travel id here, based on the travel id we fetch all bookings.
  • Pick up the largest booking number and add new booking numbers with incrementing by 1 and put the data
  • Then call FM /DMO/FLIGHT_TRAVEL_UPDATE to update/add the booking and pass the booking id to mapped-booking / failed-booking depending on whether or not the booking update is successful.
METHOD cba_Booking.

  DATA : lt_booking TYPE /dmo/t_booking,
         lt_msg     TYPE /dmo/t_message,
         lt_msg_b   TYPE /dmo/t_message.

  LOOP AT entities_cba ASSIGNING FIELD-SYMBOL(<lfs_travel_booking>).

    DATA(lv_travel_id) = <lfs_travel_booking>-TravelId.

    "Get Travel and Booking Data
    CALL FUNCTION '/DMO/FLIGHT_TRAVEL_READ'
      EXPORTING
        iv_travel_id = lv_travel_id
      IMPORTING
        et_booking   = lt_booking
        et_messages  = lt_msg.

    IF lt_msg IS INITIAL.
      IF lt_booking IS NOT INITIAL.
        DATA(lv_last_booking_id) = lt_booking[ lines( lt_booking ) ]-booking_id.
      ELSE.
        CLEAR lv_last_booking_id.
      ENDIF.

      LOOP AT <lfs_travel_booking>-%target ASSIGNING FIELD-SYMBOL(<lfs_booking>).
        DATA(ls_booking) = CORRESPONDING /dmo/booking( <lfs_booking> MAPPING FROM ENTITY USING CONTROL ).
        lv_last_booking_id += 1.
        ls_booking-booking_id = lv_last_booking_id.

        CALL FUNCTION '/DMO/FLIGHT_TRAVEL_UPDATE'
          EXPORTING
            is_travel   = VALUE /dmo/s_travel_in( travel_id = lv_travel_id )
            is_travelx  = VALUE /dmo/s_travel_inx( travel_id = lv_travel_id )
            it_booking  = VALUE /dmo/t_booking_in( ( CORRESPONDING #( ls_booking ) ) )
            it_bookingx = VALUE /dmo/t_booking_inx( ( booking_id = ls_booking-booking_id
                                                      action_code = /dmo/if_flight_legacy=>action_code-create ) )
          IMPORTING
            et_messages = lt_msg_b.

        "Pass data back to UI
        INSERT VALUE #( %cid = <lfs_booking>-%cid
                        travelid = lv_travel_id
                        bookingid = ls_booking-booking_id
                      ) INTO  TABLE mapped-booking.

        LOOP AT lt_msg_b INTO DATA(ls_msg) WHERE msgty CA 'EA'.
          APPEND VALUE #( %cid      = <lfs_booking>-%cid
                          travelid  = lv_travel_id
                          bookingid = ls_booking-booking_id
                        ) TO failed-booking.
          APPEND VALUE #( %msg = new_message( id       = ls_msg-msgid
                                              number   = ls_msg-msgno
                                              v1       = ls_msg-msgv1
                                              v2       = ls_msg-msgv2
                                              v3       = ls_msg-msgv3
                                              v4       = ls_msg-msgv4
                                              severity = if_abap_behv_message=>severity-error )
                          %key-TravelID = lv_travel_id
                          %key-bookingid = ls_booking-booking_id
                          %cid = <lfs_booking>-%cid
                          TravelID = lv_travel_id
                          bookingid = ls_booking-booking_id
                         ) TO reported-booking.
        ENDLOOP.
      ENDLOOP.

    ELSE.

      LOOP AT lt_msg INTO ls_msg WHERE msgty CA 'EA'.
        APPEND VALUE #( %cid     = <lfs_travel_booking>-%cid_ref
                        travelid = lv_travel_id
                      ) TO failed-travel.

        APPEND VALUE #( %msg = new_message( id       = ls_msg-msgid
                                            number   = ls_msg-msgno
                                            v1       = ls_msg-msgv1
                                            v2       = ls_msg-msgv2
                                            v3       = ls_msg-msgv3
                                            v4       = ls_msg-msgv4
                                            severity = if_abap_behv_message=>severity-error )
                        %key-TravelID = lv_travel_id
                        %cid          = <lfs_travel_booking>-%cid_ref
                        TravelID      = lv_travel_id
                      ) TO reported-travel.
      ENDLOOP.
    ENDIF.
  ENDLOOP.
ENDMETHOD.

Booking update and delete are implemented in class zbp_i_jp_booking. In both methods, FM /DMO/FLIGHT_TRAVEL_UPDATE is called with update / delete flags. In many actual scenarios, delete would mean setting up deletion flag or delete would not be an option at all.

Booking update

  METHOD update.

    DATA : lt_msg     TYPE /dmo/t_message.

    LOOP AT entities ASSIGNING FIELD-SYMBOL(<lfs_booking>).
      DATA(ls_booking) = CORRESPONDING /dmo/booking( <lfs_booking> MAPPING FROM ENTITY ).

      CALL FUNCTION '/DMO/FLIGHT_TRAVEL_UPDATE'
        EXPORTING
          is_travel   = VALUE /dmo/s_travel_in( travel_id = <lfs_booking>-TravelID )
          is_travelx  = VALUE /dmo/s_travel_inx( travel_id = <lfs_booking>-TravelID )
          it_booking  = VALUE /dmo/t_booking_in( ( CORRESPONDING #( ls_booking ) ) )
          it_bookingx = VALUE /dmo/t_booking_inx( ( booking_id = <lfs_booking>-BookingID
                                                    _intx      = CORRESPONDING #( <lfs_booking> MAPPING FROM ENTITY )
                                                    action_code = /dmo/if_flight_legacy=>action_code-update ) )
        IMPORTING
          et_messages = lt_msg.

      "Pass data back to UI
      INSERT VALUE #( %cid = <lfs_booking>-%cid_ref
                      travelid = <lfs_booking>-TravelID
                      bookingid = <lfs_booking>-BookingID
                    ) INTO  TABLE mapped-booking.

      LOOP AT lt_msg INTO DATA(ls_msg) WHERE msgty CA 'EA'.
        APPEND VALUE #( %cid      =  <lfs_booking>-%cid_ref
                        travelid  = <lfs_booking>-TravelID
                        bookingid = <lfs_booking>-BookingID
                      ) TO failed-booking.

        APPEND VALUE #( %msg = new_message( id       = ls_msg-msgid
                                            number   = ls_msg-msgno
                                            v1       = ls_msg-msgv1
                                            v2       = ls_msg-msgv2
                                            v3       = ls_msg-msgv3
                                            v4       = ls_msg-msgv4
                                            severity = if_abap_behv_message=>severity-error )
                        %key-TravelID  = <lfs_booking>-TravelID
                        %key-bookingid = ls_booking-booking_id
                        %cid           = <lfs_booking>-%cid_ref
                        %update        = 'X'
                        TravelID       = <lfs_booking>-TravelID
                        bookingid      = <lfs_booking>-BookingID
                       ) TO reported-booking.
      ENDLOOP.
    ENDLOOP.
  ENDMETHOD.

Booking delete

METHOD delete.
    DATA : lt_msg     TYPE /dmo/t_message.
    LOOP AT keys ASSIGNING FIELD-SYMBOL(<lfs_booking>).

      CALL FUNCTION '/DMO/FLIGHT_TRAVEL_UPDATE'
        EXPORTING
          is_travel   = VALUE /dmo/s_travel_in( travel_id = <lfs_booking>-TravelID )
          is_travelx  = VALUE /dmo/s_travel_inx( travel_id = <lfs_booking>-TravelID )
          it_booking  = VALUE /dmo/t_booking_in( ( booking_id = <lfs_booking>-BookingID ) )
          it_bookingx = VALUE /dmo/t_booking_inx( ( booking_id = <lfs_booking>-BookingID
                                                    action_code = /dmo/if_flight_legacy=>action_code-delete ) )
        IMPORTING
          et_messages = lt_msg.
      IF lt_msg IS NOT INITIAL.
        LOOP AT lt_msg INTO DATA(ls_msg) WHERE msgty CA 'EA'.
          APPEND VALUE #( %cid     = <lfs_booking>-%cid_ref
                          travelid = <lfs_booking>-TravelID
                          bookingid = <lfs_booking>-BookingID
                        ) TO failed-booking.

          APPEND VALUE #( %msg = new_message( id       = ls_msg-msgid
                                              number   = ls_msg-msgno
                                              v1       = ls_msg-msgv1
                                              v2       = ls_msg-msgv2
                                              v3       = ls_msg-msgv3
                                              v4       = ls_msg-msgv4
                                              severity = if_abap_behv_message=>severity-error )
                          %key-TravelID = <lfs_booking>-TravelID
                          %key-bookingid = <lfs_booking>-BookingID
                          %cid          =  <lfs_booking>-%cid_ref
                          %delete       = 'X'
                          TravelID      = <lfs_booking>-TravelID
                          bookingid = <lfs_booking>-BookingID
                        ) TO reported-booking.
        ENDLOOP.
      ENDIF.
    ENDLOOP.
  ENDMETHOD.

Now test all the functionality and you have a working service. This marks end of the 3 part series on unmanaged scenario. In next post we will see how the unmanaged service can be used to create a Fiori element app in Business Application Studio.

This has become a long post and I have tried to explain as much as I can. I know I have skipped few key words / concepts like strict, late numbering, etag master etc, which I will try to explain in some post later.

For now, try this, if you face issues – let me know in comments and I will try to get back as soon as I can.

Visit ABAP RESTful Application Programming Model to explore all articles on ABAP RAP Model.


If you like the content, please subscribe…

Join 4,010 other subscribers

Discovering ABAP YouTube Channel