Skip to Content

The use case

At the last CodeJam in Gütersloh I got a question from a participant that turned out to be more tricky than I thought.

Suppose you have a SAPUI5 application that allows a Google like search for two properties of your OData service but the underlying API being called in the SAP backend only supports select options as input parameters.

Since the application only offers one search field the resulting OData request would look like follows:

.../ProductSet?$filter=substringof('ABC',ProductId) or substringof('ABC',SupplierName)

When you try to retrieve select options via the following statement

lt_filter_select_options = io_tech_request_context->get_filter( )->get_filter_select_options( ).

you will notice that nothing is returned in table lt_filter_select_options.

This is because select options can only be combined logically with an ‘and’.

The solution(s)

Option 1: The OData request cannot be changed

For such a special use case we are going to use the filter tree which is unfortunately not easy to handle in the OData V2 framework of SAP Gateway.

The filter tree can be obtained using the following statement.

lo_filter_tree = io_tech_request_context->get_filter_expression_tree( ).

In our simple use case the filter tree will be a simple binary tree of type (/iwbep/if_mgw_expr_binary) that contains two nodes of type (/iwbep/if_mgw_expr_function).

By casting the objects to the appropriate types we are able to use the type specific methods.

The following code allows us to check that the function ‘substringof’ has been used.

lv_function = lo_function->function.
IF lv_function <> 'substringof'.
   lv_filter_error = 'Only substringof is supported. '.
   lv_wrong_filter = abap_true.
ENDIF.

The property being used can be retrieved using the following statement:

 lt_param_tab = lo_function->parameters.

It returns a table that contains two objects. The first object of type (/iwbep/if_mgw_expr_property) allows the retrieval of the property name while the filter string can be obtained via an object of type (/iwbep/if_mgw_expr_literal).

IF lt_param_tab[ 1 ]->kind = /iwbep/if_mgw_expr_node~kind_literal.
   lo_literal ?= lt_param_tab[ 1 ].
   lv_literal = lo_literal->literal_converted.
ELSE.
   lv_wrong_filter = abap_true.
ENDIF.

IF lt_param_tab[ 2 ]->kind = /iwbep/if_mgw_expr_node~kind_property.
   lo_property ?= lt_param_tab[ 2 ].
   lv_property = lo_property->property_name.
ELSE.
   lv_wrong_filter = abap_true.
ENDIF

After having retrieved the data necessary to create the select options for the ProductId and the SupplierName we call the BAPI BAPI_EPM_PRODUCT_GET_LIST twice.

The first time using the select options of the ProductId and a second time with the select options for the SupplierName.

Both result sets are finally merged.

When using the following query parameters

.../ProductSet?$filter=substringof('2001',ProductId) or substringof('AVANT',SupplierName)

we get the following response.

The product ‘HT-2001‘ from the supplier ‘Panorama Studios‘ and three products from the supplier ‘AVANTEL‘.

 

 

Option 2 (the OData request can be changed)

If you are able to change the OData request it would be more easy to use the query option search.

?search=<search string>

This way you would be able to retrieve the search string in the coding of your data provider extenstion class.

    data: lv_search_string type string.
    lv_search_string = io_tech_request_context->get_search_string( ).    

and you could create select options like this

ls_supplier_name-sign  = 'I'.
ls_supplier_name-option  ='CP'.
ls_supplier_name-low  = '*' && lv_search_string && '*'.
APPEND ls_supplier_name TO lt_supplier_name.

ls_product_id-sign  = 'I'.
ls_product_id-option  ='CP'.
ls_product_id-low  = '*' && lv_search_string && '*'.
APPEND ls_product_id TO lt_product_id.

 

The source Code for evaluating the filter tree

As a simple example I created an OData service with one entity set ProductSet using DDIC import of the structure BAPI_EPM_PRODUCT_HEADER. Data is retrieved using the function module BAPI_EPM_PRODUCT_GET_LIST that offers three select options for product categories, supplier names and product ids.

The code first checks whether select options can be retrieved. Here code would have to be implemented for the normal data retrieval.

When our search URL that searches via select options for product ids or for supplier names is reaching our service implementation the code switches to the handling of the filter tree.

The source code throws an error if it finds out that the filter statement does not match the expected use case.

  METHOD productset_get_entityset.

    DATA : lo_filter_tree             TYPE REF TO /iwbep/if_mgw_expr_node,
           lo_left_node               TYPE REF TO /iwbep/if_mgw_expr_node,
           lo_right_node              TYPE REF TO /iwbep/if_mgw_expr_node,
           lo_binary                  TYPE REF TO /iwbep/if_mgw_expr_binary,
           lo_function                TYPE REF TO /iwbep/if_mgw_expr_function,
           lo_property                TYPE REF TO /iwbep/if_mgw_expr_property,
           lo_literal                 TYPE REF TO /iwbep/if_mgw_expr_literal,
           lt_param_tab               TYPE /iwbep/if_mgw_expr_function=>parameter_t,
           ls_param_tab               TYPE LINE OF /iwbep/if_mgw_expr_function=>parameter_t,
           lv_operator                TYPE string,
           lv_function                TYPE string,
           lv_literal                 TYPE string,
           lv_property                TYPE string,
           lv_supported_filter_string TYPE string,
           lv_filter_error            TYPE string,
           lv_wrong_filter            TYPE abap_bool,
           lt_filter_select_options   TYPE /iwbep/t_mgw_select_option.

    CONSTANTS : lc_kind_unary    TYPE c LENGTH 1 VALUE 'U',
                lc_kind_binary   TYPE c LENGTH 1 VALUE 'B',
                lc_kind_literal  TYPE c LENGTH 1 VALUE 'C',
                lc_kind_function TYPE c LENGTH 1 VALUE 'F',
                lc_kind_member   TYPE c LENGTH 1 VALUE 'M',
                lc_kind_property TYPE c LENGTH 1 VALUE 'P'.

    DATA: lt_headerdata        TYPE STANDARD TABLE OF bapi_epm_product_header,
          ls_headerdata        TYPE                   bapi_epm_product_header,
          ls_entity            LIKE LINE OF           et_entityset,
          lt_product_id        TYPE TABLE OF          bapi_epm_product_id_range,
          ls_product_id        TYPE                   bapi_epm_product_id_range,
          lt_supplier_name     TYPE TABLE OF          bapi_epm_supplier_name_range,
          ls_supplier_name     TYPE                   bapi_epm_supplier_name_range,
          lt_category          TYPE TABLE OF          bapi_epm_product_categ_range,
          ls_category          TYPE                   bapi_epm_product_categ_range,
          lt_return            TYPE TABLE OF          bapiret2,
          lo_message_container TYPE REF TO            /iwbep/if_message_container.



    lv_wrong_filter = abap_false.

    lv_supported_filter_string = 'Only the following filterstring is supported: substringof(<some string>,SupplierName) or substringof(<some string>,ProductID)'.


    lt_filter_select_options = io_tech_request_context->get_filter( )->get_filter_select_options( ).

    IF lt_filter_select_options IS NOT INITIAL.

      "implement coding to retrieve data via select options

    ELSE.

      lo_filter_tree = io_tech_request_context->get_filter_expression_tree( ).

      IF lo_filter_tree IS BOUND.
        IF lo_filter_tree->kind = lc_kind_binary. " 'B'

          lo_filter_tree->prepare_converted_values( ).
          lo_binary ?= lo_filter_tree.
          lv_operator = lo_binary->operator.
          lo_left_node  = lo_binary->left_operand.
          lo_right_node = lo_binary->right_operand.

          IF lo_left_node IS BOUND.
            IF lo_left_node->kind = lc_kind_function. "  'F' .

              lo_function ?= lo_left_node.
              lv_function = lo_function->function.
              IF lv_function <> 'substringof'.
                lv_filter_error = 'Only substringof is supported. '.
                lv_wrong_filter = abap_true.
              ENDIF.

              lt_param_tab = lo_function->parameters.

              IF lt_param_tab IS NOT INITIAL.

                IF lt_param_tab[ 1 ]->kind = lc_kind_literal.
                  lo_literal ?= lt_param_tab[ 1 ].
                  lv_literal = lo_literal->literal_converted.
                ELSE.
                  lv_wrong_filter = abap_true.
                ENDIF.

                IF lt_param_tab[ 2 ]->kind = lc_kind_property.
                  lo_property ?= lt_param_tab[ 2 ].
                  lv_property = lo_property->property_name.
                ELSE.
                  lv_wrong_filter = abap_true.
                ENDIF.

                IF lv_property = 'SUPPLIER_NAME'.

                  ls_supplier_name-sign  = 'I'.
                  ls_supplier_name-option  ='CP'.
                  ls_supplier_name-low  = '*' && lv_literal && '*'.
                  APPEND ls_supplier_name TO lt_supplier_name.

                ELSEIF lv_property = 'PRODUCT_ID'.

                  ls_product_id-sign  = 'I'.
                  ls_product_id-option  ='CP'.
                  ls_product_id-low  = '*' && lv_literal && '*'.
                  APPEND ls_product_id TO lt_product_id.

                ELSE.
                  " raise error message that filter string does not match the expected format
                  " an additional property was found in the filter string
                  lv_filter_error = 'Property:' && lv_property && ' is not supported. '.
                  lv_wrong_filter = abap_true.
                ENDIF.
              ELSE.
                lv_wrong_filter = abap_true.
              ENDIF.
            ELSE.
              lv_wrong_filter = abap_true.
            ENDIF.
          ENDIF.

          CLEAR lo_function.
          CLEAR lo_property.
          CLEAR lo_literal.
          CLEAR lt_param_tab.
          CLEAR ls_param_tab.
          CLEAR lv_operator.
          CLEAR lv_function.
          CLEAR lv_literal.
          CLEAR lv_property.

          IF lo_right_node IS BOUND.
            IF lo_right_node->kind = lc_kind_function. "  'F' .

              lo_function ?= lo_right_node.
              lv_function = lo_function->function.
              IF lv_function <> 'substringof'.
                lv_filter_error = 'Only substringof is supported. '.
                lv_wrong_filter = abap_true.
              ENDIF.

              lt_param_tab = lo_function->parameters.

              IF lt_param_tab IS NOT INITIAL.

                IF lt_param_tab[ 1 ]->kind = lc_kind_literal.
                  lo_literal ?= lt_param_tab[ 1 ].
                  lv_literal = lo_literal->literal_converted.
                ELSE.
                  lv_wrong_filter = abap_true.
                ENDIF.

                IF lt_param_tab[ 2 ]->kind = lc_kind_property.
                  lo_property ?= lt_param_tab[ 2 ].
                  lv_property = lo_property->property_name.
                ELSE.
                  lv_wrong_filter = abap_true.
                ENDIF.

                IF lv_property = 'SUPPLIER_NAME'.

                  ls_supplier_name-sign  = 'I'.
                  ls_supplier_name-option  ='CP'.
                  ls_supplier_name-low  = '*' && lv_literal && '*'.
                  APPEND ls_supplier_name TO lt_supplier_name.

                ELSEIF lv_property = 'PRODUCT_ID'.

                  ls_product_id-sign  = 'I'.
                  ls_product_id-option  ='CP'.
                  ls_product_id-low  = '*' && lv_literal && '*'.
                  APPEND ls_product_id TO lt_product_id.

                ELSE.
                  " raise error message that filter string does not match the expected format
                  " an additional property was found in the filter string
                  lv_filter_error = 'Property:' && lv_property && ' is not supported. '.
                  lv_wrong_filter = abap_true.
                ENDIF.
              ELSE.
                lv_wrong_filter = abap_true.
              ENDIF.
            ELSE.
              lv_wrong_filter = abap_true.
            ENDIF.
          ENDIF.

          "    IF lo_right_node->kind = /iwbep/if_mgw_expr_node~kind_function. "  'F' .
          "lr_function ?= lo_right_node.


        ELSE.
          lv_filter_error = ' Filter is not binary. '.
          lv_wrong_filter = abap_true.
        ENDIF.



      ENDIF.

      IF lv_wrong_filter = abap_true.

        RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
          EXPORTING
            textid            = /iwbep/cx_mgw_busi_exception=>business_error_unlimited
            message_unlimited = lv_filter_error && lv_supported_filter_string.

      ENDIF.

      "first call of BAPI for select options for 'ProductId'.


      CALL FUNCTION 'BAPI_EPM_PRODUCT_GET_LIST'
*    EXPORTING
*      MAX_ROWS              = lv_maxrows
        TABLES
          headerdata        = et_entityset
          selparamproductid = lt_product_id
*         selparamsuppliernames = lt_supplier_name
*         SELPARAMCATEGORIES    = lt_category
          return            = lt_return.


      IF lt_return IS NOT INITIAL.

*---Message Container
        lo_message_container = mo_context->get_message_container( ).

        lo_message_container->add_messages_from_bapi(
          it_bapi_messages         = lt_return
          iv_determine_leading_msg = /iwbep/if_message_container=>gcs_leading_msg_search_option-first
        ).

        RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
          EXPORTING
            textid            = /iwbep/cx_mgw_busi_exception=>business_error
            message_container = lo_message_container.

      ENDIF.

      "second call of BAPI with select options for 'SupplierName'

      CALL FUNCTION 'BAPI_EPM_PRODUCT_GET_LIST'
*    EXPORTING
*      MAX_ROWS              = lv_maxrows
        TABLES
          headerdata            = lt_headerdata
*         selparamproductid     = lt_product_id
          selparamsuppliernames = lt_supplier_name
*         SELPARAMCATEGORIES    = lt_category
          return                = lt_return.

      IF lt_return IS NOT INITIAL.

*---Message Container
        lo_message_container = mo_context->get_message_container( ).

        lo_message_container->add_messages_from_bapi(
          it_bapi_messages         = lt_return
          iv_determine_leading_msg = /iwbep/if_message_container=>gcs_leading_msg_search_option-first
        ).

        RAISE EXCEPTION TYPE /iwbep/cx_mgw_busi_exception
          EXPORTING
            textid            = /iwbep/cx_mgw_busi_exception=>business_error
            message_container = lo_message_container.

      ENDIF.

      LOOP AT lt_headerdata INTO ls_headerdata.

        "check if product is already part of the result set
        READ TABLE et_entityset WITH KEY product_id = ls_headerdata-product_id TRANSPORTING NO FIELDS.

        IF sy-subrc <> 0.
          APPEND ls_headerdata TO et_entityset.
        ENDIF.

      ENDLOOP.
    ENDIF.

  ENDMETHOD.

 

 

 

 

 

 

To report this post you need to login first.

Be the first to leave a comment

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

Leave a Reply