Saturday, 4 August 2018

Getting comfortable using the Object-Oriented design model with ABAP – Part 6

To recap from the preceding blog, we took a program having virtually identical classes and removed the unnecessary duplication. Here is the source code as we left it in the previous blog:

report.
interface data_exchangeable.
  types          : row_counter    type n length 02.
  types          : email_recipient
                                  type adr6-smtp_addr.
endinterface.
class flight_records_retriever         definition
                                       abstract
                                       final.
  public section.
    types        : record_list    type standard table of sflight.
    class-methods: retrieve_records
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                     exporting
                       record_stack
                         type flight_records_retriever=>record_list
                 .
endclass.
class flight_records_retriever         implementation.
  method retrieve_records.
    select *
      into table record_stack
      from sflight
             up to row_count rows.
  endmethod.
endclass.
class carrier_records_retriever        definition
                                       abstract
                                       final.
  public section.
    types        : record_list    type standard table of scarr.
    class-methods: retrieve_records
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                     exporting
                       record_stack
                         type carrier_records_retriever=>record_list
                 .
endclass.
class carrier_records_retriever        implementation.
  method retrieve_records.
    select *
      into table record_stack
      from scarr
             up to row_count rows.
  endmethod.
endclass.
class booking_records_retriever        definition
                                       abstract
                                       final.
  public section.
    types        : record_list    type standard table of sbook.
    class-methods: retrieve_records
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                     exporting
                       record_stack
                         type booking_records_retriever=>record_list
                 .
endclass.
class booking_records_retriever        implementation.
  method retrieve_records.
    select *
      into table record_stack
      from sbook
             up to row_count rows.
  endmethod.
endclass.
class excel_spreadsheet_manager        definition
                                       final.
  public section.
    methods      : copy_table_to_excel_worksheet
                     importing
                       source_stack
                         type standard table
                       source_description
                         type string
                     raising
                       zcx_excel
                 , send_excel_via_email
                     importing
                       recipient
                         type data_exchangeable=>email_recipient
                 .
  private section.
    data         : excel          type ref to zcl_excel.
endclass.
class excel_spreadsheet_manager        implementation.
  method copy_table_to_excel_worksheet.
    constants    : first_column   type char1     value 'A'
                 .
    data         : worksheet      type ref to zcl_excel_worksheet
                 , worksheet_title
                                  type zexcel_sheet_title
                 , table_settings type zexcel_s_table_settings
                 .
    table_settings-table_style    = zcl_excel_table=>builtinstyle_medium2.
    table_settings-show_row_stripes
                                  = abap_true.
    table_settings-nofilters      = abap_true.
    table_settings-top_left_column
                                  = first_column.
    table_settings-top_left_row   = 01.
    if excel is not bound.
      create object excel.
      worksheet                   = excel->get_active_worksheet( ).
    else.
      worksheet                   = excel->add_new_worksheet( ).
    endif.
    worksheet_title               = source_description.
    worksheet->set_title( worksheet_title ).
    worksheet->bind_table(
      ip_table                    = source_stack
      is_table_settings           = table_settings
      ).
  endmethod.
  method send_excel_via_email.
    constants    : excel_file_type
                                 type string value '.xlsx'
                 , file_name_parameter
                                  type string value '&SO_FILENAME='
                 .
    data         : excel_writer   type ref to zif_excel_writer
                 , excel_as_xstring
                                  type xstring
                 , excel_as_xstring_bytecount
                                  type i
                 , excel_as_solix_stack
                                  type solix_tab
                 , mail_send_request
                                  type ref to cl_bcs
                 , mail_message   type ref to cl_document_bcs
                 , any_bcs_exception
                                  type ref to cx_bcs
                 , diagnostic     type string
                 , mail_title     type so_obj_des
                 , mail_text_stack
                                  type soli_tab
                 , mail_text_entry
                                  like line
                                    of mail_text_stack
                 , mail_attachment_subject
                                  type sood-objdes
                 , mail_attachment_bytecount
                                  type sood-objlen
                 , mail_attachment_header_stack
                                  type soli_tab
                 , mail_attachment_header_entry
                                  like line of mail_attachment_header_stack
                 , internet_email_recipient
                                  type ref to if_recipient_bcs
                 , successful_send
                                  type abap_bool
                 , file_name      type string
                 .
    " Much of the code here was lifted from method send_mail of
    " class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
    concatenate sy-repid          " this report name
                sy-datum          " current date
                sy-uzeit          " current time
                excel_file_type   " excel file extension
           into file_name.
    mail_title                    = file_name.
    mail_attachment_subject       = file_name.
    mail_text_entry               = 'See attachment'.
    append mail_text_entry
        to mail_text_stack.
    concatenate file_name_parameter
                file_name
           into mail_attachment_header_entry.
    append mail_attachment_header_entry
        to mail_attachment_header_stack.
    create object excel_writer type zcl_excel_writer_2007.
    excel_as_xstring              = excel_writer->write_file( excel ).
    excel_as_solix_stack          = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
    excel_as_xstring_bytecount    = xstrlen( excel_as_xstring ).
    mail_attachment_bytecount     = excel_as_xstring_bytecount.
    try.
      mail_message                = cl_document_bcs=>create_document(
                                      i_type    = 'RAW' "#EC NOTEXT
                                      i_text    = mail_text_stack
                                      i_subject = mail_title
                                      ).
      mail_message->add_attachment(
        i_attachment_type         = 'XLS' "#EC NOTEXT
        i_attachment_subject      = mail_attachment_subject
        i_attachment_size         = mail_attachment_bytecount
        i_att_content_hex         = excel_as_solix_stack
        i_attachment_header       = mail_attachment_header_stack
        ).
      mail_send_request           = cl_bcs=>create_persistent( ).
      mail_send_request->set_document( mail_message ).
      internet_email_recipient    = cl_cam_address_bcs=>create_internet_address( recipient ).
      mail_send_request->add_recipient( internet_email_recipient ).
      successful_send             = mail_send_request->send( ).
      commit work.
      if successful_send eq abap_false.
        message i500(sbcoms) with recipient.
      else.
        message s022(so).
        message 'Document ready to be sent - Check SOST' type 'I'.
      endif.
    catch cx_bcs into any_bcs_exception.
      diagnostic                  = any_bcs_exception->if_message~get_text( ).
      message diagnostic type 'I'.
    endtry.
  endmethod.
