This blog represents part 1 Getting comfortable using the Object-Oriented design model with ABAP – Part 1
To recap from the preceding blog, we took a procedural program and transformed it into one that uses local static classes and a local interface. 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 excel_spreadsheet_manager definition
abstract
final.
public section.
class-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.
class-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 flight_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
endmethod.
endclass.
class carrier_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class carrier_report implementation.
method produce_report.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
endmethod.
endclass.
class booking_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class booking_report implementation.
method produce_report.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
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.
try.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
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 convert some of the static classes into classes that can be instantiated. 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
abstract
final.
public section.
class-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.
class-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.
Currently all the local classes we defined in our example program are static classes. 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:
◈ 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. Indeed, many object-oriented scholars frown on the definition and use of a static class since there is no object associated with it.
Changing the report classes to enable instantiation
Let’s start with the static classes flight_report, carrier_report and booking_report. Change these classes by doing the following:
◈ Remove the qualifier abstract from their class definition statements.
◈ Change the definition of their method produce_report from a static method to an instance method by replacing “class-methods” with “methods”.
A syntax check at this point will fail on the statement invoking method produce_report of class flight_report. Yes, this class no longer has a static method, so this statement invoking a static method is no longer applicable. To resolve this and the other imminent syntax errors, include the following statements at the top of method drive_process:
data : flight_report type ref to flight_report
, carrier_report type ref to carrier_report
, booking_report type ref to booking_report
.
create object: flight_report
, carrier_report
, booking_report
.
The data statements define instance reference variables to each of these classes. The create object statement creates instances of these three types of classes, placing the references to these instances into the respective instance references variables. In addition, change each of the call method statements in the subsequent try-endtry block so that method name produce_report is preceded not by a class selector (=>) but by an instance selector (->), as in:
call method flight_report->produce_report exporting row_count = row_count.
call method carrier_report->produce_report exporting row_count = row_count.
call method booking_report->produce_report exporting row_count = row_count.
Now a syntax check will pass again. Executing the program at this point should prove that it still works as before.
What we’ve done here is to change the flight_report, carrier_report and booking_report classes from static classes to classes that can be instantiated. In addition, we’ve changed the drive_process method to a) create instances of these classes and then b) invoke the produce_report method upon those instances. Prior to these changes the program could not create instances of its local classes, but after applying these changes it can, meaning that we now have objects providing the reports to us.
Changing the excel_spreadsheet_manager class to enable instantiation
Next, we’ll do for local static class excel_spreadsheet_manager the same thing we just did for each of the local static report classes, but as you’ll soon see, this change is a bit more involved. Start by doing the following:
◈ Remove the qualifier abstract from its class definition statement
◈ Change the definition of its methods from static methods to instance methods by replacing “class-methods” with “methods”.
◈ Change the definition of the private attribute excel from a static attribute to an instance attribute by replacing “class-data” with “data”.
A syntax check now will fail on the call to method copy_table_to_excel_worksheet in method produce_report of class flight_report. Since class excel_spreadsheet_manager no longer has static methods, these methods no longer can be invoked via the class name, but more to the point is that to invoke the methods of class excel_spreadsheet_manager we now need an instance reference to it. To facilitate this, change the signature for produce_report in class flight_report so that it now accepts a second importing parameter as a reference to an instance of excel_spreadsheet_manager, as in:
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
Then change method produce_report of class flight_report so that the call to method name copy_table_to_excel_worksheet is preceded not by a class selector (=>) but by an instance selector (->), as in:
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
Apply this same relative change to both classes carrier_report and booking_report. Afterward, change method drive_process to:
◈ include a new reference variable to class excel_spreadsheet_manager
◈ include this new reference variable on the create object statement
◈ adjust all the calls to methods produce_report to include the now-mandatory second exporting parameter
◈ change the call to method send_excel_via_email of class excel_spreadsheet_manager so that the call to this method is preceded not by a class selector (=>) but by an instance selector (->)
The code in method drive_process now should look like this:
method drive_process.
data : flight_report type ref to flight_report
, carrier_report type ref to carrier_report
, booking_report type ref to booking_report
, excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
.
create object: flight_report
, carrier_report
, booking_report
, excel_spreadsheet_manager
.
try.
call method flight_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method carrier_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method booking_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager->send_excel_via_email exporting recipient = recipient.
endmethod.
Now a syntax check will pass again. Executing the program at this point should prove that it still works as before.
What we’ve done here is to change class excel_spreadsheet_manager from a static class to an instance class, but just look at all the other changes this required:
◈ We now create an instance of this class in method drive_process.
◈ We now pass the reference to this instance on the signatures for the produce_report methods of classes flight_report, carrier_report and booking_report.
◈ In the produce_report methods of classes flight_report, carrier_report and booking_report, we now invoke the method copy_table_to_excel_worksheet using the instance passed in through the signature of produce_report.
◈ We now invoke method send_excel_via_email using the excel_spreadsheet_manager reference variable defined and populated in method drive_process.
One of the notable things about the changes we’ve made here is that the produce_report methods of classes flight_report, carrier_report and booking_report have had their signatures changed to accept a reference to an instance of a class on an importing parameter. In this case the instance is one of type reference to excel_spreadsheet_manager. When excel_spreadsheet_manager was defined as a static class its methods could be reached from anywhere within the program simply by prefixing the method calls with the class qualifier, as in:
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet . . .
Notice the use of the class selector (=>) in the statement above instead of the instance selector (->) now being used for such calls. Now that excel_spreadsheet_manager is no longer a static class, the produce_report methods no longer would have had access to what is now an instance of this class. Accordingly, the produce_report methods of classes flight_report, carrier_report and booking_report need to have an instance of class excel_spreadsheet_manager made available to them. With our new process, the instance on which the produce_report method is dependent is injected into the method through its signature. This concept is known as dependency injection. There are other ways to inject dependencies into instances of classes, but when a method signature provides an object on which the method depends, it is known as parameter injection, a specific type of dependency injection. In short, dependency injection is in effect when a class or method is provided with a dependency it needs to satisfy its processing.
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 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 flight_report definition
final.
public section.
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
endmethod.
endclass.
class carrier_report definition
final.
public section.
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
endclass.
class carrier_report implementation.
method produce_report.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
endmethod.
endclass.
class booking_report definition
final.
public section.
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
endclass.
class booking_report implementation.
method produce_report.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
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 : flight_report type ref to flight_report
, carrier_report type ref to carrier_report
, booking_report type ref to booking_report
, excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
.
create object: flight_report
, carrier_report
, booking_report
, excel_spreadsheet_manager
.
try.
call method flight_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method carrier_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method booking_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
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.
We now have instances of local classes being created and we are passing instances between objects via dependency injection.
To recap from the preceding blog, we took a procedural program and transformed it into one that uses local static classes and a local interface. 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 excel_spreadsheet_manager definition
abstract
final.
public section.
class-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.
class-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 flight_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
endmethod.
endclass.
class carrier_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class carrier_report implementation.
method produce_report.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
endmethod.
endclass.
class booking_report definition
abstract
final.
public section.
class-methods: produce_report
importing
row_count
type data_exchangeable=>row_counter
raising
zcx_excel
.
endclass.
class booking_report implementation.
method produce_report.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
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.
try.
call method flight_report=>produce_report exporting row_count = row_count.
call method carrier_report=>produce_report exporting row_count = row_count.
call method booking_report=>produce_report exporting row_count = row_count.
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 convert some of the static classes into classes that can be instantiated. 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
abstract
final.
public section.
class-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.
class-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
Currently all the local classes we defined in our example program are static classes. 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:
◈ 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. Indeed, many object-oriented scholars frown on the definition and use of a static class since there is no object associated with it.
Changing the report classes to enable instantiation
Let’s start with the static classes flight_report, carrier_report and booking_report. Change these classes by doing the following:
◈ Remove the qualifier abstract from their class definition statements.
◈ Change the definition of their method produce_report from a static method to an instance method by replacing “class-methods” with “methods”.
A syntax check at this point will fail on the statement invoking method produce_report of class flight_report. Yes, this class no longer has a static method, so this statement invoking a static method is no longer applicable. To resolve this and the other imminent syntax errors, include the following statements at the top of method drive_process:
data : flight_report type ref to flight_report
, carrier_report type ref to carrier_report
, booking_report type ref to booking_report
.
create object: flight_report
, carrier_report
, booking_report
.
The data statements define instance reference variables to each of these classes. The create object statement creates instances of these three types of classes, placing the references to these instances into the respective instance references variables. In addition, change each of the call method statements in the subsequent try-endtry block so that method name produce_report is preceded not by a class selector (=>) but by an instance selector (->), as in:
call method flight_report->produce_report exporting row_count = row_count.
call method carrier_report->produce_report exporting row_count = row_count.
call method booking_report->produce_report exporting row_count = row_count.
Now a syntax check will pass again. Executing the program at this point should prove that it still works as before.
What we’ve done here is to change the flight_report, carrier_report and booking_report classes from static classes to classes that can be instantiated. In addition, we’ve changed the drive_process method to a) create instances of these classes and then b) invoke the produce_report method upon those instances. Prior to these changes the program could not create instances of its local classes, but after applying these changes it can, meaning that we now have objects providing the reports to us.
Changing the excel_spreadsheet_manager class to enable instantiation
Next, we’ll do for local static class excel_spreadsheet_manager the same thing we just did for each of the local static report classes, but as you’ll soon see, this change is a bit more involved. Start by doing the following:
◈ Remove the qualifier abstract from its class definition statement
◈ Change the definition of its methods from static methods to instance methods by replacing “class-methods” with “methods”.
◈ Change the definition of the private attribute excel from a static attribute to an instance attribute by replacing “class-data” with “data”.
A syntax check now will fail on the call to method copy_table_to_excel_worksheet in method produce_report of class flight_report. Since class excel_spreadsheet_manager no longer has static methods, these methods no longer can be invoked via the class name, but more to the point is that to invoke the methods of class excel_spreadsheet_manager we now need an instance reference to it. To facilitate this, change the signature for produce_report in class flight_report so that it now accepts a second importing parameter as a reference to an instance of excel_spreadsheet_manager, as in:
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
Then change method produce_report of class flight_report so that the call to method name copy_table_to_excel_worksheet is preceded not by a class selector (=>) but by an instance selector (->), as in:
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
Apply this same relative change to both classes carrier_report and booking_report. Afterward, change method drive_process to:
◈ include a new reference variable to class excel_spreadsheet_manager
◈ include this new reference variable on the create object statement
◈ adjust all the calls to methods produce_report to include the now-mandatory second exporting parameter
◈ change the call to method send_excel_via_email of class excel_spreadsheet_manager so that the call to this method is preceded not by a class selector (=>) but by an instance selector (->)
The code in method drive_process now should look like this:
method drive_process.
data : flight_report type ref to flight_report
, carrier_report type ref to carrier_report
, booking_report type ref to booking_report
, excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
.
create object: flight_report
, carrier_report
, booking_report
, excel_spreadsheet_manager
.
try.
call method flight_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method carrier_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method booking_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
catch zcx_excel ##NO_HANDLER.
endtry.
call method excel_spreadsheet_manager->send_excel_via_email exporting recipient = recipient.
endmethod.
Now a syntax check will pass again. Executing the program at this point should prove that it still works as before.
What we’ve done here is to change class excel_spreadsheet_manager from a static class to an instance class, but just look at all the other changes this required:
◈ We now create an instance of this class in method drive_process.
◈ We now pass the reference to this instance on the signatures for the produce_report methods of classes flight_report, carrier_report and booking_report.
◈ In the produce_report methods of classes flight_report, carrier_report and booking_report, we now invoke the method copy_table_to_excel_worksheet using the instance passed in through the signature of produce_report.
◈ We now invoke method send_excel_via_email using the excel_spreadsheet_manager reference variable defined and populated in method drive_process.
Dependency Injection
One of the notable things about the changes we’ve made here is that the produce_report methods of classes flight_report, carrier_report and booking_report have had their signatures changed to accept a reference to an instance of a class on an importing parameter. In this case the instance is one of type reference to excel_spreadsheet_manager. When excel_spreadsheet_manager was defined as a static class its methods could be reached from anywhere within the program simply by prefixing the method calls with the class qualifier, as in:
call method excel_spreadsheet_manager=>copy_table_to_excel_worksheet . . .
Notice the use of the class selector (=>) in the statement above instead of the instance selector (->) now being used for such calls. Now that excel_spreadsheet_manager is no longer a static class, the produce_report methods no longer would have had access to what is now an instance of this class. Accordingly, the produce_report methods of classes flight_report, carrier_report and booking_report need to have an instance of class excel_spreadsheet_manager made available to them. With our new process, the instance on which the produce_report method is dependent is injected into the method through its signature. This concept is known as dependency injection. There are other ways to inject dependencies into instances of classes, but when a method signature provides an object on which the method depends, it is known as parameter injection, a specific type of dependency injection. In short, dependency injection is in effect when a class or method is provided with a dependency it needs to satisfy its processing.
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 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 flight_report definition
final.
public section.
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
endclass.
class flight_report implementation.
method produce_report.
data : flight_stack type standard table of sflight
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = flight_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table flight_stack
from sflight
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = flight_stack
source_description = 'Flights'.
endmethod.
endclass.
class carrier_report definition
final.
public section.
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
endclass.
class carrier_report implementation.
method produce_report.
data : carrier_stack type standard table of scarr
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = carrier_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table carrier_stack
from scarr
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = carrier_stack
source_description = 'Carriers'.
endmethod.
endclass.
class booking_report definition
final.
public section.
methods : produce_report
importing
row_count
type data_exchangeable=>row_counter
excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
raising
zcx_excel
.
endclass.
class booking_report implementation.
method produce_report.
data : booking_stack type standard table of sbook
, alv_report type ref to cl_salv_table
.
try.
call method cl_salv_table=>factory
importing
r_salv_table = alv_report
changing
t_table = booking_stack.
catch cx_salv_msg.
return.
endtry.
select *
into table booking_stack
from sbook
up to row_count rows.
alv_report->display( ).
call method excel_spreadsheet_manager->copy_table_to_excel_worksheet
exporting
source_stack = booking_stack
source_description = 'Bookings'.
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 : flight_report type ref to flight_report
, carrier_report type ref to carrier_report
, booking_report type ref to booking_report
, excel_spreadsheet_manager
type ref to excel_spreadsheet_manager
.
create object: flight_report
, carrier_report
, booking_report
, excel_spreadsheet_manager
.
try.
call method flight_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method carrier_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
call method booking_report->produce_report exporting row_count = row_count
excel_spreadsheet_manager = excel_spreadsheet_manager.
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.
We now have instances of local classes being created and we are passing instances between objects via dependency injection.
No comments:
Post a Comment