1 6 7 8 9 10 48 Previous Next

ABAP Development

716 Posts
Paul Hardy

Shoot Me Up ABAP

Posted by Paul Hardy Mar 25, 2014

Dependency Injection

 

image001.png

 

There is many a true word, Spoken Inject

 

One line summary:-

 

One way to write OO programs with many small classes with less lines of code.

 

Back Story

 

The other day there was a blog on SCN about Dependency injection.

 

http://scn.sap.com/community/abap/blog/2014/01/06/successful-abap-dependency-injection

 

I thought – I know what that is – if an object (say a car object) needs an engine object to work, you don’t have the car object create the engine object, you pass in the engine object through the car objects constructor.

 

I had thought that was solely concerned with unit testing, but if you look at the comments at the bottom, when I started talking about this on the blog comments, people soon put me right, it turns out it has a much wider scope.

 

As soon as I realised I was barking up the wrong tree, I read all I could on the subject, for example …

 

http://en.wikipedia.org/wiki/Dependency_injection

 

http://martinfowler.com/articles/injection.html

 

http://www.jamesshore.com/Blog/Dependency-Injection-Demystified.html

 

… ending with this blog by Jack Stewart

 

http://scn.sap.com/community/abap/blog/2013/08/28/dependency-injection-for-abap

 

I always thought the idea was great – often you have to create a bunch of objects and then “wire them together” by passing them into each other’s constructors so they know about each other.

 

This gives you the flexibility to pass in subclasses to alter the behaviour of the application – as I said I had first heard about this in the context of unit testing, but when I thought about it again naturally you can pass in any sort of subclass to change the way the program runs e.g. different subclass based on whatever criteria makes sense, just like the BADI filter mechanism.

 

That is a wonderful thing to be able to do, and subclassing is one of the few benefits of OO programming that one of my colleagues can get his head around, but it does tend to involve a lot of “boiler plate” programming i.e. lots of CREATE OBJECT statements, passing in assorted parameters.

 

Many Small Classes, make Light Work

 

http://scn.sap.com/community/abap/blog/2013/08/22/the-issue-with-having-many-small-classes

 

The idea is that the smaller and more focused your classes are, the easier they are to re-use and maintain. An OO principle is that a class should only have one reason to change i.e. it should do one thing only. If you follow that principle you get loads of benefits, but you have to create loads of classes in your program.

 

When I first started playing around with OO programming I was too lazy to keep writing CREATE OBJECT so I made everything static. That is not actually a sensible thing to do just to avoid work, as then you can’t subclass things. SAP itself found that out when they initially made ABAP proxy classes static.

 

The NEW Objects on the Block

 

In the Java programming language you create objects by saying GOAT = NEW GOAT as opposed to CREATE OBJECT GOAT.

 

In the “Head First Design Patterns Book” it gives a bunch of about five rules of programming which every Java programmer should aspire to but are in fact impossible to follow in real life.

 

One of those revolved around the rule being never to use the NEW statement because that hard coded the exact type of class you were creating, but how can you create objects if the only way to create them is to use the NEW statement?

 

In both Java and ABAP interfaces come into play here, you declare the ANIMAL object as an interface of type FARM ANIMAL (which GOAT implements) and say CREATE OBJECT ANIMAL TYPE GOAT. Perhaps a better example is in ABAP2XLS when you declare the object that writes out the file as an interface and then create it using the TYPE of the EXCEL version you want e.g. 2007.

 

Now you are always going to have to say the specific type (subclass) you want somewhere, but is it possible to decouple this from the exact instant you call the CREATE OBJECT statement?

 

Since you can have a dynamic CREATE OBJECT statement, you would think so, but how does this apparent diversion link back to what I was talking about earlier?

 

Jack Black and his blog Silver

 

Going back to Dependency Injection the blog by Jack Stewart contained a link to download some sample code. I downloaded it, had a look, thought it was great, and then totally re-wrote it. That is no reflection on the quality of the original; I am just physically incapable of not rewriting every single thing I come across.

 

I am going to include a SAPLINK file in text format at the end of this blog, but first I shall go through the code, top down. Firstly, this test program shows exactly what I am trying to achieve i.e. the same thing in less lines of code.

 

I have created some dummy Y classes which just have constructors to pass in a mixture of object instances and elementary data object parameters, my dear Watson. They only have one method each, just to write out if they are a base class or a subclass. The important thing is the effort involved to create them.

 

The Da Vinci Code Samples

 

First of all, a basic structure to get some elementary parameters and say if we want to use a test double or not. I am sticking with the unit test concept for now, but as I mentioned, you can pass in any old subclass you want, according to the good old, every popular, Liskov Substitution principle.

 

*&---------------------------------------------------------------------*
*& Report  Y_INJECTION_TEST
*&
*&---------------------------------------------------------------------*
* Show two ways to create linked objects, one using dependency injection
*--------------------------------------------------------------------*
REPORT  y_injection_test.

PARAMETERS : p_valid TYPE sy-datum,
             p_werks
TYPE werks_d,
             p_test 
AS CHECKBOX.

INITIALIZATION.
  p_valid
= sy-datum.
  p_werks
= '3116'.

START-OF-SELECTION.
 
PERFORM do_it_the_long_way.
 
PERFORM do_it_the_short_way.

 

It’s a Long Long Way, from there to here

 

Firstly, the traditional way….

 

*&---------------------------------------------------------------------*
*&      Form  DO_IT_THE_LONG_WAY
*&---------------------------------------------------------------------*
* Normal way of doing things
*----------------------------------------------------------------------*
FORM do_it_the_long_way .
 
DATA: lo_logger        TYPE REF TO ycl_test_logger.
 
DATA: lo_db_layer      TYPE REF TO ycl_test_db_layer.
 
DATA: lo_mock_db_layer TYPE REF TO ycl_test_mock_db_layer.
 
