Quantcast
Channel: SCN : All Content - SAP Gateway
Viewing all articles
Browse latest Browse all 2823

Reusable READ functionality. Part 3

$
0
0

Welcome Back

 

Welcome back again to our third installment of the series. Today we will cover off navigation and the odata $expand operator. If you haven’t followed along already check out the previous 2 blogs to find out how we ended up here, at part 3.

 

Part 1 - Developing data driven reusable READ functionality.

Part 2 - Reusable READ functionality. $select, $orderby, $top, $skip, $inlinecount, $count

 

 

SEGW

 

Let’s start with defining some new entities to use in our examples. Referring back to part 1 of this series we’ll import from DDIC structures to create our new entities. We’ll create 3 new entities, Flight (using table SFLIGHT), FlightSchedule (using table SPFLI) and Plane (using table SAPLANE). Define them as below with their Name, Key and ABAP fieldname.

 

entities.png

 

Association / Navigation / Referential Constraints

Now let’s create some associations and navigations using the wizard, as below. As part of the creation we will also setup some referential constraints. This is the information we will be using in our navigation routine to determine associations between entities.

 

02_createAssoc_sm.png

 

 

AirlineToSchedule

The first association and navigation we will create is from the Airline entity to the FlightSchedule entity. As in the image below, in the first step specify the association details and navigation property followed by the referential constraints. Use the search help to look the values up to make it a little easier. Although you will need to type the Association Name and Navigation Property (if you wish to change it as below ).

 

airline_schedule.png

 

 

ScheduleToFlight

Next we will create the association and navigation from the FlightSchedule entity to the Flight entity. Follow the same steps as above with the below image as reference.

 

schedule_flight.png

 

FlightToPlane

Finally we will create an association and navigation from the Flight entity to the Plane entity. Again follow the same steps using the image below as reference.

Though note in this last example we are setting the dependent navigation property.

 

flight_plane.png

 

Complete Model

After all the changes made the complete model should look as below in the overview section.

 

09_final_model.png

 

Now save the model and generate our runtime objects as we did in part 1. Make sure you can see your new entities and associations by running the service with the $metadata uri option:

 

     /sap/opu/odata/sap/ZFLIGHT_SRV/$metadata

 

All good? Let’s continue.

 

 

Data Provider Extension Class - ZCL_ZFLIGHT_DPC_EXT

 

Now we will redefine our data provider methods for the newly created entities.  Simply redefine the below methods of class

ZCL_ZFLIGHT_DPC_EXT and add our standard code for get_entity() and get_entityset(), as we did in part 1 of the series for the Airline entity.

 

Redefine the methods:

10_redefine_methods.png

 

 

Code for ...get_entity():

 

get_gw_helper( )->get_entity(  exporting io_tech_request_context = io_tech_request_context  changing  cs_entity = er_entity   cs_response_context = es_response_context ).

 

Code for ...get_entityset():

 

get_gw_helper( )->get_entityset(  exporting io_tech_request_context = io_tech_request_context  changing  ct_entityset = et_entityset   cs_response_context = es_response_context ).

 

 

This stems back to our original goal of adding as little as possible to our data provider extension class in the way of reading entities and allowing the helper class to do most of the work dynamically for us.

 

Finished, that’s it for the data provider class. Save the changes and activate.

 

 

Helper Class - ZCL_GATEWAY_HELPER

Now let’s add the expand and navigation logic to our helper class.

 

First we will create two new class attributes as below.

 

class attrib.PNG

 

Mv_conv_keys, will be used to determine if we should convert our source keys, mv_entity_name will be used to check if we need to run though our init() method due to a new entity, as explained below in the init() change.

 

 

Init(), change

Now we will be running though the gateway frame work executing expands. So we’ll place a check in this method to ensure that we are not loading all the model details again for the same entity. We will check the entity name passed in does not match the entity last processed.

 

Place these new lines of code after the declaration for the field symbols.

 

  field-symbols:  <ls_property> like line of lt_table_properties.
* first check if we already loaded this entity, eg we might be processing
* through a $expand.  if mv_entity_name <> iv_entity_name.

and the following new lines at the end of the method:

 

* save this for comparison on the next round though.
* if we have already computed this then lets not do it again, eg in an $expand!    mv_entity_name = iv_entity_name.  endif.
endmethod.

 

get_entity_nav(), new method, protected

The first new method we will create is a helper method to be used with our next method process_nav_path(). The purpose of this method is select our current navigation entity in readiness for our next navigation. The method will take 3 parameters the entity name, the where condition and the returned entity selected. This entity can then be used as the data for next navigations required keys. Don’t forget to add the exceptions also.

 

Method Signature

11_get_entity_sig.png

 

At the start we just free any data which may be left over from a previous call, as you’ll see in the next method the call is embedded in the navigation loop. Next we get the DDIC structure name from the entity as we have seen in previous examples. We create some data based on this DDIC structure and assign to a field symbol ready for selection. The select is then processed using the where condition passed in. An exception will be raised if no data can be selected as we’ll be unable to continue our navigation.

 

 

method get_entity_nav.  data: lr_entity type ref to /iwbep/if_mgw_med_odata_types=>ty_s_med_entity_type.  field-symbols: <ls_entity> type any.  lr_entity = mr_model->get_entity( iv_entity_name ).  free: er_entity.
*   create our data to select into  create data er_entity type (lr_entity->attribute_struct).  assign er_entity->* to <ls_entity>.
*   select entity, for navigation  select single *         into <ls_entity>         from (lr_entity->attribute_struct)         where (iv_db_where).  if sy-subrc <> 0.    raise exception type /iwbep/cx_mgw_busi_exception      exporting        textid            = /iwbep/cx_mgw_busi_exception=>business_error_unlimited        message_unlimited = 'Navigation select failed on entity, ' && iv_entity_name && '.'.  endif.
endmethod.

process_nav_path(), new method, protected

This is a big one! I’ll try to explain most of what is going on here and justifications, in a way which makes sense!

 

To start the method takes 4 parameters, these being the navigation path, the source keys, a returned parameter to determine if we are navigating and finally the OSQL where condition to be executed in the calling get_entity() or get_entityset() method. Again remember to add the exceptions.

 

 

Method Signature

11_process_nav_sig.png

Initially we check to see if there is any navigation, if not simply exit. Otherwise we continue to loop over the navigation table.

 

First iteration

In the first iteration only (defined by AT FIRST.) we build our OSQL where condition based on the source keys. The source keys will only be converted if this is the first time though a get…() method, for example not an subsequent call via an expand or navigation, as in this case the keys should already be converted.

 

To convert the keys we use the key converter contained in the navigation structure attribute KEY_CONVERTER. We do this by defining some dynamic data based on the DDIC structure and call the EXECUTE() method of the key converter, the results are returned back into our field symbol to be used in our source key loop, again only if this is the first time in a get…() method. Otherwise we simply use the keys passed into parameter it_source_keys.

 

Once the OSQL where condition has been built we call our new method above get_entity_nav() to retrieve our entity details.

 

All iterations

Continuing on from the above first / initial processing, we can either process navigation keys or process our referential constraints.

 

Navigations with keys

Following on from the first lot of processing, we check to see if we have any key fields from navigations. For example:

 

src_nav_keys.png

If we do then we convert the navigation keys using the class: /iwbep/cl_mgw_req_key_convert. We first create the key converter using the target entity type. Next create some dynamic data based on the DDIC structure of the target entity, and finally call execute() method of the key converter to convert our key values. We loop over the key tab first attempting to get the same key value from the entity details we have at hand. This is in an attempt to stop invalid navigations where keys do not match, for example:

 

keys_no_match.png

 

If the key value can be found in the entity and it does not match the value in the navigation key an exception is raised, otherwise the value is added to the OSQL where condition. If the key value cannot be found in the entity then the value is taken directly from our converted navigation keys.

 

Lastly we check if this is the last navigation, if so we want to return this OSQL where condition back to the caller to process. Otherwise we select our next entity, set the source entity to the previous target entity and continue our next loop iteration.

 

 

Referential Constraints

If we had no navigation keys then we will drop into processing the referential constraints. We set these up at the start of this blog during the association and navigation section. We first retrieve the navigation property, from this we can then pick up the association and from the association we then pick up the referential constraints.  We loop over the referential constraints retrieving the target property and matching this up with the source key property value from our entity. Using this information we can build our OSQL where condition, to be passed back to the caller.

 

Finally after the navigation loop we combine the where condition with the one passed in, being the condition from the odata $filter operator.

 

  method process_nav_path.    data: lv_tech_name type /iwbep/if_mgw_med_odata_types=>ty_e_med_technical_name,          lr_entity_type type ref to /iwbep/if_mgw_med_odata_types=>ty_s_med_entity_type,          lr_nav_prop type ref to /iwbep/if_mgw_med_odata_types=>ty_s_med_reference,          lr_assoc type ref to /iwbep/if_mgw_odata_re_assoc,          lr_assoc_ref type ref to /iwbep/if_mgw_med_odata_types=>ty_s_med_reference,          lt_ref_constraints type  /iwbep/if_mgw_odata_re_assoc=>ty_t_mgw_odata_ref_constraints,          lv_value type string,          lv_db_and type string value '',          lv_db_where type string,          lr_entity type ref to data,          lv_max_nav type i,          lv_src_entity type /iwbep/mgw_tech_name,          lr_key_values type ref to data,          lv_ddic type string,          lr_key_conv type ref to /iwbep/if_mgw_req_key_convert,          lr_etype type ref to /iwbep/if_mgw_odata_fw_etype.    field-symbols: <ls_nav_path> like line of it_nav_path,                   <ls_key_values> type any,                   <ls_keytab> type /iwbep/s_mgw_tech_pair,                   <ls_ref_constraints> like line of lt_ref_constraints,                   <ls_key> like line of it_source_keys,                   <lv_any> type any.    ev_navigating = abap_false.
* check if we are navigating    if it_nav_path is not initial.      ev_navigating = abap_true.
* get maximum number of navigations, as we what to process the
* last select from the callers method.      lv_max_nav = lines( it_nav_path ).      loop at it_nav_path assigning <ls_nav_path>.        at first.
* in our initial iteration, read our entity using our source keys
* as this is our starting point if first time through a get...()
* method then convert keys, otherwise keys should already be converted          if mv_conv_keys = abap_true.            lr_key_conv ?= <ls_nav_path>-key_converter.            lv_ddic = lr_key_conv->get_entity_type( )->get_structure( ).            create data lr_key_values type (lv_ddic).            assign lr_key_values->* to <ls_key_values>.            lr_key_conv->execute(              exporting it_tech_pair = it_source_keys              importing es_key_values = <ls_key_values>            ).          endif.          loop at it_source_keys assigning <ls_key>.
* grab key from converted source keys?            if mv_conv_keys = abap_true.              assign component <ls_key>-name of structure <ls_key_values> to <lv_any>.              lv_value = <lv_any>.            else.
* otherwise directly from source keys              lv_value = <ls_key>-value.            endif.            lv_db_where = lv_db_where && lv_db_and && `( ` && <ls_key>-name && ` = '` && lv_value && `' )`.            lv_db_and = ` and `.          endloop.
* select our entity to complete navigation          get_entity_nav(            exporting              iv_entity_name = <ls_nav_path>-source_entity_type              iv_db_where = lv_db_where            importing              er_entity = lr_entity ).          lv_src_entity = <ls_nav_path>-source_entity_type.        endat.        free: lv_db_where,lv_db_and.
* if we have navigation key then lets process them        if <ls_nav_path>-key_tab[] is not initial.
* Create a key converter here based on the target entity type and convert our keys          lr_etype = mr_model->get_entity_type( <ls_nav_path>-target_entity_type ).          free: lr_key_values, lr_key_conv.          create object lr_key_conv            type            /iwbep/cl_mgw_req_key_convert            exporting              io_entity_type = lr_etype.          lv_ddic = lr_etype->get_structure( ).          create data lr_key_values type (lv_ddic).          assign lr_key_values->* to <ls_key_values>.          lr_key_conv->execute(             exporting it_tech_pair = <ls_nav_path>-key_tab[]             importing es_key_values = <ls_key_values>          ).          loop at <ls_nav_path>-key_tab[] assigning <ls_keytab>.
*
* first attempt is to retrieve key from source entity, if it can be found use it,
* otherwise use key from keytab, this is to stop this sort of thing returning
* valid results:
*
* ZFLIGHT_SRV/AirlineSet(AirlineId='AA')/FlightScheduleSet(AirlineId='AZ',ConnectionId='555')
*                                    \------> KEYS NOT EQUAL <------- /
*            assign lr_entity->(<ls_keytab>-name) to <lv_any>.            if sy-subrc = 0.              if <lv_any> = <ls_keytab>-value.                lv_value = <lv_any>.              else.                raise exception type /iwbep/cx_mgw_busi_exception                  exporting                    textid            = /iwbep/cx_mgw_busi_exception=>business_error_unlimited                    message_unlimited = 'Key fields differ during navigation.'.              endif.            else.
* read value from our converted keys              assign component <ls_keytab>-name of structure <ls_key_values> to <lv_any>.              lv_value = <lv_any>.            endif.            lv_db_where = lv_db_where && lv_db_and && `( ` && <ls_keytab>-name && ` = '` && lv_value && `' )`.            lv_db_and = ` and `.          endloop.
* if we are on the last navigation don't process. Pass where condition back
* to our caller, get_entity() or get_entityset() to complete          if sy-tabix <> lv_max_nav.
* select our entity to continue next navigation loop            get_entity_nav(              exporting                iv_entity_name = <ls_nav_path>-target_entity_type                iv_db_where = lv_db_where              importing                er_entity = lr_entity ).            lv_src_entity = <ls_nav_path>-target_entity_type.          endif.        else.
* if no navigation keys, then try and build our navigation from
* our referential constraints          if lv_src_entity is not initial.            lr_entity_type = mr_model->get_entity( lv_src_entity ).
* get navigation property            lv_tech_name = <ls_nav_path>-nav_prop.            lr_nav_prop = mr_model->/iwbep/if_mgw_odata_fw_model~get_nav_property_by_tech(                                        iv_name = lv_tech_name                                        ir_entity_type = lr_entity_type  ).
* get association, and ref. contraints            lr_assoc_ref = mr_model->get_association_by_id( lr_nav_prop->target_entity_id ).            lr_assoc = mr_model->/iwbep/if_mgw_odata_re_model~get_association( lr_assoc_ref->name ).            lt_ref_constraints = lr_assoc->get_ref_constraints( ).
* loop over ref contraints and marry up targets to sources            loop at lt_ref_constraints assigning <ls_ref_constraints>.              assign lr_entity->(<ls_ref_constraints>-target_property-name) to <lv_any>.              if sy-subrc = 0.                lv_db_where = lv_db_where && lv_db_and && `( ` && <ls_ref_constraints>-target_property-name && ` = '` && <lv_any> && `' )`.                lv_db_and = ` and `.              endif.            endloop.          endif.        endif.      endloop.
* return our where condition, either as is, or appended to our $filter      if cv_db_where is not initial.        cv_db_where = cv_db_where && ` and ` && lv_db_where.      else.        cv_db_where = lv_db_where.      endif.    endif.  endmethod.                    "process_nav_path

 

get_entity(), change

Now time to insert our navigation processing into our get_entity() method. First we create a new variable to determine if we are navigating. Next we add the code to call our new method process_nav_path() passing the required parameters. Then we check to see if we are navigating, if so execute the returned OSQL where condition. Otherwise continue as we previously have. Finally we update our class attribute mv_conv_keys to false, check for source key conversion in our process_nav_path() method.

 

New modified method

method get_entity.  data: lt_keys type /iwbep/t_mgw_tech_pairs,        lv_db_where type string,        lv_db_and type string value '',        lv_db_select type string,        lr_data type ref to data,        lv_navigating type abap_bool.  field-symbols: <ls_key> like line of lt_keys,                 <ls_data> type any,                 <lv_value> type any.
* initialise  init( io_tech_request_context->get_entity_type_name( ) ).  if mv_db_tabname is not initial.
* $select, grab fields to select if any    lv_db_select = process_select( io_tech_request_context->get_select_entity_properties( ) ).    lt_keys = io_tech_request_context->get_keys( ).
* read association and keys    process_nav_path(      exporting it_nav_path = io_tech_request_context->get_navigation_path( )                it_source_keys = io_tech_request_context->get_source_keys( )      importing ev_navigating = lv_navigating      changing  cv_db_where = lv_db_where ).    if lv_navigating = abap_false.
* create data struct to grab converted keys      create data lr_data like cs_entity.      assign lr_data->* to <ls_data>.      io_tech_request_context->get_converted_keys(        importing          es_key_values = <ls_data> ).
* loop over keys to build where condition      loop at lt_keys assigning <ls_key>.        assign component <ls_key>-name of structure <ls_data> to <lv_value>.        if sy-subrc = 0.          lv_db_where = lv_db_where && lv_db_and && `( ` && <ls_key>-name && ` = '` && <lv_value> && `' )`.          lv_db_and = ` and `.        endif.      endloop.    endif.    if lv_db_where is not initial.      select single (lv_db_select)        from (mv_db_tabname)        into corresponding fields of cs_entity        where (lv_db_where).    endif.  endif.  mv_conv_keys = abap_false.
endmethod.

 

 

get_entityset(), change

Now let’s put the logic in our get_entityset() method. Again we will create a new variable to determine if we are navigating. Next we call our new method process_nav_path() passing the required parameters. First we leave the check for our has_count(), if we have a count we will execute with our navigated OSQL where condition. Otherwise we check to see if we are navigating, if so execute the returned OSQL where condition, else continue as we have previously. Finally again we update our class attribute mv_conv_keys to false, check for source key conversion in our process_nav_path() method.

 

New modified method

method get_entityset.  data: lv_db_where type string,        lv_db_select type string,        lv_db_orderby type string,        lv_top type i,        lv_skip type i,        lv_navigating type abap_bool.
* initialise  init(    exporting iv_entity_name = io_tech_request_context->get_entity_type_name( )              iv_max_top = iv_max_top ).  if mv_db_tabname is not initial.
* $top, $skip, process our paging    process_paging(      exporting io_tech_request_context = io_tech_request_context      importing ev_top = lv_top                ev_skip = lv_skip ).
* $filter, grab our converted filter    lv_db_where = io_tech_request_context->get_osql_where_clause_convert( ).
* read association and keys    process_nav_path(      exporting it_nav_path = io_tech_request_context->get_navigation_path( )                it_source_keys = io_tech_request_context->get_source_keys( )      importing ev_navigating = lv_navigating      changing  cv_db_where = lv_db_where ).
* check for $count if present just count the records
* no need to order results, or select fields!    if io_tech_request_context->has_count( ) = abap_true.
* execute our select      select count(*) up to lv_top rows        into cs_response_context-count        from (mv_db_tabname)        where (lv_db_where).    else.
* $select, grab fields to select if any      lv_db_select = process_select( io_tech_request_context->get_select_entity_properties( ) ).
* $orderby grab our order      lv_db_orderby = process_orderby( io_tech_request_context ).      if lv_navigating = abap_true.
* we are navigating, execute select with determined lv_db_where
* from navigation!        select (lv_db_select)            from (mv_db_tabname)            into corresponding fields of table ct_entityset            where (lv_db_where)            order by (lv_db_orderby).      else.
* execute our select        select (lv_db_select) up to lv_top rows          from (mv_db_tabname)          into corresponding fields of table ct_entityset          where (lv_db_where)          order by (lv_db_orderby).        if lv_skip > 0.          delete ct_entityset from 1 to lv_skip.        endif.      endif.
* $inlinecount, check for inline count and update      if io_tech_request_context->has_inlinecount( ) = abap_true.        cs_response_context-inlinecount = lines( ct_entityset ).      endif.    endif.  endif.  mv_conv_keys = abap_false.
endmethod.

 

 

Time to test

Activate all changes and try some of the following examples. Note some key fields may be different depending on demo data created:

 

/sap/opu/odata/sap/ZFLIGHT_SRV/AirlineSet('UA')/ToFlightSchedules?$format=json

/sap/opu/odata/sap/ZFLIGHT_SRV/AirlineSet('UA')/ToFlightSchedules?$filter=AirportFrom eq 'FRA'&$format=json

/sap/opu/odata/sap/ZFLIGHT_SRV/AirlineSet('AZ')/ToFlightSchedules(AirlineId='AZ',ConnectionId='555')?$format=json

/sap/opu/odata/sap/ZFLIGHT_SRV/AirlineSet('AZ')/ToFlightSchedules/$count?$filter=CountryFrom eq 'IT'

/sap/opu/odata/sap/ZFLIGHT_SRV/AirlineSet('AZ')?$expand=ToFlightSchedules,ToFlightSchedules/ToFlights,ToFlightSchedules/ToFlights/ToPlane&$format=json

/sap/opu/odata/sap/ZFLIGHT_SRV/FlightSet(AirlineId='AA',ConnectionId='17',FlightDate=datetime'2016-01-13T00:00:00')/ToPlane?$format=json

 

 

Final Words

Here we are at the end of another blog in the series. Hopefully you have been able to follow along and have all the examples working. Either with this set of data or your own set that you have experimented with.

 

Remember to try and use some views as well as transparent tables, this way we can make use of some inner joins with in the view, to include data from related tables.

 

Stay tuned for another blog…


Viewing all articles
Browse latest Browse all 2823

Trending Articles