endclass.
class report                           definition
                                       final.
  public section.
    methods      : present_report
                     changing
                       record_stack
                         type standard table
                 .
endclass.
class report                           implementation.
  method present_report.
    data         : alv_report     type ref to cl_salv_table
                 .
    try.
      call method cl_salv_table=>factory
        importing
          r_salv_table            = alv_report
        changing
          t_table                 = record_stack.
    catch cx_salv_msg.
      return.
    endtry.
    alv_report->display( ).
  endmethod.
endclass.
class process_driver                   definition
                                       abstract
                                       final.
  public section.
    class-methods: drive_process
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                       recipient
                         type data_exchangeable=>email_recipient
                 .
endclass.
class process_driver                   implementation.
  method drive_process.
    data         : report         type ref to report
                 , excel_spreadsheet_manager
                                  type ref to excel_spreadsheet_manager
                 , flight_stack   type flight_records_retriever=>record_list
                 , carrier_stack  type carrier_records_retriever=>record_list
                 , booking_stack  type booking_records_retriever=>record_list
                 .
    create object: report
                 , excel_spreadsheet_manager
                 .
    call method flight_records_retriever=>retrieve_records
      exporting
        row_count                 = row_count
      importing
        record_stack              = flight_stack.
    call method carrier_records_retriever=>retrieve_records
      exporting
        row_count                 = row_count
      importing
        record_stack              = carrier_stack.
    call method booking_records_retriever=>retrieve_records
      exporting
        row_count                 = row_count
      importing
        record_stack              = booking_stack.
    call method report->present_report changing record_stack = flight_stack.
    call method report->present_report changing record_stack = carrier_stack.
    call method report->present_report changing record_stack = booking_stack.
    try.
      call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
        exporting
          source_stack            = flight_stack
          source_description      = 'Flights'.
      call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
        exporting
          source_stack            = carrier_stack
          source_description      = 'Carriers'.
      call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
        exporting
          source_stack            = booking_stack
          source_description      = 'Bookings'.
    catch zcx_excel ##NO_HANDLER.
    endtry.
    call method excel_spreadsheet_manager->send_excel_via_email exporting recipient = recipient.
  endmethod.