DATA: lo_simulator     TYPE REF TO ycl_test_simulator.

 
CREATE OBJECT lo_logger.

 
IF p_test = abap_true.

   
CREATE OBJECT lo_mock_db_layer
     
EXPORTING
        io_logger  
= lo_logger
        id_valid_on
= p_valid.

   
CREATE OBJECT lo_simulator
     
EXPORTING
        id_plant_id  
= p_werks
        io_db_layer  
= lo_mock_db_layer
        io_logger    
= lo_logger.

 
ELSE.

   
CREATE OBJECT lo_db_layer
     
EXPORTING
        io_logger  
= lo_logger
        id_valid_on
= p_valid.

   
CREATE OBJECT lo_simulator
     
EXPORTING
        id_plant_id  
= p_werks
        io_db_layer  
= lo_db_layer
        io_logger    
= lo_logger.

 
ENDIF.

  lo_simulator
->say_who_you_are( ).

 
SKIP.

ENDFORM.                    " DO_IT_THE_LONG_WAY

 

Get Shorty

 

Now we do the same thing, using a Z class I created to use dependency injection.

 

*&---------------------------------------------------------------------*
*&      Form  DO_IT_THE_SHORT_WAY
*&---------------------------------------------------------------------*
*  Using Constructor Injection
*----------------------------------------------------------------------*
FORM do_it_the_short_way .
* Local Variables
 
DATA: lo_simulator  TYPE REF TO ycl_test_simulator.

  zcl_bc_injector
=>during_construction( :
    for_parameter
= 'ID_PLANT_ID' use_value = p_werks ),
    for_parameter
= 'ID_VALID_ON' use_value = p_valid ).

 
IF p_test = abap_true.
   
"We want to use a test double for the database object
    zcl_bc_injector
=>instead_of( using_main_class = 'YCL_TEST_DB_LAYER'
                                 use_sub_class   
= 'YCL_TEST_MOCK_DB_LAYER' ).
 
ENDIF.

  zcl_bc_injector
=>create_via_injection( CHANGING co_object = lo_simulator ).

  lo_simulator
->say_who_you_are( ).

ENDFORM.                    " DO_IT_THE_SHORT_WAY

 

I think the advantage is self-evident – the second way is much shorter, and it’s got Big Feet.

 

If the importing parameter of the object constructor was an interface it would not matter at all. You just pass the interface name in to the INSTEAD_OF method as opposed to the main class name.

 

I have done virtually no error handling in the below code, except throwing fatal exceptions when unexpected things occur. This could be a lot more elegant, I am just demonstrating the basic principle.

 

Firstly the DURING CONSTRUCTION method analyses elementary parameters and then does nothing more fancy than adding entries to an internal table.

 

* Local Variables
 
DATA: lo_description       TYPE REF TO cl_abap_typedescr,
        ld_dummy            
TYPE string ##needed,
        ld_data_element_name
TYPE string,
        ls_parameter_values 
LIKE LINE OF mt_parameter_values.

  ls_parameter_values
-identifier = for_parameter.

 
CREATE DATA ls_parameter_values-do_value LIKE use_value.
 
GET REFERENCE OF use_value INTO ls_parameter_values-do_value.

 
CHECK sy-subrc = 0.

 
CALL METHOD cl_abap_structdescr=>describe_by_data_ref
   
EXPORTING
      p_data_ref          
= ls_parameter_values-do_value
    RECEIVING
      p_descr_ref         
= lo_description
   
EXCEPTIONS
      reference_is_initial
= 1
     
OTHERS               = 2.

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

 
SPLIT lo_description->absolute_name AT '=' INTO ld_dummy ld_data_element_name.

  ls_parameter_values
-rollname = ld_data_element_name.

 
INSERT ls_parameter_values INTO TABLE mt_parameter_values.

 

It’s the same deal with the INSTEAD_OF method for saying what exact subclass you want to create, except it’s even simpler this time.

 

METHOD instead_of.
* Local Variables
 
DATA: ls_sub_classes_to_use LIKE LINE OF mt_sub_classes_to_use.

  ls_sub_classes_to_use
-main_class = using_main_class.
  ls_sub_classes_to_use
-sub_class  = use_sub_class.

 
"Add entry at the start, so it takes priority over previous
 
"similar entries
 
INSERT ls_sub_classes_to_use INTO mt_sub_classes_to_use INDEX 1.

ENDMETHOD.

 

Now we come to the main CREATE_BY_INJECTION method.  I like to think I have written this as close to plain English as I can, so that this is more or less elf-explanatory.

 

METHOD create_via_injection.
* Local Variables
 
DATA: lo_class_in_reference_details  TYPE REF TO cl_abap_refdescr,
        lo_class_in_type_details      
TYPE REF TO cl_abap_typedescr,
        lo_class_to_create_type_detail
TYPE REF TO cl_abap_typedescr,
        ld_class_passed_in            
TYPE seoclass-clsname,
        ld_class_type_to_create       
TYPE seoclass-clsname,
        ls_created_objects            
LIKE LINE OF mt_created_objects,
        lt_signature_values           
TYPE abap_parmbind_tab.

* Determine the class type of the reference object that was passed in
  lo_class_in_reference_details ?= cl_abap_refdescr
=>describe_by_data( co_object ).
  lo_class_in_type_details      
= lo_class_in_reference_details->get_referenced_type( ).
  ld_class_passed_in            
= lo_class_in_type_details->get_relative_name( ).

 
"See if we need to create the real class, or a subclass
  determine_class_to_create
(
   
EXPORTING
      id_class_passed_in            
= ld_class_passed_in
      io_class_in_type_details      
= lo_class_in_type_details
   
IMPORTING
      ed_class_type_to_create       
= ld_class_type_to_create
      eo_class_to_create_type_detail
= lo_class_to_create_type_detail ).

 
READ TABLE mt_created_objects INTO ls_created_objects WITH TABLE KEY clsname = ld_class_type_to_create.

 
IF sy-subrc = 0.
   
"We already have an instance of this class we can use
    co_object ?= ls_created_objects
-object.
   
RETURN.
 
ENDIF.

 
"See if the object we want to create has parameters, and if so, fill them up
  fill_constructor_parameters
( EXPORTING io_class_to_create_type_detail = lo_class_to_create_type_detail
                              
IMPORTING et_signature_values            = lt_signature_values ).


  create_parameter_object
( EXPORTING id_class_type_to_create = ld_class_type_to_create
                                     it_signature_values    
= lt_signature_values       " Parameter Values
                          
CHANGING  co_object               = co_object ).              " Created Object

ENDMETHOD.

 

There is not a lot of point in drilling into this any further – I would encourage you to download the SAPLINK file, and then run this in debug mode to see what is happening.

 

In summary, I am always on the lookout for ways to reduce the so called “boiler plate” code, so the remaining code can concentrate on what the application is supposed to be doing as opposed to how it is doing it. This dependency injection business seems ideally suited so this purpose.

 

Now, while I am here.

 

image002.png

 

Did I mention I am giving a speech at the “Mastering SAP Technology 2014” conference at Melbourne on the 31/03/2014 – it’s about unit testing of ABAP programs.

 

What’s that? I’ve already mentioned this? Many times?

 

Oh dear, that must have slipped my mind. In that case I won’t go on about it, and I’ll sign off.

 

Cheersy Cheers

 

Paul

 

Sometimes you would like to view the content of field with RAWSTRING type for a table:

clipboard1.png

The raw string represents the configuration with XML format however the correct format could not be viewed in SE16 directly.

clipboard2.png

In fact, the dynpro in the screenshot above is implemented by a program which is automatically generated by framework. You could find its name via System->Status:

clipboard3.png

clipboard4.png

clipboard5.png

execute report RS_ABAP_SOURCE_SCAN with search key = select * from BSPC_DL_PERSSTOR, search program = /1BCDWB/DBBSPC_DL_PERSSTOR.

clipboard6.png

Set breakpoint on the three search result:

clipboard7.png

relaunch SE16 and access the table, one of the breakpoint is triggered:

clipboard8.png

switch to XML Browser:

clipboard9.png

then you can see XML detail in debugger. With such tip it is not necessary to write any report to select the xml data out of the database table.

clipboard10.png


Nick Young

Addressing down

Posted by Nick Young Mar 24, 2014

Triggered by this forum post Issue of blank lines removal in address in master page in Adobe form, I’ve decided to tackle a topic that has been eating at me for some years.

 

Why do developers seem so reluctant to use address nodes in forms?

 

This is not a criticism of Anindita, the author of the post who has inherited a form and understandably wants to minimise the amount of change.  It’s more a result of spending years having to convince developers that address nodes and not individual text fields are the best way to deliver this functionality.

 

"I was only doing what I was taught"

 

My own theory as to why this is not adopted puts the blame squarely with the SAP training material.  We’re all familiar with the flight model that is used in the ABAP training (I found my original ABAP training certificate from 1991 recently, and as I recall that course used the same model).  But the problem is that this model pre-dates the introduction of Central Address Management (or Business Address Services as it seems to be called now).  So while it’s fine for the programming courses, the form development courses tend not to give CAM or BAS the focus it deserves.  While the courses for SAPScript, Smartforms and adobe forms all cover the topic of the address node, none of them include the topic in the exercises.

 

When I taught the SAPscript and Smartform courses myself I always checked table ADRC in the training system and found some valid address numbers to both demonstrate their use and include the topic in the exercises, but any trainer focusing solely on the material will inevitably skim over this topic.

 

"I was only doing what I was told"

 

My other theory is that developers are following Functional Specs too closely.  A form FS will often include a mock-up something like this;

form_2.png

Then rather than challenging the specific example or just using an address node because it’s best practice, the developer will slavishly follow what has been specified.  And in the relatively clean data of a project test system all will be well, only when the vagaries of production data are introduced do blank lines appear in the address, and by then there’s a reluctance to make fundamental changes to forms.


The advantages to address nodes are many, compression of blank lines, prioritisation of lines when space is limited, international formatting, fewer fields passed from the print program or initialisation.  I could cover these in detail, but they’re all covered in the SAP help and there’s not a great deal I could add to that.


Now, like any technique I’m sure there are disadvantages to address nodes and please use the comments section to point out their shortcomings.  Otherwise, go out there and champion the often forgotten address node.

Here I will not write about the details of using BAPI_GOODSMVT_CREATE, has already
been written about this many times, and SCN including.

I propose to focus on one small detail, without which multiple call BAPI_GOODSMVT_CREATE will not work correctly.


Why BAPI_GOODSMVT_CREATE called repeatedly in his Z program? For example, you specify parameters for moving material,
but BAPI returned an error.
You change something and press the button again, causing BAPI.

 


So, if the call looks CALL FUNCTION 'BAPI_GOODSMVT_CREATE' again you'll get an error, despite the correct parameters.

But if you specify the addition CALL FUNCTION 'BAPI_GOODSMVT_CREATE' DESTINATION 'NONE' - the document will be created!

Thus, using DESTINATION 'NONE', you can be sure that the data buffer previous calls have no impact!

 

P.S. It is also necessary to specify DESTINATION 'NONE'  in calling COMMIT or ROLLBACK like below

IF p_matdoc IS INITIAL .

  CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK' DESTINATION 'NONE'.

ELSE .

  CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' DESTINATION 'NONE'.

ENDIF .

CALL FUNCTION 'RFC_CONNECTION_CLOSE'    EXPORTING      destination = 'NONE'.

Hi SCN community!

 

If you're not familiar with Matthew Billingham's Project Objectify, please read it before you continue.

 

The idea is simple... let's build a set of highly reusable, flexible and helpful abap classes that we share and collaborate on.

 

