Method invocation queue

Popups in Webdynpro for ABAP are annoying

Recently I had the pleasure to show one of these “Are you sure?” popups in the UI. The issue with popups in webdynpro is, due to the web based architecture, the fact that there is no real synchronous program flow as we were used to in SAP GUI.
Instead of stopping the program flow when a popup is shown, the Webdynpro runtime continues in order to fulfill the HTTP web request.
This means, instead of receiving the result of the user’s decision right after the popup call, we usually need to implement callback methods which are called when the user presses either “Ok”, “No” and so on.
In the sample this would be the view’s method ONACTIONAPPLY_CONFIRM.

  1. DATA lo_comp_api TYPE REF TO if_wd_component.
  2. DATA lo_controller_api TYPE REF TO if_wd_controller.
  3. DATA ls_config TYPE wdr_popup_to_confirm.
  4. DATA: lt_text_table TYPE string_table,
  5.           lv_text_table TYPE string,
  6.           lv_title TYPE string.
  7. ls_config-button_cancel-enabled = abap_false.
  8. lo_controller_api = wd_this->wd_get_api( ).
  9. lo_comp_api = wd_comp_controller->wd_get_api( ).
  10. lv_title = 'Are you sure?'.
  11. lv_text_table = 'Really???'.
  12. APPEND lv_text_table TO lt_text_table.
  13. TRY.
  14.           CALL METHOD cl_wd_popup_factory=>popup_to_confirm
  15.           EXPORTING
  16.             component        = lo_comp_api
  17.             text             = lt_text_table
  18.             window_title     = lv_title
  19.             configuration    = ls_config
  20.           RECEIVING
  21.             popup_to_confirm = wd_this->mo_popup.
  22.         wd_this->mo_popup->subscribe_to_events(
  23.           controller = lo_controller_api
  24.           handler_name = 'ONACTIONAPPLY_CONFIRM' ).
  25. *retrieve backend call parameters
  26. DATA lv_p1 TYPE i.
  27. DATA lv_p2 TYPE string.
  28. *...retrieve parameters
  29. *store backend call parameters in the view's attributes
  30. wd_this->mv_p1 = lv_p1.
  31. wd_this->mv_p2 = lv_p2.


Did you recognize the last few lines? Actually, what is done here is the preparation of the backend call which is to be executed depending on the user’s decision. The method parameters are already stored in some attributes and read for later purpose.
In the callback method, the backend invocation might look like the code below.

  1. METHOD ONACTIONAPPLY_CONFIRM.
  2.   CHECK wd_this->mo_popup->answer = if_wd_popup_to_confirm=>co_button_1_pressed.
  3.   wd_this->mo_some_weird_backend->perform_some_weird_operation( iv_p1 = wd_this->mv_p1 iv_p2 = wd_this->mv_p2 )
  4. ENDMETHOD.


Every parameter that will conditionally passed to the backend will have to be stored somewhere.
You do this kind of work one time, maybe two times or three times and you are going to be annoyed at it.
Couldn’t this behaviour be achieved easier and more conventiently?

What is a method invocation queue?

A method invocation queue records method calls for later execution. Instead of storing parameters for later usage, you will store the objects and their method calls including parameters for later usage. As ABAP still lacks function pointers we will need to implement it manually.
Compare the above shown coding, starting at line 29 with this one.

  1. *retrieve backend call parameters and record backend calls
  2. DATA lv_p1 TYPE i.
  3. DATA lv_p2 TYPE string.
  4. *...retrieve parameters
  5. wd_this->mo_method_queue = zcl_method_queue=>new_method_invocation( ).
  6. wd_this->mo_method_queue->with( wd_this->mo_some_weird_backend )->on( 'perform_some_weird_operation' ).
  7. wd_this->mo_method_queue->add_parameter( iv_name = 'iv_p1' iv_value = lv_p1 ).
  8. wd_this->mo_method_queue->add_parameter( iv_name = 'iv_p2' iv_value = lv_p2 ).


Looks more complicated at the beginning. But the big advantage of this concept is, that you will now only have to keep track of the method invocation queue, not method parameters. No further attributes are required.
Actually, using the fluent API, you could write lines 6 to 8 in just one single line.

The popup’s callback method now looks like this.

  1. METHOD ONACTIONAPPLY_CONFIRM.
  2.   CHECK wd_this->mo_popup->answer = if_wd_popup_to_confirm=>co_button_1_pressed.
  3.   wd_this->mo_method_queue->flush( ).
  4. ENDMETHOD.

Interested?

So here is the coding. Please be aware, this is just experimental and not meant for productive usage.
This code lacks the support of any other parameter type than IMPORTING parameters as well as proper exception handling. However you may consider to enhance it and write about what you found out 😉

  1. CLASS zcl_method_queue DEFINITION
  2.    PUBLIC
  3.    FINAL
  4.    CREATE PROTECTED .
  5.    PUBLIC SECTION.
  6.      TYPE-POOLS abap .
  7.      CLASS-METHODS new_method_invocation
  8.        RETURNING
  9.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  10.      METHODS with
  11.        IMPORTING
  12.          !io_caller TYPE REF TO object
  13.        RETURNING
  14.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  15.      METHODS on
  16.        IMPORTING
  17.          !iv_method TYPE seocpdname
  18.        RETURNING
  19.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  20.      METHODS add_parameter
  21.        IMPORTING
  22.          !iv_name TYPE abap_parmname
  23.          value(iv_value) TYPE any
  24.        RETURNING
  25.          value(ro_method_queue) TYPE REF TO zcl_method_queue .
  26.      METHODS finalize .
  27.      METHODS flush
  28.        RAISING
  29.          zcx_method_queue .
  30.    PROTECTED SECTION.
  31.      TYPES:
  32.        BEGIN OF ty_s_method_invocation,
  33.          caller TYPE REF TO  object,
  34.          method TYPE  seocpdname,
  35.          parameters TYPE  abap_parmbind_tab,
  36.        END OF ty_s_method_invocation .
  37.      DATA ms_method_invocation TYPE ty_s_method_invocation .
  38.      DATA:
  39.        mt_method_invocations TYPE TABLE OF ty_s_method_invocation .
  40.      METHODS copy_value
  41.        IMPORTING
  42.          !ir_ref TYPE REF TO data
  43.        RETURNING
  44.          value(rr_ref) TYPE REF TO data .
  45.      METHODS constructor .
  46.    PRIVATE SECTION.
  47.  ENDCLASS.
  48.  CLASS ZCL_METHOD_QUEUE IMPLEMENTATION.
  49.    METHOD add_parameter.
  50.      ro_method_queue = me.
  51.      DATA ls_parameter TYPE abap_parmbind.
  52.      DATA lr_value TYPE REF TO data.
  53.      ls_parameter-name = iv_name.
  54.      TRANSLATE ls_parameter-name TO UPPER CASE.
  55.      ls_parameter-kind = cl_abap_objectdescr=>exporting.
  56.      GET REFERENCE OF iv_value INTO lr_value.
  57.      ls_parameter-value = copy_value( lr_value ).
  58.      INSERT ls_parameter INTO TABLE ms_method_invocation-parameters.
  59.    ENDMETHOD.                    "add_parameter
  60.    METHOD constructor.
  61.    ENDMETHOD.                    "CONSTRUCTOR
  62.    METHOD copy_value.
  63.      FIELD-SYMBOLS <lv_in> TYPE any.
  64.      FIELD-SYMBOLS <lv_out> TYPE any.
  65.      ASSIGN ir_ref->* TO <lv_in>.
  66.      CREATE DATA rr_ref LIKE <lv_in>.
  67.      ASSIGN rr_ref->* TO <lv_out>.
  68.      <lv_out> = <lv_in>.
  69.    ENDMETHOD.
  70.    METHOD finalize.
  71.      CHECK: ms_method_invocation-caller IS NOT INITIAL AND ms_method_invocation-method IS NOT INITIAL.
  72.      APPEND ms_method_invocation TO mt_method_invocations.
  73.      CLEAR: ms_method_invocation.
  74.    ENDMETHOD.
  75.    METHOD flush.
  76.      FIELD-SYMBOLS <ls_call> TYPE ty_s_method_invocation.
  77.      DATA lo_cx_root TYPE REF TO cx_root.
  78.      DATA lo_objectdescr TYPE REF TO cl_abap_classdescr.
  79.      DATA lo_cx_sy_dyn_call_illegal_meth TYPE REF TO cx_sy_dyn_call_illegal_method.
  80.      DATA ls_method TYPE abap_methdescr.
  81.      DATA lv_has_method_been_called TYPE abap_bool.
  82.      DATA lv_count TYPE i.
  83.      DATA lt_method_parts TYPE string_table.
  84.      DATA lv_method_name TYPE string.
  85.      finalize( ).
  86.      LOOP AT mt_method_invocations ASSIGNING <ls_call>.
  87.        TRY.
  88.            CALL METHOD <ls_call>-caller->(<ls_call>-method) PARAMETER-TABLE <ls_call>-parameters.
  89.          CATCH cx_sy_dyn_call_illegal_method INTO lo_cx_sy_dyn_call_illegal_meth.
  90.  *         no such method - try to call named method in any interface that the current object might implement
  91.            lv_has_method_been_called = abap_false.
  92.            lo_objectdescr ?= cl_abap_classdescr=>describe_by_object_ref( <ls_call>-caller ).
  93.            LOOP AT lo_objectdescr->methods INTO ls_method.
  94.              TRY.
  95.                  lv_method_name = ls_method-name.
  96.                  SPLIT lv_method_name AT '~' INTO TABLE lt_method_parts.
  97.                  FIND <ls_call>-method IN TABLE lt_method_parts MATCH COUNT lv_count.
  98.                  IF lv_count > 0.
  99.                    CALL METHOD <ls_call>-caller->(ls_method-name) PARAMETER-TABLE <ls_call>-parameters.
  100.                    lv_has_method_been_called = abap_true.
  101.                    EXIT.
  102.                  ENDIF.
  103.                CATCH cx_root INTO lo_cx_root.
  104.                  CONTINUE.
  105.              ENDTRY.
  106.            ENDLOOP.
  107.  *         has method been found?
  108.            IF lv_has_method_been_called = abap_false.
  109.              RAISE EXCEPTION TYPE zcx_method_queue
  110.                EXPORTING
  111.                  previous = lo_cx_root
  112.                  method   = <ls_call>-method.
  113.            ENDIF.
  114.          CATCH cx_root INTO lo_cx_root.
  115.            RAISE EXCEPTION TYPE zcx_method_queue
  116.              EXPORTING
  117.                previous = lo_cx_root
  118.                method   = <ls_call>-method.
  119.        ENDTRY.
  120.      ENDLOOP.
  121.    ENDMETHOD.                    "flush
  122.    METHOD new_method_invocation.
  123.      CREATE OBJECT ro_method_queue.
  124.    ENDMETHOD.                    "new_method_invocation
  125.    METHOD on.
  126.      ro_method_queue = me.
  127.      ms_method_invocation-method = iv_method.
  128.      TRANSLATE ms_method_invocation-method TO UPPER CASE.
  129.    ENDMETHOD.                    "on
  130.    METHOD with.
  131.      ro_method_queue = me.
  132.      IF ms_method_invocation-caller IS NOT INITIAL AND ms_method_invocation-method IS NOT INITIAL.
  133.        finalize( ).
  134.      ENDIF.
  135.      ms_method_invocation-caller = io_caller.
  136.    ENDMETHOD.                    "with
  137.  ENDCLASS.

Drawbacks of this approach

However, there’s no such thing as a free lunch.
If you decide to use such a method invocation queue, you lose the where-used-list support of your backend’s methods.
Also, you can only access real backend methods and not component controller methods, as they are accesssible for their views or windows, but not for any other objects, unless these methods are declared public.

So stay careful, be watchful and enjoy…