endclass.
class email_address_resolver           definition
                                       abstract
                                       final.
  public section.
    class-methods: resolve_email_address
                     importing
                       userid
                         type syuname
                     exporting
                       email_address
                         type data_exchangeable=>email_recipient
                 .
endclass.
class email_address_resolver           implementation.
  method resolve_email_address.
    select single smtp_addr
      into email_address
      from adr6 ##WARN_OK
             inner join
           usr21 on usr21~persnumber eq adr6~persnumber
     where usr21~bname            eq userid.
  endmethod.
endclass.
  parameters     : rowcount       type data_exchangeable=>row_counter.
  parameters     : recipien       type data_exchangeable=>email_recipient.
initialization.
  call method email_address_resolver=>resolve_email_address
    exporting
      userid                      = sy-uname
    importing
      email_address               = recipien.
start-of-selection.
  call method process_driver=>drive_process
    exporting
      row_count                   = rowcount
      recipient                   = recipien.

Open your favorite ABAP editor, make a copy of this ABAP program and follow along as we apply changes to eliminate all static classes. For those who do not have ABAP2XLSX available at their site, replace class excel_spreadsheet_manager with the following source code:

class excel_spreadsheet_manager        definition
                                       final.
  public section.
    methods      : copy_table_to_excel_worksheet
                     importing
                       source_stack
                         type standard table
                       source_description
                         type string
                     raising
                       zcx_excel
                 , send_excel_via_email
                     importing
                       recipient
                         type data_exchangeable=>email_recipient
                 .
  private section.
    data         : excel          type string.
endclass.
class excel_spreadsheet_manager        implementation.
  method copy_table_to_excel_worksheet.
    data         : source_stack_lines type string.
    describe table source_stack lines source_stack_lines.
    concatenate excel
                source_stack_lines
                source_description
           into excel separated by space.
  endmethod.
  method send_excel_via_email.
    data         : message        type string.
    concatenate excel
                'would be sent to'
                recipient
           into message separated by space.
    message message type 'I'.
  endmethod.
endclass.

and include the following local exception class definition after the report statement:

class zcx_excel                        definition
                                       inheriting from cx_static_check.
endclass.


Motivation



As noted in one of the previous blogs in this series, static classes offer ABAP programmers a stepping stone toward becoming more familiar with object-oriented principles due to their similarity with ABAP function groups, specifically:

SAP ABAP Certification, SAP ABAP Learning, SAP ABAP Tutorial and Material, SAP ABAP Study Materials

◈ Function groups and static classes both offer only a single copy of the attributes and data fields they define.
◈ Function groups and static classes both are loaded into storage with the first reference to one of its members.
◈ Methods of static classes can be invoked via the class name, similar to the way function modules can be invoked simply via the function module name.

Though static classes are easier to grasp for those new to OO concepts, they have limitations to what they can offer with a truly object-oriented design.

Rule 5.3: Do Not Use Static Classes

Preferably use objects instead of static classes. If you don’t want to have a multiple instantiation, you can use singletons.

The book goes on to provide further details about why static classes should be avoided, amongst them:

◈ Explicit object creation is essential for object-oriented programming.
◈ The static constructor offers only limited functionality.
◈ Static classes lack support for polymorphism.

Understanding multiple instantiations


Let’s take a moment to understand the phrase “don’t want to have a multiple instantiation” appearing in the paragraph accompanying Rule 5.3. Static classes have no concept of instantiation. Any attributes defined for a static class will exist only once in storage while the program is executing. In contrast, for a class offering instantiation it is possible to have multiple instances of the class existing simultaneously while the program is executing, in each case the class instance having been created by a create object statement (or some variation of this statement, such as the operator new).

Let’s see this with an example. Suppose we have a static class car as defined below:

class car                              definition
                                       abstract
                                       final.
  public section.
    class-methods: set_color
                     importing
                       color
                         type string
                 , set_year
                     importing
                       year
                         type string
                 .
  private section.
    class-data   : color          type string
                 , year           type string
                 .
endclass.
class car                              implementation.
  method set_color.
    car=>color                    = color.
  endmethod.
  method set_year.
    car=>year                     = year.
  endmethod.
endclass.

Notice that the two methods, set_color and set_year, are static methods since they are defined with a class-methods statement. Notice also that the two attributes, color and year, are static attributes since they are defined with a class-data statement. In fact, all attributes and methods of this class are statically defined, rendering this class a static class. It is not necessary – indeed, it is incorrect – to instantiate such a class; we simply can begin to use the class in our code, as in:

call method car=>set_color exporting color = ‘silver’.
call method car=>set_year exporting year = ‘2010’.

The attributes color and year of class car exist only once in storage for the duration of the program in which these statements appear.

The problem with class car being defined as a static class is that we can use it to track the color and year of only one car. If we need to track color and year for a multitude of car objects, then we would need to change the class such that a) it offers instantiation, and b) we create multiple instances of the class to represent the multiple instances of cars we want to track. Here is how the car class would need to be changed to accommodate multiple instantiations:

class car                              definition
                                       final.
  public section.
    methods      : set_color
                     importing
                       color
                         type string
                 , set_year
                     importing
                       year
                         type string
                 .
  private section.
    data         : color          type string
                 , year           type string
                 .
endclass.
class car                              implementation.
  method set_color.
    me->color                     = color.
  endmethod.
  method set_year.
    me->year                      = year.
  endmethod.
endclass.

The differences between the static version and the version above are:

◈ The qualifier abstract was removed from the class definition statement.
◈ The class-methods statement was changed to a methods statement.
◈ The class-data statement was changed to a data statement.
◈ The two methods refer to their respective attributes using the instance self-reference (me) instead of the class name and using the instance selector (->) instead of the class selector (=>).

Now we are able to create multiple instances of cars and keep track their respective colors and years, as in:

  data           : car_01         type ref to car
                 , car_02         type ref to car
                 , car_03         type ref to car
                 .
  create object: car_01
               , car_02
               , car_03
               .
  call method car_01->set_color exporting color = 'silver'.
  call method car_01->set_year  exporting year  = '2010'.
  call method car_02->set_color exporting color = 'maroon'.
  call method car_02->set_year  exporting year  = '2014'.
  call method car_03->set_color exporting color = 'green'.
  call method car_03->set_year  exporting year  = '2015'.

Because the car class is no longer static, its attributes color and year exist once per instance of class car instead of only once per program execution.

Singletons


Let’s also take a moment to understand the phrase “you can use singletons” appearing in the paragraph accompanying Rule 5.3. A singleton is an object-oriented design pattern guaranteeing only a single instance of a class will exist during program execution.  It is one of the 23 object-oriented design patterns covered in the book Design Patterns: Elements of Reusable Object-Oriented Software (Gamma, Helms, Johnson, Vlissides).

An instance class does not require multiple instances to exist during program execution, it merely enables this capability. As per one of the previous blogs in this series, when we converted class excel_spreadsheet_manager from a static class to an instance class, we gained the capability to instantiate this class more than once during program execution. However, you’ll notice that our example program instantiates this class only once. Indeed, for all classes in our example program facilitating multiple instantiation, we have never taken advantage to instantiate any one of them more than once. This is because we need only a single instance of these classes to handle our processing requirements. Since for these classes we “don’t want to have a multiple instantiation”, as stated in the paragraph accompanying Rule 5.3, then we “can use singletons” to insure there is one and only one instance of these classes during execution.

Converting our first class from static to Singleton


To get started, let’s take static class flight_records_retriever and convert it into a singleton class by making the following changes:

◈ Change its class definition statement, removing the qualifier abstract and adding the qualifier create private following the qualifier final.
◈ Include in its public section the following statements preceding the definition of method retrieve_records:

    class-data   : singleton      type ref to flight_records_retriever read-only.
    class-methods: class_constructor.

◈ Change method retrieve_records from a static method to an instance method by replacing “class-methods” with “methods”.
◈ Include after the class implementation statement the following static constructor method:

  method class_constructor.
    create object flight_records_retriever=>singleton.
  endmethod.

Upon completing these changes, class flight_records_retriever should look like this:

class flight_records_retriever         definition
                                       final
                                       create private.
  public section.
    types        : record_list    type standard table of sflight.
    class-data   : singleton      type ref to flight_records_retriever read-only.
    class-methods: class_constructor.
    methods      : retrieve_records
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                     exporting
                       record_stack
                         type flight_records_retriever=>record_list
                 .
endclass.
class flight_records_retriever         implementation.
  method class_constructor.
    create object flight_records_retriever=>singleton.
  endmethod.
  method retrieve_records.
    select *
      into table record_stack
      from sflight
             up to row_count rows.
  endmethod.
endclass.

At this point a syntax check will fail on the call to method retrieve_records of class flight_records_retriever. Correct this by changing the statement:

    call method flight_records_retriever=>retrieve_records
to

    call method flight_records_retriever=>singleton->retrieve_records

Afterward a syntax check will pass.

Let’s review what we’ve done. Due to including the qualifier create private on the class definition, we now have a class that cannot be instantiated other than by this class itself, and indeed the instantiation of this class occurs via the static constructor (class_constructor), which is invoked immediately upon the first reference to this class in the code, placing a reference to the instance of class flight_records_retriever it creates into the new static attribute called singleton. Notice that this class now has both static and instance members. There will be only one instance of this class ever created because the instantiation occurs during the static constructor, which will be executed exactly once per program execution. To access this single instance, we can refer to the public static attribute flight_records_retriever=>singleton, an attribute we can access but cannot change due to the use of the qualifier read-only on its definition.

Presto. A former static class now exists as a singleton, conforming with Rule 5.3.

Further explanation of Singleton


There are other ways to define a class as a singleton than how it is shown in this example but most ways are simply variations on a theme. With the variation we are using here, we changed the only static member the class formerly had – the static method retrieve_records – into an instance method. Having done that, we effectively removed all static members from the class, but then we went ahead and added two new static members to it: a public static attribute called singleton and a static constructor. ABAP provides the reserved method name “class_constructor” to designate a static constructor and it always must be defined with public visibility. The static attribute we added – singleton – is a reference variable to an instance of the very same class in which it is defined. Notice that the implementation of the static constructor consists of only one statement creating an instance of this class and placing the reference to it into the new static attribute we added.

While the method name class_constructor is required when defining a static constructor, the name of the static attribute into which the instance reference is placed can be any name conforming to rules of variable naming. I prefer to call this field “singleton” to reinforce the role the class plays since any programmer subsequently maintaining this program can immediately recognize the design pattern this class employs. Notice also that this new static attribute is accompanied by the “read-only” qualifier. This means that any external entity has read access to this static attribute, but only the class itself is able to change its value, and the only time this value is changed by this class is during the execution of its static constructor.

Some may find it difficult to understand how the statement

call method flight_records_retriever=>singleton->retrieve_records ...

is capable of invoking an instance of a class that we did not explicitly create prior to reaching this statement. Indeed, it may be difficult even to grasp the concept of how an instance of a class can be created at all when the class definition indicates, via the qualifier create private, that only the class itself is capable of creating instances of the class. Let’s further examine both of these concepts.

Understanding the concept of instantiating a Singleton


Until we have a better understanding of how this works, we might find ourselves wrestling with the counterintuitive quandary expressed by this question: How can instances of a class be created when only the class itself is capable of creating such instances? The solution to this enigma is that the class needs to be composed of both static and instance members. When the definition of a class indicates create private, it is the static members of a class that facilitate creating instances of the very same class since the static members of a class are available during program execution even when no instances of the class exist.  Indeed, a class defined as a singleton typically contains only as many static members as necessary to manage access to its singleton instance.

Understanding the mechanics of invoking a Singleton


Now let’s examine how the statement

call method flight_records_retriever=>singleton->retrieve_records ...

enables us to access the singleton instance of the flight_records_retriever class before we even caused an instance to be explicitly created prior to reaching this statement. Indeed, here we are accessing an instance method of a class for which our program does not even provide a reference to a corresponding instance. How is this even possible?

Let’s first notice that the string following “call method” is composed of the name of the class (flight_records_retriever), followed by a class component selector (=>), followed by the name of a public static attribute of the class (singleton), followed by an instance component selector (->), followed by the name of a public instance method (retrieve_records), all with no intervening spaces between them, effectively representing a compound reference due to the use of more than one component selector in the name.

Although the runtime environment does not behave exactly this way, it is helpful to conceive the execution of this statement in the following way:

1. After moving past the “call method” words starting this statement, the statement parser facilitating program execution reaches the portion of the statement operand containing “flight_records_retriever=>”. Recognizing that the class selector => denotes a reference to a class name, the statement parser at that point requests the runtime environment to load into storage, if not already loaded, the class name that precedes the class selector, in this case flight_records_retriever. Indeed, since this is the first reference to this class, this will be the point at which the runtime environment loads class flight_records_retriever. At that moment, its static constructor will be invoked, which, according to the implementation for this method, will initialize static attribute singleton with a reference to an object of type flight_records_retriever.

2. Next, the statement parser will reach the portion of the statement operand containing “singleton->”. Recognizing that the instance selector -> denotes an instance reference, the reference field preceding the instance selector, in this case singleton, defined as a static attribute of the flight_records_retriever class, is inspected to determine the type of the instance it holds, in this case a reference to a flight_records_retriever instance. By this time, a corresponding object of type flight_records_retriever already will have been instantiated into this static attribute through the completion of the static constructor method, invoked during the parsing of the previous portion of this statement operand.

3. Next, the statement parser will reach the portion of the statement operand containing “retrieve_records”, which it will recognize as the instance method of class flight_records_retriever to be invoked through the instance reference held in publicly available static reference field singleton and will parse the remainder of the statement to resolve the parameters to be exchanged with this instance method.

Accordingly, using this variation of a Singleton, we are able to invoke the instance methods of the singleton object directly through the public static attribute provided by the class itself.  If this very same statement were to be encountered later during the execution of this program, it would go through the same 3 steps noted above, but this time the runtime environment would not need
to load the class into storage since that already had been done, and as a consequence the static constructor of the class would not be executed again.

Converting other classes from static to Singleton


Next, make the same relative changes to classes carrier_records_retriever and booking_records_retriever as were made for class flight_records_retriever. Afterward a syntax check still will pass.

Then make the same relative changes to classes process_driver and email_address_resolver. Afterward a syntax check still will pass.

Executing the program at this point should prove that it still works as before.

Summary


We’ve made enough changes for now and the final image of the code looks like this:

report.
interface data_exchangeable.
  types          : row_counter    type n length 02.
  types          : email_recipient
                                  type adr6-smtp_addr.
endinterface.
class flight_records_retriever         definition
                                       final
                                       create private.
  public section.
    types        : record_list    type standard table of sflight.
    class-data   : singleton      type ref to flight_records_retriever read-only.
    class-methods: class_constructor.
    methods      : retrieve_records
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                     exporting
                       record_stack
                         type flight_records_retriever=>record_list
                 .
endclass.
class flight_records_retriever         implementation.
  method class_constructor.
    create object flight_records_retriever=>singleton.
  endmethod.
  method retrieve_records.
    select *
      into table record_stack
      from sflight
             up to row_count rows.
  endmethod.
endclass.
class carrier_records_retriever        definition
                                       final
                                       create private.
  public section.
    types        : record_list    type standard table of scarr.
    class-data   : singleton      type ref to carrier_records_retriever read-only.
    class-methods: class_constructor.
    methods      : retrieve_records
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                     exporting
                       record_stack
                         type carrier_records_retriever=>record_list
                 .
endclass.
class carrier_records_retriever        implementation.
  method class_constructor.
    create object carrier_records_retriever=>singleton.
  endmethod.
  method retrieve_records.
    select *
      into table record_stack
      from scarr
             up to row_count rows.
  endmethod.
endclass.
class booking_records_retriever        definition
                                       final
                                       create private.
  public section.
    types        : record_list    type standard table of sbook.
    class-data   : singleton      type ref to booking_records_retriever read-only.
    class-methods: class_constructor.
    methods      : retrieve_records
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                     exporting
                       record_stack
                         type booking_records_retriever=>record_list
                 .
endclass.
class booking_records_retriever        implementation.
  method class_constructor.
    create object booking_records_retriever=>singleton.
  endmethod.
  method retrieve_records.
    select *
      into table record_stack
      from sbook
             up to row_count rows.
  endmethod.
endclass.
class excel_spreadsheet_manager        definition
                                       final.
  public section.
    methods      : copy_table_to_excel_worksheet
                     importing
                       source_stack
                         type standard table
                       source_description
                         type string
                     raising
                       zcx_excel
                 , send_excel_via_email
                     importing
                       recipient
                         type data_exchangeable=>email_recipient
                 .
  private section.
    data         : excel          type ref to zcl_excel.
endclass.
class excel_spreadsheet_manager        implementation.
  method copy_table_to_excel_worksheet.
    constants    : first_column   type char1     value 'A'
                 .
    data         : worksheet      type ref to zcl_excel_worksheet
                 , worksheet_title
                                  type zexcel_sheet_title
                 , table_settings type zexcel_s_table_settings
                 .
    table_settings-table_style    = zcl_excel_table=>builtinstyle_medium2.
    table_settings-show_row_stripes
                                  = abap_true.
    table_settings-nofilters      = abap_true.
    table_settings-top_left_column
                                  = first_column.
    table_settings-top_left_row   = 01.
    if excel is not bound.
      create object excel.
      worksheet                   = excel->get_active_worksheet( ).
    else.
      worksheet                   = excel->add_new_worksheet( ).
    endif.
    worksheet_title               = source_description.
    worksheet->set_title( worksheet_title ).
    worksheet->bind_table(
      ip_table                    = source_stack
      is_table_settings           = table_settings
      ).
  endmethod.
  method send_excel_via_email.
    constants    : excel_file_type
                                 type string value '.xlsx'
                 , file_name_parameter
                                  type string value '&SO_FILENAME='
                 .
    data         : excel_writer   type ref to zif_excel_writer
                 , excel_as_xstring
                                  type xstring
                 , excel_as_xstring_bytecount
                                  type i
                 , excel_as_solix_stack
                                  type solix_tab
                 , mail_send_request
                                  type ref to cl_bcs
                 , mail_message   type ref to cl_document_bcs
                 , any_bcs_exception
                                  type ref to cx_bcs
                 , diagnostic     type string
                 , mail_title     type so_obj_des
                 , mail_text_stack
                                  type soli_tab
                 , mail_text_entry
                                  like line
                                    of mail_text_stack
                 , mail_attachment_subject
                                  type sood-objdes
                 , mail_attachment_bytecount
                                  type sood-objlen
                 , mail_attachment_header_stack
                                  type soli_tab
                 , mail_attachment_header_entry
                                  like line of mail_attachment_header_stack
                 , internet_email_recipient
                                  type ref to if_recipient_bcs
                 , successful_send
                                  type abap_bool
                 , file_name      type string
                 .
    " Much of the code here was lifted from method send_mail of
    " class lcl_ouput, defined in object ZDEMO_EXCEL_OUTPUTOPT_INCL:
    concatenate sy-repid          " this report name
                sy-datum          " current date
                sy-uzeit          " current time
                excel_file_type   " excel file extension
           into file_name.
    mail_title                    = file_name.
    mail_attachment_subject       = file_name.
    mail_text_entry               = 'See attachment'.
    append mail_text_entry
        to mail_text_stack.
    concatenate file_name_parameter
                file_name
           into mail_attachment_header_entry.
    append mail_attachment_header_entry
        to mail_attachment_header_stack.
    create object excel_writer type zcl_excel_writer_2007.
    excel_as_xstring              = excel_writer->write_file( excel ).
    excel_as_solix_stack          = cl_bcs_convert=>xstring_to_solix( iv_xstring = excel_as_xstring ).
    excel_as_xstring_bytecount    = xstrlen( excel_as_xstring ).
    mail_attachment_bytecount     = excel_as_xstring_bytecount.
    try.
      mail_message                = cl_document_bcs=>create_document(
                                      i_type    = 'RAW' "#EC NOTEXT
                                      i_text    = mail_text_stack
                                      i_subject = mail_title
                                      ).
      mail_message->add_attachment(
        i_attachment_type         = 'XLS' "#EC NOTEXT
        i_attachment_subject      = mail_attachment_subject
        i_attachment_size         = mail_attachment_bytecount
        i_att_content_hex         = excel_as_solix_stack
        i_attachment_header       = mail_attachment_header_stack
        ).
      mail_send_request           = cl_bcs=>create_persistent( ).
      mail_send_request->set_document( mail_message ).
      internet_email_recipient    = cl_cam_address_bcs=>create_internet_address( recipient ).
      mail_send_request->add_recipient( internet_email_recipient ).
      successful_send             = mail_send_request->send( ).
      commit work.
      if successful_send eq abap_false.
        message i500(sbcoms) with recipient.
      else.
        message s022(so).
        message 'Document ready to be sent - Check SOST' type 'I'.
      endif.
    catch cx_bcs into any_bcs_exception.
      diagnostic                  = any_bcs_exception->if_message~get_text( ).
      message diagnostic type 'I'.
    endtry.
  endmethod.
endclass.
class report                           definition
                                       final.
  public section.
    methods      : present_report
                     changing
                       record_stack
                         type standard table
                 .
endclass.
class report                           implementation.
  method present_report.
    data         : alv_report     type ref to cl_salv_table
                 .
    try.
      call method cl_salv_table=>factory
        importing
          r_salv_table            = alv_report
        changing
          t_table                 = record_stack.
    catch cx_salv_msg.
      return.
    endtry.
    alv_report->display( ).
  endmethod.
endclass.
class process_driver                   definition
                                       final
                                       create private.
  public section.
    class-data   : singleton      type ref to process_driver read-only.
    class-methods: class_constructor.
    methods      : drive_process
                     importing
                       row_count
                         type data_exchangeable=>row_counter
                       recipient
                         type data_exchangeable=>email_recipient
                 .
endclass.
class process_driver                   implementation.
  method class_constructor.
    create object process_driver=>singleton.
  endmethod.
  method drive_process.
    data         : report         type ref to report
                 , excel_spreadsheet_manager
                                  type ref to excel_spreadsheet_manager
                 , flight_stack   type flight_records_retriever=>record_list
                 , carrier_stack  type carrier_records_retriever=>record_list
                 , booking_stack  type booking_records_retriever=>record_list
                 .
    create object: report
                 , excel_spreadsheet_manager
                 .
    call method flight_records_retriever=>singleton->retrieve_records
      exporting
        row_count                 = row_count
      importing
        record_stack              = flight_stack.
    call method carrier_records_retriever=>singleton->retrieve_records
      exporting
        row_count                 = row_count
      importing
        record_stack              = carrier_stack.
    call method booking_records_retriever=>singleton->retrieve_records
      exporting
        row_count                 = row_count
      importing
        record_stack              = booking_stack.
    call method report->present_report changing record_stack = flight_stack.
    call method report->present_report changing record_stack = carrier_stack.
    call method report->present_report changing record_stack = booking_stack.
    try.
      call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
        exporting
          source_stack            = flight_stack
          source_description      = 'Flights'.
      call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
        exporting
          source_stack            = carrier_stack
          source_description      = 'Carriers'.
      call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
        exporting
          source_stack            = booking_stack
          source_description      = 'Bookings'.
    catch zcx_excel ##NO_HANDLER.
    endtry.
    call method excel_spreadsheet_manager->send_excel_via_email exporting recipient = recipient.
  endmethod.
endclass.
class email_address_resolver           definition
                                       final
                                       create private.
  public section.
    class-data   : singleton      type ref to email_address_resolver read-only.
    class-methods: class_constructor.
    methods      : resolve_email_address
                     importing
                       userid
                         type syuname
                     exporting
                       email_address
                         type data_exchangeable=>email_recipient
                 .
endclass.
class email_address_resolver           implementation.
  method class_constructor.
    create object email_address_resolver=>singleton.
  endmethod.
  method resolve_email_address.
    select single smtp_addr
      into email_address
      from adr6 ##WARN_OK
             inner join
           usr21 on usr21~persnumber eq adr6~persnumber
     where usr21~bname            eq userid.
  endmethod.
endclass.
  parameters     : rowcount       type data_exchangeable=>row_counter.
  parameters     : recipien       type data_exchangeable=>email_recipient.
initialization.
  call method email_address_resolver=>singleton->resolve_email_address
    exporting
      userid                      = sy-uname
    importing
      email_address               = recipien.
start-of-selection.
  call method process_driver=>singleton->drive_process
    exporting
      row_count                   = rowcount
      recipient                   = recipien.

We no longer have any static classes remaining in the program. The entire program is now in conformance with Rule 5.3 and will produce the same results as the version of the program before we began implementing these changes.

No comments:

Post a Comment