Who hasn't had the feeling to be writing the same code over and over again? To get some document flow, pricing conditions, etc. Wouldn't it make more sense to have a set of powerful abap classes, properly designed and coded, that you can easily export/import for reuse?

 

The idea was coined in 2009 by Matthew, and I was surprised to see no one had actually picked it up, so I've created a github repository for this, and I've started by sharing a few very simple classes, that I hope will set the template for future development.

 

Here is the link for it: https://github.com/EsperancaB/sap_project_object

 

Hope to see you there.

 

All the best,

Bruno

The requirement is: there is an internal table with a large number of table row.

 

If all rows have the identical recipient_id, that id( 30273 ) must be returned.

 

UUID

Phone_number

Recipient_id

0412ASFDSFDSFXCVS

138XXXXX1

30273

0412ASFDSFDSFXCVD

138XXXXX2

30273

0412ASFDSFDSFXCVF

138XXXXX3

30273

30273

 

If not, it must return empty.

UUID

Phone_number

Recipient_id

0412ASFDSFDSFXCVS

138XXXXX1

30273

0412ASFDSFDSFXCVD

138XXXXX2

30273

0412ASFDSFDSFXCVF

138XXXXX3

30273

30272

 

The table line type structure in the project looks like below:

clipboard1.png

Three different solutions have been made.

 

Approach1

the idea is a temporary table lt_sms_status is used to hold all the content of the internal table to be checked, and then SORT on the temporary table and delete adjacent entries. If all the table row have the same recipient id, after the operation there must be only one entry left.

    DATA: lt_sms_status LIKE it_tab.
    lt_sms_status = it_tab.
SORT lt_sms_status BY recipient_id.
DELETE ADJACENT DUPLICATES FROM lt_sms_status COMPARING recipient_id.
IF lines( lt_sms_status ) = 1.
READ TABLE it_tab ASSIGNING FIELD-SYMBOL(<line>) INDEX 1.
        ev_rec_id = <line>-recipient_id.
ENDIF.

The drawback of approach1 is it could lead to the unnecessary high memory assumption. when lt_sms_status = it_tab is executed, no new memory allocation will not occur, until the write operation on the copied content. This behavior is documented as "Delayed Copy".

We also have concern regarding the performance of SORT and DELETE keyword when they are executed on a big internal table.

clipboard2.png

Approach2

Now we fetch the recipient id of the first row, and compare it with the left rows in the table. If most of the table rows have different recipient id, the execution has the chance to quit early. However if unfortunately all the table rows have exactly the same recipient id, this approach has to loop until last table row.

  

 DATA: lv_diff_found TYPE abap_bool VALUE abap_false.
READ TABLE it_tab ASSIGNING FIELD-SYMBOL(<line>) INDEX 1.
DATA(lv_account_id) = <line>-recipient_id.
LOOP AT it_tab ASSIGNING FIELD-SYMBOL(<ls_line>).
IF lv_account_id <> <ls_line>-recipient_id.
          lv_diff_found = abap_true.
EXIT.
ENDIF.
ENDLOOP.
IF lv_diff_found = abap_false.
       ev_rec_id = lv_account_id.
ENDIF.

Approach3

the idea is similar as approach2, now instead of manual comparison inside each LOOP, we leverage "LOOP AT XXX WHERE condition".

  

 READ TABLE it_tab ASSIGNING FIELD-SYMBOL(<line>) INDEX 1.
    LOOP AT it_tab ASSIGNING FIELD-SYMBOL(<ls_line>) WHERE recipient_id <> <line>-recipient_id.
    ENDLOOP.
IF sy-subrc <> 0.
       ev_rec_id = <line>-recipient_id.
ENDIF.

In order to measure the perfomance, we construct two kinds of test case. In the first one, we generate the internal table with N rows, each has exactly the same recipient id. And for the second, each one has different. Both are extreme kinds of scenarios. We may consider to measure the case between these two, for example for a N rows table there are 50% table rows have the same id and another 50% have difference one.

 

Performance test result

The time spent is measured in microsecond.

N = 1000

For the first test case, approach3 is most efficient. For the second test case, approach2 is the winner, as we expected.

clipboard4.png


N = 10000

clipboard5.png

N = 100000

clipboard6.png

N = 1000000

clipboard7.png

N = 5000000

clipboard8.png

Based on the performance result, we do not consider approach1 any more. For the choice between approach2 and 3, we need to investigate on the distriction of recipient id in the real world.

 

Maybe you can also share if you have better solutions?

Hello SCN,

 

So the other day I had the following requirement (I work on a SAP CRM 7.0 system): I wrote a new program in which I needed some data processing which was already coded in the subroutine of another – already existing – program. Since it concerned a pretty large piece of code, I decided not to simply copy-paste the logic but to call the subroutine from within my program like this:

 

PERFORM subroutine IN PROGRAM main_program CHANGING t_result.

 

Since the program in which I was calling the subroutine has a selection screen, and some of these parameters are used in the subroutine, I had to add an importing (USING) parameter to the subroutine which contained the values for these parameters. These values are partially supplied by the user in the selection screen of my program, and others are calculated in my program flow. So the above statement was corrected as follows:

 

PERFORM subroutine IN PROGRAM main_program

USING     t_selscr_parameters

CHANGING  t_result.

 

Now comes the tricky part . The table T_SELSCR_PARAMETERS is a table with structure RSPARAMS (so basically the standard type for any selection screen, with components SELNAME, KIND, SIGN, OPTION, LOW and HIGH). Containing records with the exact names (SELNAME) of the corresponding selection screen parameter, and – of course – the value to be transferred to the selection screen parameter (e.g. SIGN=’I’, OPTION = ‘EQ’, LOW = ‘xxx’).

 

So I added some logic to the subroutine which we are calling: a loop over SELSCR_PARAMETERS to transfer the value of each table line into the corresponding parameter from our main program’s selection screen.

For a regular parameter, I knew I could work with a field symbol of type ‘any’, and simply assign the name of the parameter (LS_RSPARAM-SELNAME) to this field symbol – let’s name him <FS_ANY>. If the assignment works (which it should, because I named the parameter records in the SELSCR_PARAMETERS table exactly the same as the parameters in the selection screen), you can transfer the value in the selection screen parameter by using the following statement:

<FS_ANY> = LS_RSPARAM-LOW.

 

But.. next to the ‘regular’ parameters, there were also some ranges (SELECT-OPTIONS) which needed to be transferred into the selection screen. Ranges are in fact separate internal tables with header line

scr-1.jpg

So you could use the same statement as for a regular parameter

ASSIGN ls_rsparam-selname TO <fs_any>.

But it would not be useful, since you need to append a structure of type RSPARAMS to your range (assigned to <FS_ANY>) and you can't do that - because <FS_ANY> is not an internal table.

 

So, you might think, I simply create a new field-symbol <fs_anytab> TYPE ANY TABLE . That way I can assign ls_rsparam-selname to <fs_anytab>, and append to that field-symbol.

 

True, syntactically this logic would not cause any problems, and your program would activate without errors. But once you step over the statement, you will get the following shortdump:

scr-2.jpg

So below you can find how I solved this issue. I searched for answers in the forum discussions here on SCN, but couldn't find it immediately. Perhaps it is out there somewhere (especially since this concept is widely used in R/3, not so much in CRM though) but I blogged about this nonetheless, hoping to save a fellow colleague some valuable time ;

 

DATA:            ref(50)         TYPE c,

                 dref            TYPE REF TO data.

FIELD-SYMBOLS:

                <fs_any>         TYPE any,

                <fs_any_1>       TYPE any,

                <fs_anytab>      TYPE ANY TABLE.

 

LOOP AT i_selscr_parameters INTO ls_rsparam.

  CASE ls_rsparam-kind.

    WHEN 'P'.

*     This is a regular parameter

      ASSIGN (ls_rsparam-selname) TO <fs_any>.

      IF <fs_any> IS ASSIGNED.

        <fs_any> = ls_rsparam-low.

        UNASSIGN <fs_any>.

      ENDIF.

    WHEN 'S'.

*     This is a range. Now ranges are in fact tables with header line,

*     and a row structure SIGN OPTION LOW HIGH.

      CONCATENATE: '(' sy-repid ')' ls_rsparam-selname '[]' INTO ref.

      CONDENSE ref NO-GAPS.

 

      ASSIGN (ref) TO <fs_anytab>.

*     So now we have the table (MAINPROGRAM)S_RANGE[] assigned to a

*     field-symbol of type ANY TABLE without dumping ;-)

      IF <fs_anytab> IS ASSIGNED.

*       We still need a structure which has the same line type as <fs_anytab>

        CREATE DATA dref LIKE LINE OF <fs_anytab>.

 

*       And now <fs_any> has our line type, we can start transferring the

*       values to the different components of the structure

        ASSIGN dref->* TO <fs_any>.

        IF <fs_any> IS ASSIGNED.

          ASSIGN COMPONENT 'SIGN' OF STRUCTURE <fs_any> TO <fs_any_1>.

          IF <fs_any_1> IS ASSIGNED.

            <fs_any_1> = ls_rsparam-sign.

            UNASSIGN <fs_any_1>.

          ENDIF.

          ASSIGN COMPONENT 'OPTION' OF STRUCTURE <fs_any> TO <fs_any_1>.

          IF <fs_any_1> IS ASSIGNED.

            <fs_any_1> = ls_rsparam-option.

            UNASSIGN <fs_any_1>.

          ENDIF.

          ASSIGN COMPONENT 'LOW' OF STRUCTURE <fs_any> TO <fs_any_1>.

          IF <fs_any_1> IS ASSIGNED.

            <fs_any_1> = ls_rsparam-low.

            UNASSIGN <fs_any_1>.

          ENDIF.

          ASSIGN COMPONENT 'HIGH' OF STRUCTURE <fs_any> TO <fs_any_1>.

          IF <fs_any_1> IS ASSIGNED.

            <fs_any_1> = ls_rsparam-high.

            UNASSIGN <fs_any_1>.

          ENDIF.

        ENDIF.

      ENDIF.

  ENDCASE.

ENDLOOP.

 

NOTE: The point of this blog is to elaborate on accessing internal table variables dynamically across programs, I certainly do not claim this was the best or most performant solution to my original requirement . Any comments on this blog however are highly appreciated!

 

Cheers,

Tom.

Hello SCN,

I just found interesting function in New ABAP Editor. It's not actually any hidden, but I think not many use it.

UPDATE: from comments it seems it is available in SAP from "EHP6 731" (screenshots are from it)

 

You can found it in context menu (right click) on code:

tooltip-1.JPG

It has different outputs on different places. For example here is what it shows when used on FM call:

tooltip-2.JPG

 

It also works on method calls, variables (showing their type) and other code...

 

What might be very helpful is that you can copy text from tooltip and use it (eg. declare variables of needed type for FM/method call).

 

Example:

Instance method: GET_DDIC_FIELD
  Returns type description for Dictionary type
IMPORTING
     Value(P_LANGU) TYPE SYLANGU Optional SY-LANGU
        Current Language
RETURNING
     Value(P_FLDDESCR) TYPE DFIES Optional
        Field Description
EXCEPTIONS
      NOT_FOUND
      NO_DDIC_TYPE

Imagine having this tooltip in switchable side panel (similar like "repository browser" in SE80) and interactive on cursor position.

No more double-clicking on methods, functions, variables etc.. to see their type, description...

Wouldn't that be amazing?

Introduction

 

Given the reluctance of the general ABAP community to use new-fangled (that is, over fifteen years old) concepts like SORTED and HASHED tables, I was hesitant to write about something a bit newer, but then I thought - what the heck, perhaps some people will find it an encouragement to use new stuff!

 

And I know this isn't that new!

 

So, we have HASHED tables, where the key is unique and the lookup time is constant for each record, and SORTED tables which mean we don't need BINARY SEARCH any more (except if we need sort descending...). For these tables, there's an index already defined to speed things up - but it's like a database table with just a primary index. Secondary keys are like additional indexes on database tables - but for internal tables.

 

I've heard it said that you should only use these if you've got tables with loads of information in. Well, so long as the data isn't being handled in a loop, I think it doesn't matter. If the data volume being processed is small, a few extra nano-seconds won't matter, and data volumes grow - so there's some future proofing in using the structures which are most efficient with large tables, right from the start.

 

Secondary keys

Here's that syntax, to refresh your memory.

 

TYPES dtype { {TYPE tabkind OF [REF TO] type}

            | {LIKE tabkind OF dobj} }

            [tabkeys]

            [INITIAL SIZE n].

 

And then tabkeys looks like this:

 

... [ WITH key ]
    [ WITH secondary_key1 ] [ WITH secondary_key2 ] ...
    [ {WITH|WITHOUT} FURTHER SECONDARY KEYS ] ... .

 

 

Additions

1. ... WITH FURTHER SECONDARY KEYS

 

2. ... WITHOUT FURTHER SECONDARY KEYS

 

Those additions, we'll forget about. They're for use when you're defining generic table types.

 

Now, for my purposes, I've got a questionnaire, with pages on it, categories of questions and questions. And I need to access it in many ways. So here's how I defined it:

 

TYPES:

     questionnaire_ty TYPE SORTED TABLE OF q_entry_ty WITH NON-UNIQUE KEY page_number cat_seq

                      WITH NON-UNIQUE SORTED KEY by_question COMPONENTS q_id

                      WITH NON-UNIQUE SORTED KEY by_cat_guid COMPONENTS cat_guid q_seq

                      WITH NON-UNIQUE SORTED KEY by_cat_text COMPONENTS cat_text

                      WITH NON-UNIQUE SORTED KEY by_cat_seq  COMPONENTS cat_seq .

 

The idea is that I can access an internal table of this type rapidly by page number, question id, category unique id (guid), category text and category sequence. Seems quite a lot, but the alternatives were to have a standard table and sort it and use binary search for each read, or not bother at all, and just put up with sequential reads.

 

Some problems

I've got the categories in my questionnaire in sequence order. So, naturally, I want to renumber them. The obvious way of doing this is

 

LOOP AT me->questionnaire ASSIGNING <entry> USING KEY by_cat_guid WHERE cat_guid EQ i_guid.

   ADD 1 TO index.

   <entry>-cat_seq = index.

ENDLOOP.

 

But there's a problem there. It dumps. And it dumps because cat_seq is part of the key by_cat_guid!

 

So, I thought, I'll delete the records, collect them and then insert them afterwards

LOOP AT me->questionnaire INTO entry USING KEY by_cat_guid WHERE cat_guid EQ i_guid.

   DELETE TABLE me->questionnaire FROM entry.

   entry-cat_seq = index.

    INSERT entry INTO TABLE renumbered.

ENDLOOP.

INSERT LINES OF renumbered INTO TABLE me->questionnaire

 

But data was still going amiss. The problem was, that the delete command deletes the entry that matches the primary key. So it was reading one entry in the LOOP AT, and deleting an entirely different entry (that matched the primary key) at the DELETE.

 

I tried the old DELETE... INDEX, but that got me nowhere. But a quick check of the syntax for DELETE gave me the hint.

 

LOOP AT me->questionnaire INTO entry USING KEY by_cat_guid WHERE cat_guid EQ i_guid.

   DELETE TABLE me->questionnaire FROM entry USING KEY by_cat_guid.

   entry-cat_seq = index.

   INSERT entry INTO TABLE renumbered.

ENDLOOP.

INSERT LINES OF renumbered USING KEY by_cat_guid INTO TABLE me->questionnaire

 

What to be aware of

With an internal table with additional keys, there are few things you really need to take care about.

 

1. You can't change a field of an entry you've ASSIGNED to, if that field is part of one of the keys

2. If you access data using one key - you really need to change it using the same key.

3. All of the usual internal table handling statements have the addition USING KEY. Sometimes it's vital - like with the DELETE example. Other times it's a matter of performance. For the INSERT LINES I could have omitted the USING KEY, and it would still work - however it is not as efficient, since I know that all my renumbered entries have the same cat_guid.

 

Final words

When new ABAP commands become available, try to use them. In my application, it probably won't make any difference. But what you don't use, you forget. Surely there will come a time when you do need additional accesses to internal tables - if you've already practiced, the next time it won't take as long.

When I talk with people about ABAP quality, the way they think of dumps always amazes me.

 

Most people believe dumps are their worst enemy. Actually they are not. Actually you're lucky if your software dumps.

 

How is that?

 

In order to understand this, we need to talk about why software dumps in the first place. Software dumps because it runs into a corrupt state it can no longer process, something virtually breaks and further processing is impossible. This results in a dump. Operation ends. Users swears.

 

While this is not good, it provides one important benefit: you now know you have a problem.

 

Ask yourself the following question: "What if my business application runs into a corrupt state and doesn't dump (right away) ?"

 

In this case your program may continue to operate for an unknown time span, potentially corrupting your persistent business data. If this happens, you won't find out for some time. If you find out, you may have a very hard time to recover from this data corruption and to trace it back to the actual programming defect that caused it.

 

Finding a problem that shows no (visible) symptoms can be extremely difficult. And its effects can be devastating once you discover them.

 

How would you - for example -  cure a disease that shows no (visible) symptoms? You couldn't. Because you don't know it's there. Until it may be too late. But if you see the symptoms, you can treat that disease and even take action to improve your health in general.

 

Under that aspect, you're lucky if your program dumps. It's like a problem that waves at you with a white flag: "Hey, here I am. Fix me!".

 

Now if you're a Padawan, you'll find the bug and you'll fix it.

 

If you're a Jedi, you'll think about a process to avoid robustness issues in the future.

 

And if you're a Jedi Master, you'll learn from every future mistake and adapt/improve your processes as new bugs come along.

 

I encourage you to see dumps as a chance, not an enemy. A chance to improve the development process. A chance to avoid similar mistakes in the future. And a reminder that robust programming matters for your business.

 

If you like to know more about avoiding robustness issues, I'd love to point you to another of my blog posts. Unfortunetaly SCN would see this as a "grounds for rejection". That's why I removed the link.

 

Dumps are not your only friend. Google is, too.

Every day you create or change ABAP class in class builder. When you activate your change, have you noticed a series of objects with several "=" in the middle part of each?

clipboard1.png

Technically speaking, an ABAP class consists of several parts. In order to figure them out, I just create a simple class with the following source code:

 

 

CLASS zcl_abap_class DEFINITION

  PUBLIC FINAL CREATE PUBLIC .

  PUBLIC SECTION.

    DATA public_attribure TYPE i .

    TYPES:BEGIN OF ty_global,

            name  TYPE string,

            score TYPE i,

          END OF ty_global.

    METHODS public .

protected section.

  data PROTECTED_ATTRIBUTE type I .

  methods PROTECTED .

  PRIVATE SECTION.

    DATA private_attribute TYPE i .

    METHODS private .

ENDCLASS.

CLASS ZCL_ABAP_CLASS IMPLEMENTATION.

  METHOD private.

  ENDMETHOD.

  method PROTECTED.

  endmethod.

  METHOD public.

  ENDMETHOD.

ENDCLASS.

I have also generated a local test class for it via SE80. After activation, look into corresponding entry in TRDIR.

clipboard2.png

The object name under column NAME could be opened via SE38. Take CCAU for example:

clipboard3.png

So CCAU contains the source code of local test class implementation:

clipboard4.png

Here below is the list of each part and its meaning:

 

Part NamePart meaning
CCAUcontains the source code of local test class implementation
CCDEFClass-Relevant Local Definitions, contains the definitions of local classes inside the public class
CCIMPIt contains the implementation for those local classes which definitions are stored in the Definitions-Include
CCMACcontains the macros of the public class
CIsource code of private section
COsource code of protected section
CUsource code of public section
CPopen it in SE38, it will automatically navigate to class builder
CTopen it in SE38, it will automatically navigate to class builder
CMXXXsource code of each method


The constant of part name is defined in type group SEOP:

clipboard7.png

If you need to get the part name of a given class via ABAP code, you can use utility class CL_OO_CLASSNAME_SERVICE. There are corresponding getter method for each kind of part defined.

 

For example, if you need to get the part name of all methods of class CL_CRM_BOL_CORE, just set breakpoint in method CL_OO_CLASSNAME_SERVICE =>GET_ALL_METHOD_INCLUDES, and open the class CL_CRM_BOL_CORE in SE24, and click "Source Code-Based" button:

clipboard8.png

Here you can find the name class part for each method are populated with one incremental step in hexadecimal.

Task essence:

My task was to set Block ID and Text automatically while end-user created purchase requisition in ME51N.

Why do it? Because, it should be approved in another SAP module (PSM-FM) and only after this it can be converted to PO.

1.jpg

                             

My solution:

First of all, look at SE11, structure MEREQ_ITEM_S_CUST_ALLOWED. There are no such fields (block id and text) there.

Create append structure.

1.jpg

Then, implement BADI ME_PROCESS_REQ_CUST, create 1 attribute and write code of 2 methods:


G_TRTYP Instance Attribute Private Type TRTYP

 

  METHOD if_ex_me_process_req_cust~open.
     me
->g_trtyp = im_trtyp .
 
ENDMETHOD.

 

  METHOD if_ex_me_process_req_cust~process_item.
   
CHECK  me->g_trtyp  = 'H' . " creatingmode
   
CHECK im_count = .

   
DATA  reqdata   TYPE mereq_item .
   
CLEAR: reqdata .
    reqdata
= im_item->get_data( ) .

    reqdata-blckd  
= '1' .
    reqdata-blckt  
= 'need to approve' .
    im_item
->set_data( reqdata ) .

   
DATA: ls_datax TYPE mereq_itemx.
   
MOVE: 'X' TO ls_datax-blckd,
         
'X' TO ls_datax-blckt.

   
im_item->set_datax( ls_datax ) .
ENDMETHOD.

 

That's all. Have a nice day

 

 

 

IMHO one purpose to write IF 1 = 0 code is to enable the cross reference of message raised in application code.

 

 

I have written several blogs to discuss the tip how to find the source code where the error message in UI is raised.

 

clipboard1.png

Most of the time you have to debug. ( Of course you can use source code scann approach to search the source code with keyword CRM_MDG_CORE 034 , the trouble is how you specify the package to be scanned, especially you are not familar with the application and you know nothing about package name at all )

 

 

The where used List on message could only identify those occurrence where the MESSAGE keyword appears in code.

clipboard2.png

clipboard3.png


For the use case in line 24 and 25 below, the where used list does not work. That is when IF 1 = 0 plays a part.

clipboard4.png

Recently in our code review meeting there are some arguments about this usage. The pros like this approach since it enable the cross reference on message raising by method call and ease the maintenance. The opposition consider that it is "unreached code" and should not appear in production code.

 

 

I just check in the system and find there are plenty of such usage in SAP standard code, also I tested that currently no ATC or extended check profile will complain about this "unreached code". Would you please kindly share your opinion of using IF 1 = 0 this way? Or do you have any concern about it?

Rainer Hübenthal

Multipart Emails

Posted by Rainer Hübenthal Mar 11, 2014

One of our customers requested data from SAP sent to him via Email. The data was a table and the customer complained that the presentation wasn't a in tabular form. Well, that is how plain text emails are working, you are sending text and the client software decides about the presentation. You do not have control over the used font and only with a monospaced font the table will be readable. A proportional font will distort the whole layout. The next try was to use tabs (0x09) but again you do not have control about the width of a tab. The result was a distorted table again.

 

Attachments like excel or PDF files were out of the question, too. So i had the idea of sending HTML emails, but they should have a fall back or at least a hint if the client software is unable to show HTML mails or was configured to only show text mails. In that case the body of the email appears empty. So i looked around and was sure to find something about multipart Emails containing a plain text part as well as a HTML part. But what a surprise, i was only able to find some fragments even in SCN and some postings on how to do it in PI with JAVA.

 

My intention was to use to the BCS classes as we have a central class wrapping all the necessary stuff in own methods. I will show the solution as own report which works fine in our environment, it's up to you to build a method for reusing the functionality

 

At least you need two tables containing the plain text and the html code. Both must be of type SOLI_TAB. That might be double work if you want to a have a fall back when only the plain text is displayed. At least there should be a hint that the main content is only visible in HTML mode. Those two tables needs to combined to a multipart/alternative document which can be achieved by the class CL_GBL_MULTIRELATED_SERVICE calling the methods set_main_text and set_main_html.

 

Instead of binding the text to the email with create_document from CL_DOCUMENT_BCS you just call the method create_from_multirelated.

 

The rest is now business as usual using the BCS classes to compose an email and send it to the recipients.

 

It was really easier as in my first thoughts and if i miss someones blog i apologize, but i really have bad search skills

 

As the source is really short i just past it here.

 

 

 

*&---------------------------------------------------------------------*
*& Report  Z_TEST_EMAIL
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*
REPORT  z_test_email.
DATA:
  subject               TYPE so_obj_des,
  it_body_text         TYPE soli_tab,
  it_body_html         TYPE soli_tab,
  from                 TYPE ad_smtpadr,
  to                   TYPE ad_smtpadr.
DATA:
  document             TYPE REF TO cl_document_bcs,
  request              TYPE REF TO cl_bcs,
  mime_helper          TYPE REF TO cl_gbt_multirelated_service,
  sender               TYPE REF TO if_sender_bcs,
  recipient            TYPE REF TO if_recipient_bcs.
to      = 'receiver@example.org'.
from    = 'sender@example.org'.
subject = 'Tabular data'.
APPEND 'Hello World'           TO it_body_text.
APPEND 'Column 1     Column 2' TO it_body_text.
APPEND 'a            b'        TO it_body_text.
APPEND 'xyz          test'     TO it_body_text.
APPEND '4711         0815'     TO it_body_text.
APPEND '<html>'                      TO it_body_html.
APPEND '<head>'                      TO it_body_html.
APPEND '<title>MY HTML part</title>' TO it_body_html.
APPEND '</head>'                     TO it_body_html.
APPEND '<body>'                      TO it_body_html.
APPEND '<h1>Hello World!</h1>'       TO it_body_html.
APPEND '<table border="1">'          TO it_body_html.
APPEND '<tr><td>Column 1</td><td>Column 2</td></tr>' TO it_body_html.
APPEND '<tr><td>a</td><td>b</td></tr>'               TO it_body_html.
APPEND '<tr><td>xyz</td><td>test</td></tr>'          TO it_body_html.
APPEND '<tr><td>4711</td><td>0815</td></tr>'         TO it_body_html.
APPEND '</table>' TO it_body_html.
APPEND '</body>'  TO it_body_html.
APPEND '</html>'  TO it_body_html.
CREATE OBJECT mime_helper.
mime_helper->set_main_text( content = it_body_text ).
mime_helper->set_main_html( content = it_body_html ).
document = cl_document_bcs=>create_from_multirelated(
  i_subject          = subject
  i_multirel_service = mime_helper ).
request = cl_bcs=>create_persistent( ).
request->set_document( document ).
IF from IS NOT INITIAL.
  sender = cl_cam_address_bcs=>create_internet_address( from ).
ELSE.
  sender = cl_sapuser_bcs=>create( sy-uname ).
ENDIF.
request->set_sender( sender ).
recipient = cl_cam_address_bcs=>create_internet_address( to ).
request->add_recipient( EXPORTING i_recipient = recipient ).
request->set_send_immediately( 'X' ).
IF request->send( i_with_error_screen = 'X' ) = 'X'.
  WRITE:/ 'Email sent succesfully'.
ELSE.
  WRITE:/ ' Error sending email'.
ENDIF.
COMMIT WORK.

I was searching for reports which can give up a report of Vendor classification used in vendore master.This blog is about getting the report for vendor classification in the Vendor master data. Also you can use this in many places to build your own logic.

 

There are 2 ways to do this .

1. We can use a standard BAPI and then call it in a loop by extracting data from KSSK.

     So as step 1 get the list of Vendors who are having valid vendor characteristics from table KSSK.

     Now loop thourhg thie table and then pass value to the BAPI 'BAPI_OBJCL_GETDETAIL'.

 

 

BAPI.JPG

Pass the values like above.

Object Key is your vendor number

Object Table will be LFA1.

CLASSNUM -. pass from KSSK.

Class type also from KSSK or mostly this will be 010 only.

 

You will get the result as below.

bapiresult.JPG

 

2. Build report using the below tables.

Table:

KSSK - Pass vendor number in Object (add preceding Zeros)  and class type '010'.

KLAH - This will give the class

KSML - give give more detals of vendor classification

AUSP - Here pass the vendor number as the Object and the Internal char. from the previus table KSML and you will get the details.

 

 

Regards,

Vijay V

Actions

Filter Blog

By author:
By date:
By tag: