1 6 7 8 9 10 76 Previous Next

ABAP Development

1,128 Posts

Hi Community,

 

This post is dedicated to open source lovers.

 

In a couple of open source projects I've seen the following dilema:

 

  • Convenience for users: the code must be easy installable so there is a tendency to keep the whole code (program) as one piece.
  • contra Convenience for the developer: when the code grows it becomes to be difficult to support it as one piece.

 

Of course there are tools like SAPLINK and ZABAPGIT available, which solves this issue (not only this one of course ). However, they must be installed on a target system which puts additional restriction.

 

Recently, I've published at Github a tool that addresses this issue from, let's say, publisher perspective. The tool is called ZINCLUDE_ASSEMBLER and what it does is simply "includes" program's includes which belong to the same dev package into the one piece of code.

 

illustration_small.png

 

So it can be used to publish easy-to-install single-file code while still enjoying nice code structure in dev environment. The result can be saved to a file or to another program (kind of ZXXX_BUILD).

 

The project homepage at Github: https://github.com/sbcgua/abap_include_assembler

 

I hope it will come to use for someone

 

All the best, Alexander Tsybulsky


Let's try to resolve one real issue now. What we want to achieve is: in CRM we need a CDS view which returns the service order guid together with its Sold-to Party information, "Title" ( Mr. ) and "Name" ( blGMOUTH ).

clipboard1.png

The title and Name information are stored on table BUT000, while Service order transactional information is maintained in table CRMD_PARTNER, which has a field PARTNER_NO ( CHAR32 ) linking to table BUT000's PARTNER_GUID ( RAW16 ).

clipboard2.png

clipboard3.png

It is not allowed to do join on these two fields since their data type are not equal. This question is asked via this SCN thread: ABAP CDS View: join tables on columns of different type .


As suggested in the Correction Answer, this issue could be resolved by using CDS Table Function. Here below are the detail steps.

clipboard5.png

1. Create a new table function

clipboard6.png

@ClientDependent: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
define table function ztf_BP_DETAIL
  with parameters @Environment.systemField: #CLIENT
                  clnt:abap.clnt
  returns { client:s_mandt;
            partner_guid:BU_PARTNER_GUID;
            partset_guid:CRMT_OBJECT_GUID;
            partner_no: CRMT_PARTNER_NO;
            bp_guid: BU_PARTNER_GUID;
            title:AD_TITLE;
            name: BU_NAME1TX;
          }
  implemented by method
    zcl_amdp_bp_detail=>crmd_partner_but000;




With keyword "with parameters", the client parameters is defined which works as the importing parameters for the ABAP class method zcl_amdp_bp_detail=>crmd_partner_but000. The keywords "returns" defines available fields which could be consumed by other CDS entities.

 

For further information about AMDP ( ABAP Managed Database Procedure ), please refer to this document Implement and consume your first ABAP Managed Database Procedure on HANA or this blog An example of AMDP( ABAP Managed Database Procedure ) in 740 .

 

2. Create a new AMDP implementation

Create a new ABAP class zcl_amdp_bp_detail by copying the following source code:

 

CLASS zcl_amdp_bp_detail DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .
  PUBLIC SECTION.
  INTERFACES if_amdp_marker_hdb.
  CLASS-METHODS crmd_partner_but000 FOR TABLE FUNCTION ztf_bp_Detail.
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.
CLASS zcl_amdp_bp_detail IMPLEMENTATION.
METHOD crmd_partner_but000
        BY DATABASE FUNCTION FOR HDB
        LANGUAGE SQLSCRIPT
        OPTIONS READ-ONLY
        USING crmd_partner but000.
    RETURN SELECT sc.client as client,
                  sc.partner_guid as partner_guid,
                  sc.guid as partset_guid,
                  sc.partner_no as partner_no,
                  sp.partner_guid as bp_guid,
                  sp.title as title,
                  sp.name1_text as name
                  FROM crmd_partner AS sc
                    INNER JOIN but000 AS sp ON sc.client = sp.client AND
                                              sc.partner_no = sp.partner_guid
                    WHERE sc.client = :clnt AND
                          sc.partner_fct = '00000001'
                    ORDER BY sc.client;
  ENDMETHOD.
ENDCLASS.




Here in line 30 the two columns of CRMD_PARTNER and BUT000 are joined. The importing parameter is used in SQLScript source code by adding a ":" before the variable name. The hard code "00000001" means the constant value for partner function "Sold-to Party".

clipboard8.png

3. Consume the created table function in CDS view

 

@AbapCatalog.sqlViewName: 'zcpartner'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'partner detail'
define view Z_c_partner as select from crmd_orderadm_h
inner join crmd_link as _link on crmd_orderadm_h.guid = _link.guid_hi and _link.objtype_hi = '05'
  and _link.objtype_set = '07'
inner join ztf_bp_detail( clnt: '001') as _bp on _link.guid_set = _bp.partset_guid
{
  key crmd_orderadm_h.guid,
  --_link.objtype_hi as header_type,
  --_link.objtype_set as item_type,
  _bp.bp_guid,
  _bp.partner_no,
  _bp.name,
  case _bp.title
    when '0001' then 'Ms.'
    when '0002' then 'Mr.'
    when '0003' then 'Company'
    when '0004' then 'Mr and Mrs'
    else 'Unknown'
  end as title
}




Please note that the created table function in step1 could be directly consumed just as a normal CDS view, see example in line 8.

Since the table function declares client as parameter, so when consumed, I put the current client id 001 into it. The fields defined as the returning parameters in table function could be used in consuming view.

clipboard9.png

4. Test the whole solution


Click F8 on the view z_c_partner, check whether data previews as expected.

clipboard10.png

or you can also check against single data record, by clicking "SQL Console", maintain the SQL and click Run:

clipboard11.png

The same test could also easily be done on ABAP side:

clipboard12.png

clipboard13.png

Hi community,

 

Another blazing fast and short blog post of mine. It seems I have come across a bug in standard bapi BAPI_OUTB_DELIVERY_CREATE_SLS.

 

I was trying to use it to deliver completely a sales order, but I ran into an interesting issue.

 

This BAPI calls function module RV_DELIVERY_CREATE, but, alas, it calls it without a "selektionsdatum", which seems to be what the "due_date" in the BAPI is for.

 

I'm not sure about this, but the fact is, if I populate this field (selektionsdatum) with the value from due_date, it works (for me... in the system I work with...).

 

So what I did was implement an implicit enhancement at the beginning of the BAPI, and I store the value of due_date in the attribute of a global class.

 

Then I created another implicit enhancement implementation at the beginning of RV_DELIVERY_CREATE and get the due_date into selektionsdatum.

 

There ya go.

 

If this is helpful, great. If it's not, I'm sorry.

 

If I'm terribly mistaken about this, let me know.

 

EDIT: Andrea Olivieri mentioned below, in the comments, that if you populate the due_date and the ship_point, it might worked. Or, at least, it worked for him. Check below for his example.

 

Cheers,

Bruno

Introduction

 

I was surprised not to find any examples on how to modify the start and/or end dates of PA infotypes using the decoupled framework. Typically the start and end dates are part of the key identifying the record. Unless you know what you are doing, you will most likely get a CX_HRPA_INVALID_PARAMETER exception with the parameter PRIMARY_RECORD-PSKEY. Important here is to have the existing record, create a new one (with new values) and refer the old record when calling the MODIFY method.

 

Acknowledgements

 

All the code was copied and adapted from the standard function module HR_CONTROL_INFTY_OPERATION, the decoupled framework parts of it.

 

The Code

 

First the definition for method CHANGE_BEGDA_ENDDA

 

  class-methods CHANGE_BEGDA_ENDDA

    importing

      value(IV_TCLAS) type PSPAR-TCLAS default 'A'

      value(IV_INFTY) type PRELP-INFTY

      value(IV_SUBTY) type P0001-SUBTY

      value(IV_OBJPS) type P0001-OBJPS

      value(IV_SPRPS) type P0001-SPRPS

      value(IV_SEQNR) type P0001-SEQNR default '000'

      value(IV_PERNR) type P0001-PERNR

      value(IV_BEGDA) type P0001-BEGDA

      value(IV_ENDDA) type P0001-ENDDA

      value(IV_BEGDA_NEW) type P0001-BEGDA

      value(IV_ENDDA_NEW) type P0001-ENDDA

      value(IV_TEST_MODE) type BOOLE_D default ' '

    exporting

      value(ET_MESSAGES) type HRPAD_MESSAGE_TAB

      value(EV_OK) type BOOLE_D .

 

 

Then the implementation of method CHANGE_BEGDA_ENDDA

 

method change_begda_endda.

  data lr_message_list          type ref to cl_hrpa_message_list.

  data lr_masterdata_bl         type ref to if_hrpa_masterdata_bl.

  data container                type ref to if_hrpa_infty_container.

  data old_container            type ref to cl_hrpa_infotype_container.

  data new_container            type ref to if_hrpa_infty_container.

  data new_infotype_container   type ref to cl_hrpa_infotype_container.

  data infotype_ref             type ref to data.

  data lv_is_ok                 type boole_d.

  data lv_dummy                 type string.

  data ls_msg                   type symsg.

  data container_tab            type hrpad_infty_container_tab.

  data container_if             type hrpad_infty_container_ref.

  data t777d                    type t777d.

  data lv_has_error             type boole_d.

  data lv_count                 type i.

 

  field-symbols <pshdr>         type pshdr.

  field-symbols <pnnnn>         type any.

  field-symbols <pskey>         type pskey.

  field-symbols <record>        type any.

  field-symbols <pxxxx>         type any.

 

  create object lr_message_list.

 

  call method cl_hrpa_masterdata_factory=>get_business_logic

    importing

      business_logic = lr_masterdata_bl.

 

  check lr_masterdata_bl is bound.

 

  call method lr_masterdata_bl->read

    exporting

      tclas          = iv_tclas

      pernr          = iv_pernr

      infty          = iv_infty

      subty          = iv_subty

      objps          = iv_objps

      sprps          = iv_sprps

      begda          = iv_begda

      endda          = iv_endda

      seqnr          = iv_seqnr

      mode           = if_hrpa_masterdata_bl=>exact_matching_record

      no_auth_check  = abap_true

      message_handler = lr_message_list

    importing

      container_tab  = container_tab

      is_ok          = lv_is_ok.

 

  if lv_is_ok eq abap_true.

    describe table container_tab lines lv_count.

 

    if lv_count gt 1.

      lv_is_ok = abap_false.

 

      message e016(pg) with 'Multiple records found, limit to exactly one' into lv_dummy.

      move-corresponding sy to ls_msg.

 

      call method lr_message_list->if_hrpa_message_handler~add_message

        exporting

          message = ls_msg

          cause  = lr_message_list->if_hrpa_message_handler~infotype_generic.

    elseif lv_count eq 0.

      lv_is_ok = abap_false.

 

      message e009(pg) with iv_infty into lv_dummy.

      move-corresponding sy to ls_msg.

 

      call method lr_message_list->if_hrpa_message_handler~add_message

        exporting

          message = ls_msg

          cause  = lr_message_list->if_hrpa_message_handler~infotype_generic.

    else.

      read table container_tab into container index 1.

    endif.

  endif.

 

  if lv_is_ok = abap_false.

    call method lr_message_list->get_abend_list

      importing

        messages = et_messages.

 

    if et_messages is initial.

      call method lr_message_list->get_error_list

        importing

          messages = et_messages.

    endif.

 

    return.

  endif.

 

  old_container ?= container.

 

  try.

      call method old_container->if_hrpa_infty_container_data~primary_record_ref

        importing

          pnnnn_ref = infotype_ref.

    catch cx_hrpa_violated_assertion .

  endtry.

 

  assign infotype_ref->* to <pxxxx>.

 

  t777d = cl_hr_t777d=>read( infty = iv_infty ).

 

  create data infotype_ref type (t777d-ppnnn).

 

  assign infotype_ref->* to <pnnnn> casting like <pxxxx>.

  <pnnnn> = <pxxxx>.

  assign <pnnnn> to <pshdr> casting.

  <pshdr>-infty = iv_infty.

 

  assign infotype_ref->* to <record> casting like <pxxxx>.

  assign component 'PSKEY' of structure <record> to <pskey>.

 

  call method lr_masterdata_bl->get_infty_container

    exporting

      tclas          = 'A'

      pskey          = <pskey>

      no_auth_check  = abap_true

      message_handler = lr_message_list

    importing

      container      = container_if.

 

  new_infotype_container ?= container_if.

 

  <pskey>-endda = iv_endda_new.

  <pskey>-begda = iv_begda_new.

 

  new_infotype_container ?= new_infotype_container->modify_key( <pskey> ).

  new_infotype_container ?= new_infotype_container->modify_primary_record( <record> ).

 

  new_container ?= new_infotype_container.

 

  if iv_test_mode eq abap_false.

    call method lr_masterdata_bl->modify

      exporting

        old_container  = old_container

        message_handler = lr_message_list

      importing

        is_ok          = lv_is_ok

      changing

        container      = new_container.

 

    if lr_message_list->has_error( ) = abap_true or

       lr_message_list->has_abend( ) = abap_true.

      lv_has_error = abap_true.

    else.

      lv_has_error = abap_false.

    endif.

 

    if lv_has_error = abap_true.

      call method lr_message_list->get_abend_list

        importing

          messages = et_messages.

 

      if et_messages[] is initial.

        call method lr_message_list->get_error_list

          importing

            messages = et_messages.

      endif.

 

      call method lr_masterdata_bl->if_hrpa_buffer_control~initialize.

    else.

      call method lr_masterdata_bl->flush

        exporting

          no_commit = ' '.

    endif.

  endif.

 

endmethod.

 

To use it, you would simply call the method with all required parameters

 

  data lt_messages type hrpad_message_tab.

  data lv_ok type boole_d.

 

  call method change_begda_endda

    exporting

      iv_tclas    = 'A'

      iv_infty    = '0002'

      iv_subty    = ''

      iv_objps    = ''

      iv_sprps    = ''

      iv_seqnr    = '000'

      iv_pernr    = '12345678'

      iv_begda    = '20001201'

      iv_endda    = '99991231'

      iv_begda_new = '20101201'

      iv_endda_new = '99991231'

      iv_test_mode = ' '

    importing

      et_messages = lt_messages

      ev_ok       = lv_ok.

 

 

This would change the start date of infotype 0002 for employee 12345678 from 12/01/2000 to 12/01/2010.

 

Limitations

 

I'm sure there are many situations where the code will break. For example, I didn't bother with secondary records and the code handles only one record at a time. In addition the code should check that the decoupled framework can actually be used for the infotype in question. Nevertheless, I thought I'd share this as it may help folks out there.

Some time ago I published a post about a mockup loading tool we use for unit testing in our projects http://scn.sap.com/community/abap/blog/2015/11/12/unit-testing-mockup-loader-for-abap. Since then me and my team were using it intensively in our developments and also introduced a couple of new features. This post describes our apprach to unit test preparation.

 

Just briefly for those who didn't read the previous post: the idea of the mockup loader is that you prepare your test data in excel, copy it to a bunch of text files (manually or with excel2txt script that is also availble in repo), zip them, upload as binary object via SMW0, and use mockup loader to extract and deploy this data dynamically for your unit test. For more details see the previous post (link above).

 

Use case

Let's suppose we have a method which is designed to identify quality of some product. We input a table of quality inspection parameters and expect some quality class assessment, in the example "A" or "B". Certain parameters are compulsory and must not be skipped during quality inspection: if they are missing the method raises an exception.

 

The goal: write a unit test code once and then easily update it with new test cases prepared in Excel.

 

Data preparation

Let's say we prepared a unit test table with 3 cases (docno = 10, 20 and 30). Note that QUANTITY parameter is missing in the document 30 - exception expected.

 

DOCNOPARAMVALUE
10WEIGHT12,89
10QUANTITY99
10MOISTURE12,6
20WEIGHT14,89
20QUANTITY103
20MOISTURE11,9
30WEIGHT14,89
30QUANTITY
30MOISTURE12,10

 

Another table is the index of test cases. Each refers to a DOCNO, describes expected result and describes the case in a human-readable form (as a kind of documentation and for failure messages). TYPE field describes if the case is positive or negative.

 

TESTIDTYPEDOCNOEXPECTMSG
1+10ACase with quality A
2+20BCase with quality B
3-30Case with a missing Param

 

 

Unit test code

 

1) Define the test case index structure (in the test class)

 

class lcl_test definition for testing inheriting from cl_aunit_assert

  duration short risk level harmless.

  public section.

    types:

        begin of ty_test_case,

          testid    type i,

          type      type char2,

          docno    type char10,

          expect    type char1,

          msg      type string,

        end of ty_test_case.

 

 

    data at_cases type table of ty_test_case.        " << index table

    data o_ml    type ref to zcl_mockup_loader.    " << mockup loader instance

    data o        type ref to zcl_class_under_test.  " << class under test

    ...

 

2) In setup() method acquire a mockup loader instance and load index table

 

  method setup.

    data lo_ex type ref to zcx_mockup_loader_error.

 

    create object o importing ...    " << Object under test initialization

 

    try.

      o_ml = zcl_mockup_loader=>get_instance( ).

 

      o_ml->load_data(

        exporting i_obj      = 'TEST1/index'    " << file path inside zipfile

        importing e_container = me->at_cases ).

 

    catch cx_static_check into lo_ex.

      fail( lo_ex->get_text( ) ).

    endtry.

  endmethod.

 

3) And here is main test cycle. BTW one of new features of ML introduced recently is data filtering - see the call of o_ml->load_data() - it passes the field name and value. If specified, the mockup loader filters the output to meed this condition. It is possible to define more complex filtering also (more details @github homepage).

 

  method test_assess_quality.

    data l_case  type ty_test_case.

    data lo_ex  type cx_root.

    data lt_insp type zcl_class_under_test=>ty_inspections.

    data l_act  type zcl_class_under_test=>ty_quality.   

 

    loop at at_cases into l_case.                      " << Loop through cases

      clear lo_ex.

 

      try.

        o_ml->load_data(

          exporting i_obj      = 'TEST1/inspections'     

                    i_where    = 'DOCNO = ' && l_case-testid

          importing e_container = lt_insp ). " ^^^ Filter only relevant lines

 

        l_act = o->assess_quality( lt_insp ).                " << Test call

 

      catch zcx_mockup_loader_error into lo_ex.

        fail( lo_ex->get_text( ) ). " Mockup load error handling, just in case

      catch zcx_root into lo_ex.

        " do nothing - this is the expected exception for negative tests

        " better use specific exeption classes of course

      endtry.

 

      if l_case-type = '+'. " Positive test

        " msg indicates the failed test id and it's description

        assert_initial( act = lo_ex

                        msg = |[{ l_case-testid }] { l_case-msg }| ).

        assert_equals( act = l_act

                       exp = l_case-expect

                       msg = |[{ l_case-testid }] { l_case-msg }| ).

      else. " '-'          " Negative test

        assert_not_initial( act = lo_ex

                            msg = |[{ l_case-testid }] { l_case-msg }| ).

        " Potentially more specific error check code should follow

      endif.

 

    endloop.

  endmethod.   

 

4) That's it !

 

Now we've got an easy extendable test infrustructure. New test cases for this test can be added without extra coding. Honestly speaking, this works better for integrated tests than for checking small methods. However, it is very useful when applicable - some methods in out developments have unit tests with 20+ cases and they are still updated from time to time with new ones (usually after yet another bug discovery ).

 

Hope you find it useful !

 

The ABAP mockup loader project lives @github, there you can find full reference of the methods and also some use cases in wiki.

 

All the best, Alexander Tsybulsky.

abaplint

Continuous Integration is a hot topic, but it has always been difficult to do anything for ABAP open source projects, as it would require a dedicated SAP system to run the tests on.

 

So I've started writing abaplint which is a linter written in TypeScript for checking ABAP code. abaplint compiles to javascript, which can be executed on various platforms. It will only do the "Code Inspector" part of checking the code, unit tests will not be run and is not in scope for this project.

 

abaplint will work for objects that have been serialized using abapGit, or files containing the raw abap code.

 

Currently different 17 rules have been implemented, see https://github.com/larshp/abaplint/wiki. The checks can be enabled or disabled plus configured via a abaplint.json file. The project is open source and is work in progress, so expect some problems, but pull requests and suggestions are also welcome.

 

abaplint can be setup to run on Travis CI(see example at https://travis-ci.org/larshp/abapGit), each time a push is done to the ABAP git repository, Travis will download the latest abaplint via npm and run the linter on the committed code.

 

It also works in the Atom editor,

atom.png

 

And in Eclipse(update site to be created),

eclipse.png

 

Plus on the web

web.png

 

abapCov

abapCov takes a step towards visualizing the coverage of the unit tests run on the ABAP server.

 

Run abapCov on the ABAP server, it will run the unit tests with coverage enabled, and the  coverage result is then uploaded to https://codecov.io where anyone can check it out.

 

See example at https://codecov.io/github/larshp/abapOpenChecks

 

 

 

https://github.com/larshp/abaplint or via npm

https://github.com/larshp/abapCov


 

So far we have a working CDS view ready for us to create a UI5 application on top of it via Smart Template in WebIDE within just a couple of minutes. Once done, the UI5 application will display the data from our CDS view like below. For step by step how to achieve this, please refer to this blog: Step by Step to create CDS view through SmartTemplate + WebIDE .


clipboard1.png

How is navigation implemented among CDS views

 

In this part, let's create CDS view which supports node navigation in OData service. The previous CDS view we created has a flat structure which only have a root node. Now let's create a series of CDS views:

 

1. A CDS view which contains two fields: spfli.connid and spfli.carrid. This view acts as the root node of the corresponding OData service model from semantic point of view. This view can support navigation from itself to the defined children node.

 

2. A CDS view which acts as the navigation target from previously defined "root" view. Besides the two fields from sflight.connid and sflight.carrid which correspond to the root view, it has additional new field sflight.fldate.

 

OData navigation means suppose currently I am in the context of spfli.connid = 0001 and spfli.carrid ( data record with yellow ), and through navigation I can get all its dependent data in red color. We will see how this navigation would be performed later.

 

clipboard1.png

3. A CDS view which exposes the two fields connid and carrid from root view and the associated data  from child view.

This view is called "consumption" view and used to published as OData service.

 

Source code of view #1:

 

@AbapCatalog.sqlViewName: 'zspfliroot'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'root view'
define view Zspfli_Root as  select from spfli
association [0..*] to Zsflight_Child as _Item on $projection.carrid = _Item.carrid
                                       and $projection.connid = _Item.connid
{
  key spfli.connid,
  key spfli.carrid,
  @ObjectModel.association.type: #TO_COMPOSITION_CHILD
  _Item
}





Source code of view #2:

 

@AbapCatalog.sqlViewName: 'zsflightchild'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'child_view'
define view Zsflight_Child as select from sflight
association [1..1] to zspfli_root as _root
on $projection.connid = _root.connid
and $projection.carrid = _root.carrid
{
   key sflight.carrid,
   key sflight.connid,
   key sflight.fldate,
   @ObjectModel.association.type: [#TO_COMPOSITION_ROOT, #TO_COMPOSITION_PARENT]
   _root
}





Source code of view #3:

 

@AbapCatalog.sqlViewName: 'zflight_c'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'flight consumption view'
@OData.publish: true
@ObjectModel: {
   type: #CONSUMPTION,
   compositionRoot,
   createEnabled,
   deleteEnabled,
   updateEnabled
}
define view Zflight_Com as select from Zspfli_Root {
  key Zspfli_Root.carrid,
  key Zspfli_Root.connid,
  @ObjectModel.association.type: [#TO_COMPOSITION_CHILD]
  Zspfli_Root._Item
}





Activate all of these three CDS views. Since the third consumption view has annotation @OData.publish: true, once activated there will be an OData service automatically generated:

clipboard3.png

How to test navigation

 

First check the response from OData metadata request via url /sap/opu/odata/sap/ZFLIGHT_COM_CDS/$metadata in gateway client.

You should find two AssociationSets generated based on corresponding annotation in CDS views.

clipboard4.png

The entityset Zflight_Com has type Zflight_ComType, which has the navigation Property "to_Item". Now we can test the navigation.

clipboard5.png

First we get the root node's content via url: /sap/opu/odata/sap/ZFLIGHT_COM_CDS/Zflight_Com(connid='0400',carrid='LH') .

clipboard6.png

And in the response, we are told that the correct url for navigation from current node to its child node is just to append the navigation property defined in metadata, toItem, to the end of url, that is, /sap/opu/odata/sap/ZFLIGHT_COM_CDS/Zflight_Com(connid='0400',carrid='LH')/to_Item .

clipboard7.png

How the navigation is implemented in ABAP side

 

Set the breakpoint in the method below and re-trigger the navigation operation.

Check the generated SQL statement in variable statement in line 27.

clipboard8.png

SELECT "Zsflight_Child"."CARRID" AS "CARRID", "Zsflight_Child"."CONNID" AS "CONNID", "Zsflight_Child"."FLDATE" AS "FLDATE" FROM "ZSFLIGHTCHILD" AS "Zsflight_Child"
WHERE "Zsflight_Child"."CARRID" = ? AND "Zsflight_Child"."CONNID" = ? AND "Zsflight_Child"."MANDT" = '001' WITH PARAMETERS( 'LOCALE' = 'CASE_INSENSITIVE' )





 

The value for two placeholders ( ? ) are stored in me->parameters->param_tab:

clipboard9.png


And check response in et_flag_data:

clipboard10.png

clipboard11.png

In part1 of this tutorial, the old way to create OData service on top of CDS view is introduced.

In SAP Help Generate Service Artifacts From a CDS View a new annotation is described:


@OData.publish: true

 

Just add this annotation to your CDS view and the odata service is automatically created, no need for you to go to code SEGW any more.

clipboard1.png

Once activated, the odata service with naming convention "<your CDS view name>_CDS" is available for registration in tcode /IWFND/MAINT_SERVICE:

clipboard2.png

Metadata retrieval test ok:

clipboard3.png

So the question here is: How does this annotation work? How can we research the service generation process through debugging?

 

Here below is how I figure it out.

 

First check what objects have been generated: three additional artifacts are highlighted below.

 

  • IWMO: SAP Gateway Business Suite Enablement - Model
  • IWSV: SAP Gateway Business Suite Enablement - Service
  • CLAS: provider class ZCL_ZJERRYTEST20160311 is generated

clipboard4.png

If I remove the annotation, or change the annotation to @OData.publish: false, and the two are gone:

clipboard5.png

So it means the annotation @OData.publish: true will trigger table entry insertion for type IWMO, IWSV and CLAS during view activation. Then again I switch on ST05 trace and easily find the ABAP code where the table insertion is done.

clipboard6.png

Set breakpoint on the code and observe the callstack in the runtime.

clipboard7.png

The highlighted callstack frames are essential for odata service generation.

clipboard8.png

Let's have a deep look at stack frame 21:

CL_WB_DDLS_SECOBJ_HNDLR_SINGLE->IF_DDIC_WB_DDLS_SECOBJ_HANDLER~ON_ACTIVATION

 

It will first identify which objects must be created based on delta state.

clipboard9.png

For example, if I add @OData.publish: true to an existing CDS view and activate it, the corresponding entry will have flag N ( New ) while other existing annotation has type "U" ( Unchanged ).

clipboard10.png

Inside this method, if annotation ODATA.PUBLISH is found,

clipboard11.png

and the annotation value is true, then it is considered that the odata service must be created.

clipboard14.pngclipboard12.png

clipboard13.png

The odata service creation is implemented in CL_SADL_GTK_ODATA_SERVICE_GEN~CREATE_VIA_EXPOSURE below.

clipboard14.png

Complete callstack:

clipboard15.png

Some companies still run non unicode SAP systems. if you plan to convert your system to unicode, you can run UCCHECK regularly on your system, or add the unicode check to the code inspector framework and perform the check e.g. during release of transports or create a list with links to the source code using code inspector / ATC.

 

The attached document shows how to create an own CI test class performing the unicode check.

 

Create checker classes

Base syntax check class

Copy class CL_CI_SOURCE_SYNTAX_CHECK to your own namespace:

1.png

Set Unicode flag to all the report sources before executing the check in the constructor method.

Either fixed:

2.png

Or – optional - by a parameter:

3.png

4.png

 

Code inspector class

Copy class CI_TEST_SYNTAX_CHECK to your namespace.

Adapt copied CI class:

  1. Replace the class names in your new class.
    1. In the local friend definition of the unit test with your new created class.

      5.png
    2. In the class constructor of the test class

      6.png
    3. In attribute C_MY_NAME:
      7.png
    4. In method IF_CI_TEST~QUERY_ATTRIBUTES

      8.png
    5. In attribute REF_CHECK

      9.png
    6. Optional: modifications for Unicode check as using a parameter.
      1. When using Unicode parameter in constructor of base syntax checker class, you can pass the parameter in the GET method:
        Fixed

        10.png
      2. Or create a parameter in your CI class for the UCCHECK
        1. Create attribute

          11.png
        2. Adapt IF_CI_TEST~QUERY_ATTRIBUTES to provide parameter

          12.png

 

 

 

  1. Adapt data transfer methods
    1. PUT_ATTRIBUTES
      13.png
    2. GET_ATTRIBUTES
      14.png
  2. Change the description of the test in the constructor, to find it afterwards in the CI tree.
    (Length of the text symbol as to be adapted)

15.png

Prepare code inspector

Activate your test (class)

16.png

17.png

Create public check variant

18.png

Activate your check and set attribute for code inspector

19.png

 

Example Use check in AT

The created check variant can be used in various scenarios e.g. during transport release.

 

Prepare ATC

Set check variant & activate inform on errors

  20.png

With this settings, the UC check is performed on release.

21.png

22.png


Let's review what we have learned so far. Through SQL trace and debugging, we have learned below:

 

1. When we click activate button in ABAP development tool ( I call it Eclipse now in this blog ), there are several corresponding ABAP objects generated and table entries inserted. Through debugging we know lots of related logic are implemented in package SDDL.

 

2. When we test read & search function against the odata service created on top of the CDS view, the operation is delegated to CL_SQL_STATEMENT~EXECUTE_QUERY.

 

And now I ask myself, since what we have typed in Eclipse is pure text, how does ABAP backend interpret the text and convert it into ABAP DDIC objects? The only hint we have is the SDDL package. There are more than 10 classes in it. Although an experienced ABAP developer can easily identify which class is responsible for the text->ABAP view conversion just according to the class name and description, whereas ABAP newbies need more time to practice their intuition.

 

clipboard1.png

The tip here is, if you are not sure which class is what you are looking for, then always start with what you have already known. In our case it is CL_SQL_STATEMENT~EXECUTE_QUERY. Set breakpoint on this method and type some more characters in Eclipse, the breakpoint is triggered immediately.

 

From the callstack, we know that every time after you type something in Eclipse, it will fire Syntax check request to ABAP backend.

clipboard2.png


You can observe this via in the bottom part of your Eclipse.

clipboard3.png

From the callstack we find a class CL_DD_DDL_HANDLER which implements interface IF_DD_DDL_HANDLER which seems is what we look for. Open this class and there is a method GET_VIEWDEF_FROM_SRC, which indicates the method converts the SouRCe code to ABAP VIEW-DEFinition. Set breakpoint in it and click activate button again in Eclipse.

clipboard4.png

clipboard5.png

This class will parse source code:

clipboard6.png

clipboard7.png

clipboard8.png

From the code, it is very clear that the parse output, view definition in ABAP format is used to generate ABAP database view.

clipboard9.png

There is also another approach which can allow you to get a clearer view:

 

Open the "ABAP Communication Log" view:

clipboard10.png


Switch on Logging:

clipboard11.png

Then make some dummy change on your view, then you can observe the following six requests sent to ABAP backend.

The highlighted "abapCheckRun" is just the syntax check we already learned.

clipboard12.png

Double click on the request to see request detail. Here the view source code is encoded in Base64 format stored in chkrun:content.

clipboard13.png


We can of course see the original text from some decode-encode online conversion website:

clipboard14.png

If you could like to debug any request you see in ABAP communication log view, please refer to this document: An example to help you understand how does ADT work .

clipboard15.png

When you click activate button in Eclipse, you can know that a syntax check is always performed before the real activation.

clipboard16.png


 

 

 

You paste the following source code for a simple CDS view into ABAP development studio and activate it:

 

@AbapCatalog.sqlViewName: 'zjerrySQL0208'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'test 233'
@ObjectModel: {
   createEnabled,
   deleteEnabled,
   updateEnabled
}
define view Zjerrytest20160208
as select from spfli association [0..1] to scarr as _scarr
on _scarr.carrid = spfli.carrid {
       key spfli.carrid,
       key _scarr.carrname,
       key spfli.connid,
       spfli.cityfrom,
       spfli.cityto
}













And you would like to know what objects are automatically generated during CDS view activation.

 

Automatically generated ABAP objects during CDS view activation

 

You could query table TADIR with following parameters:

clipboard1.png

And get answer:

 

DDLS: Data Definition Language Source

 

STOB: Structured Object

clipboard2.png

The relationship among these objects is listed below:

clipboard3.png

And if you use the same approach described in tutorial part1, you can realize that lots of database tables "DD*" are involved during CDS view activation, for example DDLDEPENDENCY. A small tip here is, if you click "Display Object List" button, you will navigate to the package where other related ABAP artifacts within the same package are displayed as well.

clipboard4.png

clipboard5.png

Now we can go through each database table one by one.

 

Automatically inserted table entries during CDS view activation

 

Several table entries are inserted to the database tables in package SDDL during view activation.

 

DDDDLSRC

 

Query this table by specifying DDLNAME as CDS view name we specified in ABAP development studio, the name after keyword "define view" : Zjerrytest20160208, and we can find view source code stored in field SOURCE.

clipboard6.png

DDDDLSRC02BT

 

Text table which stores the view description specified via annotation @EndUserText.label.

clipboard7.png

DDHEADANNO

 

It stores all header annotation specified in CDS view source code with corresponding value.

clipboard8.png

DDLDEPENDENCY

 

It maintains relationship between the CDS core entity and automatically generated database view.

clipboard9.png

 

 

I am a newbie of CDS view related topic and recently I have to learn it. I will write down here not only the knowledge I learned but also the way how I obtain them via self study ( debugging, or other ABAP tool ). Because it would be quite easy for us to just paste the source code of sample CDS view from other guy's blog and activate it. The CDS view works. But what have you learned from this simple Ctrl+c and Ctrl+v? For me, I always get used to dig a little bit deeper such as "what has happened in the backend after I click the activate button in ABAP development studio? ".

 

In this part, I will introduce how to test the OData service generated based on my CDS view via Chrome extension - postman.

 

Prerequisite

 

I have created two simple CDS views. They are:

 

@AbapCatalog.sqlViewName: 'z20160310'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'consume view test '
@ObjectModel: {
  type: #CONSUMPTION,
  compositionRoot,
  semanticKey: ['Actor'],
  createEnabled,
  deleteEnabled,
  updateEnabled
}
define view Zjerrytest20160310 as select from Zjerrytest20160309 {
    key Zjerrytest20160309.carrid as Jerryid,
    key Zjerrytest20160309.carrname as name,
    key Zjerrytest20160309.cityfrom as startLocation,
    key Zjerrytest20160309.cityto as target,
    key Zjerrytest20160309.connid
}

















and

@AbapCatalog.sqlViewName: 'zjerrySQL0309'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #CHECK
@EndUserText.label: 'test 233'
@ObjectModel: {
  createEnabled,
  deleteEnabled,
  updateEnabled
}
define view Zjerrytest20160309
as select from spfli association [0..1] to scarr as _scarr
on _scarr.carrid = spfli.carrid {
      key spfli.carrid,
      key _scarr.carrname,
      key spfli.connid,
      spfli.cityfrom,
      spfli.cityto
}

















And create a project in tcode SEGW, import the first CDS view via context menu:

clipboard1.png

After that the project looks like below:

clipboard2.png

Generate runtime artifacts and register the odata service via tcode /IWFND/MAINT_SERVICE. Now the odata service is ready for testing.

 

Metadata test

 

Of course you can use SAP gateway client to test, however I prefer Chrome extension, postman, which can organize all my test cases in a hierarchical structure like below.

clipboard3.png

You have several ways to get the url to test metadata retrieve operation.

 

In tcode SEGW, you can get your service name.

clipboard4.png


Search it in tcode /IWFND/MAINT_SERVICE, click "SAP Gateway Client",

clipboard5.png

Change the url as "/sap/opu/odata/sap/<Your service name>/?$metadata", and then trigger request. You should see 200 return code with status OK.

clipboard6.png

Or you can also use Postman, in this case you have to paste the absolute url with host name and port number, both of which could be found in gateway client response, as marked by the black rectangle above.

clipboard7.png

Read operation test

 

The url I am using is:

https://<host name>:<port number>/sap/opu/odata/sap/ZJERRY20160310TRY_SRV/Zjerrytest20160310

 

The name "Zjerrytest20160310" is the entitySet name which you can find in SEGW.

clipboard8.png

The read operation works, see the part of response data below. But how does the read operation work under the hood?

clipboard9.png

We can ensure that the response we see are fetched from the automatically generated database view when CDS view is activated, and we would like to know which exact line of code in ABAP does this job. As introduced in my blog Six kinds of debugging tips to find the source code where the message is raised  we can get the answer via tcode ST05.

 

Switch on SQL trace in your system via tcode ST05, and then perform the read operation again. Once finished, display the trace result with filter Object Name = "*03*0*". ( Since at this time I am not sure whether data comes from Z20160309 or Z20160310 ).

clipboard10.png

Only one result is found, and click the button to display ABAP code.

clipboard11.png

Then we get what we look for. The line 22 does the read operation.

clipboard12.png

This method CL_SQL_STATEMENT~EXECUTE_QUERY is quite useful and would be used in following chapters of this tutorial as well.


Now we can study the callstack in the debugger to know how our request sent in UI is parsed and handled.

clipboard13.png

Variable lv_sql_statement in line 629 contains the automatically generated SQL statement:

clipboard14.png

SELECT "Zjerrytest20160310"."JERRYID" AS "JERRYID", "Zjerrytest20160310"."NAME" AS "NAME", "Zjerrytest20160310"."STARTLOCATION" AS "STARTLOCATION", "Zjerrytest20160310"."TARGET" AS "TARGET", "Zjerrytest20160310"."CONNID" AS "CONNID" FROM "Z20160310" AS "Zjerrytest20160310" WHERE "Zjerrytest20160310"."MANDT" = '001' WITH PARAMETERS( 'LOCALE' = 'CASE_INSENSITIVE' )

















 

The response data in ABAP format could be found in the variable et_flat_data in this callstack frame:

clipboard15.png

clipboard16.png

Filter operation test


The url I am using is:


https://<host name>:<port number>/sap/opu/odata/sap/ZJERRY20160310TRY_SRV/Zjerrytest20160310?$filter=(Jerryid%20eq'LH')

 

It means I want only those records which fulfill the condition "Jerryid = LH" are returned.

clipboard17.png

This time, the automatically generated SQL statement is a little bit different from the one for read operation.

Here the "?" acts as a placeholder for parameter, whose value is specified by another variable in line 29.

clipboard18.png

clipboard19.png

clipboard20.png

Once line 22 is executed, the filter operation works as expected.

clipboard21.png

We all have heard about dynamic conditions in WHERE clauses (Dynamic where clause - ABAP Development - SCN Wiki), dynamic READ statements (Dynamic WHERE in READ TABLE | SCN), dynamic internal tables/ structures/ ALV (Tutorial abap - Code for Dynamic Alv grid - Code Gallery - SCN Wiki), in short dynamic programming (Dynamic Programming -  Application Development on AS ABAP - SAP Library)...

 

But, when I searched about dynamic conditions in IF statement, I couldn't find many hits. Some of the hits said, it was not possible, some said to use ranges, and some others said it required dynamic subroutine generation etc.

 

I have used EVAL_FORMULA function module before (Program to evaluate formula in a string - ABAP Development - SCN Wiki). But, I haven't had much success with TEXT operations, using this function module.

 

So, I am here to discuss a method to achieve dynamic IF conditions in ABAP.

 

Interfaces used:

IF_FOBU_CONNECTOR

IF_FOEV_CONNECTOR

 

Classes used:

CL_FOBU_FORMULA - Formula Builder

CL_FOEV_FORMULA - Formula Evaluator

 

This same method can be used to evaluate dynamic formulae, just by changing the return type of the method. Since, I have used return type as BOOLEAN, I expect a TRUE or FALSE value to be returned by the method.

 

Global Class ZCL_EVALUATE_DYNAMIC_CONDITION:

    1  class zcl_evaluate_dynamic_condition definition

    2    public

    3    final

    4    create public .

    5 

    6    public section.

    7 

    8      interfaces if_fobu_connector .

    9      interfaces if_foev_connector .

   10 

   11      types:begin            of   ty_field_values .

   12      types name             type string.

   13      types data             type ref to data.

   14      types type             type string.

   15      types end              of   ty_field_values .

   16      types:tyt_field_values type standard table

   17            of ty_field_values with non-unique key table_line .

   18 

   19      class-methods factory

   20        returning

   21          value(rv_evaluator) type ref to zcl_evaluate_dynamic_condition .

   22      methods calculate

   23        importing

   24          value(it_dictionary) type tyt_field_values

   25          value(iv_formula)    type string

   26        returning

   27          value(rv_value)      type boolean .

   28    protected section.

   29    private section.

   30 

   31      data    dictionary       type tyt_field_values .

   32  endclass.

   33 

   34  class zcl_evaluate_dynamic_condition implementation.

   35 

   36    method calculate.

   37      data: lr_env     type ref to if_foev_connector,

   38            lr_fenv    type ref to if_fobu_connector,

   39            lr_formula type ref to cl_fobu_formula,

   40            lr_runtime type ref to cl_foev_formula.

   41 

   42      dictionary      =  it_dictionary.

   43      lr_env          ?= me.

   44      lr_fenv         ?= me.

   45      cl_fobu_formula=>create(

   46        exporting

   47          im_tech_names   = abap_true

   48          im_environment  = lr_fenv

   49          im_desired_type = 'BOOLEAN' "<<<<<

                 "Change this return type to use for other purposes

   50          io_fobu_storage = cl_fobu_storage=>get_instance( )

   51        importing

   52          ex_formula      = lr_formula ).

   53      lr_formula->parse( iv_formula ).

   54      lr_runtime       = cl_foev_formula=>load_from_fobu(

   55                           im_formula     = lr_formula

   56                           im_environment = lr_env ).

   57      data(lv_resuld)  = lr_runtime->evaluate( ).

   58      assign lv_resuld->* to field-symbol(<res>).

   59      rv_value         = <res>.

   60    endmethod.

   61 

   62    method factory.

   63      create object rv_evaluator.

   64    endmethod.

   65 

   66    method if_fobu_connector~check.

   67    endmethod.

   68 

   69    method if_fobu_connector~get.

   70    endmethod.

   71 

   72    method if_fobu_connector~get_all_operands.

   73    endmethod.

   74 

   75    method if_fobu_connector~parse_op_get.

   76      read table dictionary into data(meaning)

   77           with key name = ch_tech_name.

   78      if sy-subrc eq 0.

   79        ex_type  = meaning-type.

   80        ex_token = cl_fobu_formula=>c_token_appl_1.

   81      endif.

   82    endmethod.

   83 

   84    method if_fobu_connector~pushbuttons_get.

   85    endmethod.

   86 

   87    method if_fobu_connector~pushbutton_op_get.

   88    endmethod.

   89 

   90    method if_foev_connector~evaluate.

   91      read table dictionary into data(meaning)

   92           with key name = im_fieldname.

   93      if sy-subrc eq 0.

   94        assign meaning-data->* to field-symbol(<fld>).

   95        create data re_result like <fld>.

   96        assign re_result->* to field-symbol(<res>).

   97        <res> = <fld>.

   98      else.

   99        raise exception type cx_foev_formula_invalid

  100          exporting

  101            textid = |{ im_fieldname } field was not found in dictionary|.

  102      endif.

  103    endmethod.

  104  endclass.

 

Explanation:

  • IF_FOBU_CONNECTOR~PARSE_OP_GET helps in specifying the field types and token type, based on DICTIONARY entries.
  • IF_FOEV_CONNECTOR~EVALUATE helps in converting the fieldnames into values, based on the DICTIONARY entries.
  • LT_FIELDVALUES holds the values of all possible fields that may be used in the formula. Here, since I already know what are the fields used in my formula, I am passing only these values. This is stored in the DICTIONARY, so that the above methods may use it.
  • In the code below, I am passing a constant formula to the method, but purpose of this method is to analyze any dynamic text formula.

 

Example usage:

    1  class zcl_evaluate_dynamic_condition

    2        definition load.

    3  try.

    4    data:lt_fieldvalues

    5      type zcl_evaluate_dynamic_condition=>tyt_field_values.

    6    lt_fieldvalues = value #( ( name = 'PLAAB'

    7                              type = 'MDPS-PLAAB'

    8                              data = ref #( gs_mdps-plaab ) )

    9                            ( name = 'DELKZ'

   10                              type = 'MDPS-DELKZ'

   11                              data = ref #( gs_mdps-delkz ) )

   12                            ( name = 'LIFNR'

   13                              type = 'MDPS-LIFNR'

   14                              data = ref #( gs_mdps-lifnr ) )

   15                            ( name = 'PLUMI'

   16                              type = 'MDPS-PLUMI'

   17                              data = ref #( gs_mdps-plumi ) ) ).

   18 

   19    if zcl_evaluate_dynamic_condition=>factory( )->calculate(

   20      iv_formula     = |PLAAB = '02' AND DELKZ = 'BB'

                            AND LIFNR <> '' AND PLUMI = '-'|

   21      it_fieldvalues = lt_fieldvalues ) = abap_true.

   22      continue.

   23    endif.

   24  catch cx_root.

   25  endtry.

There was a requirement to get date name from a time stamp, which was stored as decimals in the tables.I need to use the ‘dayname’ date function of the sql query to convert to decimals to a string.

Asyou cannot use direct sql functions in CDS views,the only option was left to use CDS table function. As defined CDS table function is associated with an AMDP function, in which it is implemented using SQLScript Like any CDS entity. In the following example, I have no input parameters and it is not client dependent.

First, I created a table function using the statementDEFINE TABLE FUNCTIONin the ABAP CDS DDL.


@ClientDependent: false

@EndUserText.label: 'DEMO_TABLE_FUNCTION'

 

define table function  DEMO_TABLE_FUNCTION

 

returns {

        

       key     guid:GUID;

        weekdayname:string10;

            st_time:string10;

             }

implemented by method

    Z_CL_AMDP_CDS_TBLFN=>FIND_DAY_FROM_DATES;



Here the output parameters are guid,weekdayname and st_time which are associated with corresponding data types.Now I will create an AMDP class and flag it as a CDS table function. Please note an AMDP function implementation for a CDS table function can only be declared in the public visibility section of a static AMDP class.

 

CLASS Z_CL_AMDP_CDS_TBLFN DEFINITION

PUBLIC

FINAL

CREATE PUBLIC .

PUBLIC SECTION.

INTERFACES if_amdp_marker_hdb .

 

 

CLASS-METHODS find_day_from_dates FOR TABLE FUNCTION DEMO_ICM_TABLE_FUNCTIO.

 

 

 

PROTECTED SECTION.

PRIVATE SECTION.

  1. ENDCLASS.

 

 

 

CLASS Z_CL_AMDP_CDS_TBLFN IMPLEMENTATION.

 

 

METHOD find_day_from_dates

       BY DATABASE function FOR HDB

       LANGUAGE SQLSCRIPT

       OPTIONS READ-ONLY using table_name.

 

  RETURN SELECT CASE_GUID as guid,DAYNAME ( TO_DATE ( SUBSTRING (create_time,1,8))) as weekdayname,TO_DATE ( SUBSTRING (create_time,1,8)) as  st_time  FROM table_name  ;

 

 

  1. ENDMETHOD.
  2. ENDCLASS.

 

Create a class  and declare interface ‘if_amdp_marker_hdb’ for HANA database(for other database use interface name as  IF_AMDP_MARKER_(DB name)).Then declare the class method and use ‘FOR TABLE FUNCTION’ and the CDS table function name to declare the AMDP method as a CDS table function implementation . Not need to declare any importing exporting parameters in the declaration of method.

 

In the implementation of the class, declare method as an AMDP function. Use ‘RETURN’ statement to return to exporting parameters .The input and the output parameters in the AMDP function implementation are determined by the input parameters of the CDS table function.

 

Now create a CDS view in ABAP CDS DDL and use the CDS table function as a data source as the following.

 

@AbapCatalog.sqlViewName: 'CCMC'

@AbapCatalog.compiler.compareFilter: true

@AccessControl.authorizationCheck: #CHECK

@EndUserText.label: 'Day based graph'

define view  ZCOMBI_VIEWAND_FNTABLE as select from CDS_VIEW_NAME as VIEW_NAME1 inner join CDS_table_function_name as day_name on VIEW_NAME1. guid=day_name.guid {

    key day_name.weekdayname as DayName,

    VIEW_NAME1.description as Description,

 

count( * ) as DayNameCount

    }

    group by day_name.weekdayname, VIEW_NAME1.description

 

 

Note that I have not used input parameters in the CDS table function. If you are required to use CDS table function with parameters, please include then in the declaration of CDS table function as “define table function  DEMO_TABLE_FUNCTION FnName with parameters() returns()implemented by method MethodName”.

Preface

 

Not so long ago I discovered the "GET_SCREENSHOT" method of class "CL_GUI_FRONTEND_SERVICES". The method creates an image of your current SAP GUI window in the "Portable Network Graphics" format (shortly called "PNG"). My first thought was: "Great, useful for support teams." Second thought was: "Support teams would need more information such as transaction code or report name." Third thought was: "Would it be really necessary to have these information in an extra (text) file?" I did a little research and found a workable solution in the PNG format itself. This blog is about my implementation to demonstrate the mentioned solution. Please use it as starting point for your own research and share your ideas and thoughts about if you want.    

 

 

Required ressources

 

  • SAP Netweaver stack (I used SAP Netweaver Trial 7.40 on MaxDB)
  • W3C PNG specification (second edition, all mentioned chapter in this blogs belongs to this document)
  • TweakPNG (freeware for examining and modifying PNG image files)
  • ASCII/Hex-Converter (I used an online converter)
  • IrfanView (freeware image viewer)

 

 

Basic idea


The method "GET_SCREENSHOT" will be used to create a PNG image of the current SAP GUI window. PNG images consist of different chunks (cf. chapter 5.3). Each chunk has a chunk type to specify its function (cf. chapter 4.7.2). The chunk type "tEXt" is used to store text strings (cf. chapter 11.3.4.3). Keywords can be used to identify the content of the text string. Here are some useful keywords (cf. 11.3.4.2):


  • Title: short title or caption
  • Author: name of creator
  • Description: more details than in title
  • Creation Time: time of original image creation
  • Comment: miscellaneous comment

 

A Keyword and his associated text string are called chunk data. Now we have all fundamentals for implementing our own solution.

 

 

Source code of demo report

 

Here is my demo report. Pay attention to the comments and subroutine structure to follow the program flow.

 

*&---------------------------------------------------------------------*
*& Report  ZMKE_SCREENSHOT_ENHANCE
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*

REPORT zmke_screenshot_enhance.

PARAMETERS: pa_kword TYPE text20 AS LISTBOX VISIBLE LENGTH 20 DEFAULT '1',
            pa_txstr TYPE text20.

INITIALIZATION.
   PERFORM initialization.

AT SELECTION-SCREEN OUTPUT.
   PERFORM at_selection_screen_output.

START-OF-SELECTION.
   PERFORM main.

*&---------------------------------------------------------------------*
*&      Form  MAIN
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM main.

   DATA: lv_subrc TYPE sysubrc,
         lv_image TYPE xstring.

   IF pa_kword IS INITIAL.
     MESSAGE 'Please choose a keyword.' TYPE 'I'.
     RETURN.
   ENDIF.

   IF pa_txstr IS INITIAL.
     MESSAGE 'Please supply a text string.' TYPE 'I'.
     RETURN.
   ENDIF.

   PERFORM screenshot_create
           CHANGING lv_subrc
                    lv_image.

   IF lv_subrc <> 0.
     RETURN.
   ENDIF.

   PERFORM screenshot_enhance
           CHANGING lv_subrc
                    lv_image.

   IF lv_subrc <> 0.
     RETURN.
   ENDIF.

   PERFORM screenshot_download
           USING    lv_image
           CHANGING lv_subrc.

   IF lv_subrc <> 0.
     RETURN.
   ENDIF.

ENDFORM.                    " MAIN
*&---------------------------------------------------------------------*
*&      Form  SCREENSHOT_CREATE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      <--P_LV_SUBRC  text
*----------------------------------------------------------------------*
FORM screenshot_create CHANGING cv_subrc TYPE sysubrc
                                cv_image TYPE xstring.

   DATA lv_mtype TYPE string.

   CALL METHOD cl_gui_frontend_services=>get_screenshot
     IMPORTING
       mime_type_str        = lv_mtype
       image                = cv_image
     EXCEPTIONS
       access_denied        = 1
       cntl_error           = 2
       error_no_gui         = 3
       not_supported_by_gui = 4
       OTHERS               = 5.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

ENDFORM.                    " SCREENSHOT_CREATE
*&---------------------------------------------------------------------*
*&      Form  SCREENSHOT_ENHANCE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_LV_IMAGE  text
*      <--P_LV_SUBRC  text
*----------------------------------------------------------------------*
FORM screenshot_enhance CHANGING cv_subrc TYPE sysubrc
                                 cv_image TYPE xstring.

   CONSTANTS: lc_zero  TYPE xstring VALUE '00',
              lc_space TYPE xstring VALUE '20'.

   TYPES: BEGIN OF line,
           text TYPE text132,
          END OF line.

   DATA: lv_chunk  TYPE xstring,
         lv_ileng  TYPE i,
         lv_xleng  TYPE x LENGTH 4,
         lv_icrc   TYPE i,
         lv_xcrc   TYPE x LENGTH 4,
         lt_stext  TYPE TABLE OF line,
         ls_stext  TYPE line,
         lv_search TYPE xstring,
         lv_rest   TYPE REF TO data,
         lv_offset TYPE i,
         lv_kword  TYPE text20.

   FIELD-SYMBOLS <fs_rest> TYPE any.

* choose keyword
   CASE pa_kword.
     WHEN '1'.
       lv_kword = 'Title'.

     WHEN '2'.
       lv_kword = 'Author'.

     WHEN '3'.
       lv_kword = 'Description'.

     WHEN '4'.
       lv_kword = 'Source'.

     WHEN '5'.
       lv_kword = 'Comment'.
   ENDCASE.

* combine chunk type ("tEXt") and keyword
  CONCATENATE 'tEXt' lv_kword INTO ls_stext.

* combine chunk type, keyword and text string
  CONCATENATE ls_stext pa_txstr INTO ls_stext SEPARATED BY space.
  APPEND ls_stext TO lt_stext.

   CALL FUNCTION 'SCMS_TEXT_TO_XSTRING'
*   EXPORTING
*     FIRST_LINE       = 0
*     LAST_LINE        = 0
*     MIMETYPE         = ' '
*     ENCODING         =
     IMPORTING
       buffer   = lv_chunk
     TABLES
       text_tab = lt_stext
     EXCEPTIONS
       failed   = 1
       OTHERS   = 2.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

* "Zero Byte" is delimiter between keyword and text string
* (remember: keyword and text string are our "chunk data")
   REPLACE FIRST OCCURRENCE OF lc_space
           IN lv_chunk
           WITH lc_zero
           IN BYTE MODE.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

* function "SCMS_TEXT_TO_XSTRING" adds a line break at the end
* which has to be removed
   lv_ileng = xstrlen( lv_chunk ) - 2.
   lv_chunk = lv_chunk+0(lv_ileng).

* get length of chunk data (ignore chunk type by -4)
   CLEAR lv_ileng.
   lv_ileng = xstrlen( lv_chunk ) - 4.
   lv_xleng = lv_ileng.

* generate CRC32 for chunk type and chunk data
   CALL METHOD cl_abap_zip=>crc32
     EXPORTING
       content = lv_chunk
     RECEIVING
       crc32   = lv_icrc.

   lv_xcrc = lv_icrc.

* build complete chunk
   CONCATENATE lv_xleng lv_chunk lv_xcrc INTO lv_chunk IN BYTE MODE.

* place the new chunk before "IEND"-chunk
   CLEAR lt_stext.
   ls_stext = 'IEND'.
   APPEND ls_stext TO lt_stext.

   CALL FUNCTION 'SCMS_TEXT_TO_XSTRING'
*   EXPORTING
*     FIRST_LINE       = 0
*     LAST_LINE        = 0
*     MIMETYPE         = ' '
*     ENCODING         =
     IMPORTING
       buffer   = lv_search
     TABLES
       text_tab = lt_stext
     EXCEPTIONS
       failed   = 1
       OTHERS   = 2.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

   lv_search = lv_search+0(4). " avoid line break

   FIND FIRST OCCURRENCE OF lv_search
        IN cv_image
        IN BYTE MODE
        MATCH OFFSET lv_offset
        MATCH LENGTH lv_ileng.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

* respect length indication befor "IEND"-chunk by -4
   lv_offset = lv_offset - 4.
   lv_ileng = xstrlen( cv_image ) - lv_offset.

* create variable for the image's tail
   CREATE DATA lv_rest TYPE x LENGTH lv_ileng.
   ASSIGN lv_rest->* TO <fs_rest>.
   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

   <fs_rest> = cv_image+lv_offset(lv_ileng).

   CONCATENATE lv_chunk <fs_rest> INTO lv_chunk IN BYTE MODE.
   REPLACE SECTION OFFSET lv_offset
           OF cv_image
           WITH lv_chunk
           IN BYTE MODE.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

ENDFORM.                    " SCREENSHOT_ENHANCE
*&---------------------------------------------------------------------*
*&      Form  SCREENSHOT_DOWNLOAD
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_LV_IMAGE  text
*      <--P_LV_SUBRC  text
*----------------------------------------------------------------------*
FORM screenshot_download USING    uv_image TYPE xstring
                          CHANGING cv_subrc TYPE sysubrc.

   DATA: lt_bdata  TYPE TABLE OF x,
         lv_title  TYPE string,
         lv_dname  TYPE string,
         lv_fname  TYPE string,
         lv_fpath  TYPE string,
         lv_path   TYPE string,
         lv_usrac  TYPE i,
         lv_filter TYPE string.

   CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
     EXPORTING
       buffer     = uv_image
*     APPEND_TO_TABLE = ' '
*   IMPORTING
*     OUTPUT_LENGTH   =
     TABLES
       binary_tab = lt_bdata.

   IF lt_bdata IS INITIAL.
     cv_subrc = 4.
     RETURN.
   ENDIF.

   lv_title = 'Download image to ...'.
   CONCATENATE sy-datum '-' sy-uzeit '.png' INTO lv_dname.
   lv_filter = '*.png'.

   CALL METHOD cl_gui_frontend_services=>file_save_dialog
     EXPORTING
       window_title         = lv_title
       default_extension    = '*.png'
       default_file_name    = lv_dname
*     with_encoding        =
       file_filter          = lv_filter
*     initial_directory    =
*     prompt_on_overwrite  = 'X'
     CHANGING
       filename             = lv_fname
       path                 = lv_path
       fullpath             = lv_fpath
       user_action          = lv_usrac
*     file_encoding        =
     EXCEPTIONS
       cntl_error           = 1
       error_no_gui         = 2
       not_supported_by_gui = 3
       OTHERS               = 4.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

   IF lv_usrac = cl_gui_frontend_services=>action_cancel.
     RETURN.
   ENDIF.

   CALL METHOD cl_gui_frontend_services=>gui_download
     EXPORTING
*     BIN_FILESIZE            =
       filename                = lv_fname
       filetype                = 'BIN'
*     APPEND                  = SPACE
*     WRITE_FIELD_SEPARATOR   = SPACE
*     HEADER                  = '00'
*     TRUNC_TRAILING_BLANKS   = SPACE
*     WRITE_LF                = 'X'
*     COL_SELECT              = SPACE
*     COL_SELECT_MASK         = SPACE
*     DAT_MODE                = SPACE
*     CONFIRM_OVERWRITE       = SPACE
*     NO_AUTH_CHECK           = SPACE
*     CODEPAGE                = SPACE
*     IGNORE_CERR             = ABAP_TRUE
*     REPLACEMENT             = '#'
*     WRITE_BOM               = SPACE
*     TRUNC_TRAILING_BLANKS_EOL = 'X'
*     WK1_N_FORMAT            = SPACE
*     WK1_N_SIZE              = SPACE
*     WK1_T_FORMAT            = SPACE
*     WK1_T_SIZE              = SPACE
*     SHOW_TRANSFER_STATUS    = 'X'
*     FIELDNAMES              =
*     WRITE_LF_AFTER_LAST_LINE  = 'X'
*     VIRUS_SCAN_PROFILE      = '/SCET/GUI_DOWNLOAD'
*    IMPORTING
*     FILELENGTH              =
     CHANGING
       data_tab                = lt_bdata
     EXCEPTIONS
       file_write_error        = 1
       no_batch                = 2
       gui_refuse_filetransfer = 3
       invalid_type            = 4
       no_authority            = 5
       unknown_error           = 6
       header_not_allowed      = 7
       separator_not_allowed   = 8
       filesize_not_allowed    = 9
       header_too_long         = 10
       dp_error_create         = 11
       dp_error_send           = 12
       dp_error_write          = 13
       unknown_dp_error        = 14
       access_denied           = 15
       dp_out_of_memory        = 16
       disk_full               = 17
       dp_timeout              = 18
       file_not_found          = 19
       dataprovider_exception  = 20
       control_flush_error     = 21
       not_supported_by_gui    = 22
       error_no_gui            = 23
       OTHERS                  = 24.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

ENDFORM.                    " SCREENSHOT_DOWNLOAD
*&---------------------------------------------------------------------*
*&      Form  AT_SELECTION_SCREEN_OUTPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM at_selection_screen_output.

   DATA: ls_values TYPE vrm_value,
         lt_values TYPE vrm_values.

   ls_values-key  = '1'.
   ls_values-text = 'Title'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '2'.
   ls_values-text = 'Author'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '3'.
   ls_values-text = 'Description'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '4'.
   ls_values-text = 'Source'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '5'.
   ls_values-text = 'Comment'.
   APPEND ls_values TO lt_values.

   CALL FUNCTION 'VRM_SET_VALUES'
     EXPORTING
       id              = 'PA_KWORD'
       values          = lt_values
     EXCEPTIONS
       id_illegal_name = 1
       OTHERS          = 2.

   IF sy-subrc <> 0.
     RETURN.
   ENDIF.

ENDFORM.                    " AT_SELECTION_SCREEN_OUTPUT
*&---------------------------------------------------------------------*
*&      Form  INITIALIZATION
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM initialization.

   pa_txstr = 'Hello World!'.

ENDFORM.                    " INITIALIZATION



Example


For our example we us "Title" as keyword and "Hello World!" as text string.

 

StepDescription
1We get a screenshot of our current SAP GUI window.
2

We combine chunk type, keyword and our text string.

Result: "tEXtTitle Hello World!"

3

We convert it to hex and place the "Zero Byte" delimiter between keyword and text string (in hex it's "00").

Result: "744558745469746C650048656C6C6F20576F726C6421"

  • "74455874" corresponds to "tEXt",
  • "5469746C65" corresponds to "Title"
  • "00" is a zero byte
  • "48656C6C6F20576F726C6421" corresponds to "Hello World!"
4

We get the length of the chunk data (that are keyword and text string).

Result: 18 bytes
5

We generate a CRC32 for chunk type and chunk data.

Result: "1373921944" (in hex it is "51E46298")

6

We build the complete chunk. It consists of length, chunk type, chunk data and CRC32.

Result "00000012744558745469746C650048656C6C6F20576F726C642151E46298"

7We place our tEXt-chunk before the IEND-chunk.
8We download our enhanced screenshot to desktop.


After downloading the image to your desktop, you can use IrfanView for displaying.

Demo.jpg

Have a look at the function "View->Show HEX view" of IrfanView.

HexView.jpg

With TweakPNG you can have a look at the different chunks. You will find our tEXt-chunk "Title" with the corresponding text string "Hello World!".

TweakPNG.jpg



Idea for practical use


It's just an idea and not a well proofed business case: Create your own generic object service to get a screenshot of the current SAP GUI window. Analyze the business context (given business object type and id, sy-structure, callstack and more - perhaps I will show that in another blog) and encode the gathered data into the PNG image as shown above. Download the image to the user's desktop so he can mail it to his support team. Support team of course would need a tool to show the PNG image and decode all tEXt chunks in it. Even if the user puts no additional information in his mail there are some helpful business context information in the image. As I said before just an idea ...

Actions

Filter Blog

By author:
By date:
By tag: