Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
thomas_jung
Developer Advocate
Developer Advocate
Introduction
Several months back I posted my BSP Developer's Journal Part XVI - Using the BTF Editor. This weblog focused on the BTF editor and its use, particularly within the BSP area. Then last week by request, I revisited the BTF subject by providing an example of BSP: BTF editor example, Non Model View Controller. A question came from this weblog posting about how to save the content that is created in the BTF Editor. That is the topic that I would like to discuss today.

The example use of BTF that I want to talk about today is basically using it as a replacement for the traditional R/3 Long Text Object. At my company I have built table structures and configuration that mirror the setup of the R/3 Long Text but designed instead for holding BTF content. Similar to the R/3 Function Group STXD, I have created an ABAP Class for easy access to and manipulation of these BTF texts. Now although my weblogs about BTF in the past have focused on BTF in BSP, I want to point out that this solution can be used in either BSP applications or standard ABAP transactions.

To give you an idea of what this solution entails before we even start, I show the Object List of all items in my BTF Framework Package:



Database Layout
The BTF objects provided by SAP don't provide any real means for storing the resulting content in relation to the business objects they are connected to. This gives the BTF technology considerable flexibility, but also means that we need to create structures for storing this content ourselves. Just like in R/3 Long Text, I am going to start by categorizing my text objects by a Business Object and a Text ID. I wanted to create my own set of configuration tables with different values from the R/3 ones. To do this I first created Domains and Data Elements for my Object, Text ID, and their descriptions. I then created language independent configuration tables for each element. The following are what those tables look like:

Application Object Config Table




Text Id Config Table




Table Maintenance
I guess I am making an assumption that my readers are already aware of how to create language independent configuration tables since I am not going into great detail on this topic. I will include this screenshot that details the special foreign key relationship that exists between the ID only table and the ID + Description table.


My next step is to create a maintenance view for each pair of tables. I then generate table maintenance for the maintenance view. The following is what one of the maintenance views look like:


After all this is said and done, we are ready to use the generated table maintenance to setup our test data. The following are the entries that I setup for testing:


Text Table
Last but not least, we need a place to store the text objects themselves. I created a simple transparent table, once again modeled off the R/3 Long text tables. For keys we have our application object, text ID, language, and a business key (named BTF_NAME). For instance, this business key might be the concatenation of Purchase Order Number and Purchase Order Line Item. The main difference between my table and the R/3 one is that I am going to store my text as a single binary string in the database.


 

Exceptions
Now that we are done talking about the boring old database stuff, we can move on to the much more exciting technologies aspects. We will start off by looking at my exception class. If you need a nice tutorial on class based exceptions, I suggest that you check out the following weblog series by Thomas Weiss:

Message Class
I chose to go with an exception class that was Message Class based. Therefore when I first generated my class I received the IF_T100_MESSAGE interface. This allows me to link my exception tests directly to my message class. The following is a screen shot of my message class with all the message that I will eventually link to my exception class.


 

Exception Class Definition
Now you can see the properties and interfaces on my Exception class itself. Notice the IF_T100_MESSAGE interface that I mentioned earlier.
 

Exception Class Attributes
Now I know that I am going to have a few values that I will want to dynamically pass into my exception messages. Therefore I need to define public attributes in my Exception Class for these values. I ended up with attributes for Object, Text ID, and Object Name.


Exception IDs
Next up we will create the individual text IDs. This is where the Exception class has a special Texts tab. From here we can create new Exception IDs, link them to a message class object, and assign exception class attributes to the variables in our message.




 

Sample Code for raising our Exception
Well this is getting a little ahead of ourselves, but I thought it would be appropriate to show a little sample code for raising one of these exceptions. Later in the coding of the main class itself, you will see plenty of these examples.
raise exception type zcx_es_btf_framework
exporting btf_name = me->btf_name
btf_object = me->btf_object
btf_id = me->btf_tdid
textid = zcx_es_btf_framework=>zcs_es_btf_name_doesnt_exists.

You can see that we are able to fill the public attributes of our exception class during the raise exception itself. We are also able to specify which text id we want to trigger because each one can be identified via a generated public constant in the exception class.

 

Persistent Class
Writing SQL code is so 2004. Therefore I decided to code my database access using an ABAP Persistent Class. If you want a little background on Persistent Classes, have a look at ABAP Persistent Classes: Coding without SQL. ABAP has a nice graphical persistent class generator. You get a visual view of the source database table. You then choose the fields that you want to expose through the persistent class. When you activate the persistent class, get and set methods are generated for each field in the chosen database table. It also generates an Actor and Base Class. The following is a screen shot of the Persistent Class Builder:


Framework Class
We are ready to start looking at the Framework class itself. If you look at the next screen shot, you can see that this is fairly basic application class. However I did add the IF_SERIALIZABLE_OBJECT interface to this class. You will see later that we will have static methods for this class to help with serialization/deserialization of itself. This can be helpful if would want to use this class in a stateless BSP application as show in WebService Navigator Page for ABAP and Java.

Class Properties


Class Attributes
The attributes of our class are all Private but one - the BTF Document Object itself (which is necessary to a pass a reference of the document object into the BTF Editor). I do have an editor object attribute for the SAPGui. This is really only included for a couple of example/test methods I will have in my class. Because the BTF Editor itself is implemented differently depending upon if you are using SAPGui or BSP, I decided to leave all this functionality up to the consuming application.


Methods
The following is a listing of all the methods implemented in this class. I hope to have exposed all the methods one would need in working with BTF. I have reading, saving, deleting, etc. methods. In addition I have a few methods for testing and example, such as the download, display in IE, and display in Editor methods.



Constructor
In our constructor we receive and validate our text objects. If everything checks out we will also initialize the BTF Document object.
method constructor.
*@78QImporting@ I_OBJECT TYPE ZZES_BTF_TTXOB BTF Texts: application object
*@78QImporting@ I_TDID TYPE ZZES_BTF_TDID Text ID
*@78QImporting@ I_NAME TYPE TDOBNAME OPTIONAL Name
*@78QImporting@ I_SPRAS TYPE SPRAS DEFAULT SY-LANGU Language Key
*@78QImporting@ I_ENCODING TYPE CSEQUENCE DEFAULT 'utf-8' Character Encoding
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
****Set Keys
btf_name = i_name.
btf_object = i_object.
btf_spras = i_spras.
btf_tdid = i_tdid.
btf_encoding = i_encoding.
****Check Object
data: l_btf_obj like me->btf_object.
select single btf_obj from zes_btf_obj into l_btf_obj
where btf_obj = me->btf_object.
if sy-subrc ne 0.
raise exception type zcx_es_btf_framework
exporting btf_object = me->btf_object
textid = zcx_es_btf_framework=>zcx_es_btf_object_error.
endif.
****Check ID
data: l_btf_id like me->btf_tdid.
select single btf_id from zes_btf_id into l_btf_id
where btf_id = me->btf_tdid. if sy-subrc ne 0.
raise exception type zcx_es_btf_framework
exporting btf_id = me->btf_tdid
textid = zcx_es_btf_framework=>zcx_es_btf_id_error.
endif.
****Check Combinatin of ID and Object
select single btf_id from zes_btf_id into l_btf_id
where btf_obj = me->btf_object
and btf_id = me->btf_tdid.
if sy-subrc ne 0.
raise exception type zcx_es_btf_framework
exporting btf_id = me->btf_tdid
btf_object = me->btf_object
textid = zcx_es_btf_framework=>zcx_es_btf_id_object_match.
endif.
****Initialize BTF
me->initialize_btf( ).
endmethod.

 

GET_CURRENT_KEYS
Since all the object keys are stored as private attributes of our class, we need a public method that will give us read only access to these keys. That is exactly what this method is for.
method get_current_keys.
*@79QExporting@ E_OBJECT TYPE ZZES_BTF_TTXOB BTF Texts: application object
*@79QExporting@ E_TDID TYPE ZZES_BTF_TDID Text ID
*@79QExporting@ E_NAME TYPE TDOBNAME Name
*@79QExporting@ E_SPRAS TYPE SPRAS Language Key
*@79QExporting@ E_ENCODING TYPE STRING Character Encoding
e_object = btf_object.
e_tdid = btf_tdid.
e_name = btf_name.
e_spras = btf_spras.
e_encoding = btf_encoding.
endmethod.

 

GET_ENCODING_FROM_LANGUAGE
This is a static helper class. If a consuming application doesn't want to use the default character encoding of Unicode (utf-8), then they should probably use the encoding that matches the logon language. This method can be called to lookup this proper encoding for you.
method get_encoding_from_language.
*@78QImporting@ I_SPRAS TYPE SPRAS DEFAULT SY-LANGU Language Key
*@7BQReturning@ VALUE( R_ENCODING ) TYPE STRING Character Encoding
constants: kind type cpattrkind value 'H'.
data: codepage type cpcodepage.
****Get Code Page for the language
call function 'SCP_CODEPAGE_FOR_LANGUAGE'
exporting
language = i_spras
importing
codepage = codepage
exceptions
no_codepage = 1
others = 2.
if sy-subrc <> 0.
r_encoding = 'utf-8'.
exit.
endif.
****Get the encoding for the Code Page
select single cpattr from tcp00a into r_encoding
where cpcodepage = codepage
and cpattrkind = kind.
if r_encoding is initial.
r_encoding = 'utf-8'.
endif.
endmethod.

INITIALIZE_BTF
This method contains the code to get a reference to the BTF Main class and use it create a reference to a BTF Document object.
method initialize_btf.
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
****Create BTF Master Object
btf = cl_btf=>get_reference( ).
****Create an Empty BTF Document
document = btf->create_document( btf_spras ).
endmethod.

INTIALIZE_DOCUMENT
If your application wants to be able to set an initial string into an empty BTF document, this is the method it could use. You can pass the initial text in as a plain string and this method will take care of casting it into the binary string.
method intialize_document.
*@78QImporting@ I_TEXT TYPE STRING OPTIONAL Initialization Text
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ CX_BTF_PARAMETER_ERROR BTF Exception: Parameter Error
data x_text type xstring.
if document is initial.
raise exception type zcx_es_btf_framework
exporting textid = zcx_es_btf_framework=>zcx_es_btf_id_doc_init.
endif.
call function 'SCMS_STRING_TO_XSTRING'
exporting
text = i_text
importing
buffer = x_text
exceptions
failed = 1
others = 2.
if sy-subrc <> 0.
raise exception type zcx_es_btf_framework
exporting textid = zcx_es_btf_framework=>zcs_es_btf_content_cast.
endif.
document->set_content( text = x_text
encoding = btf_encoding ).
endmethod.

 

READ_TEXT
This critical method will use the persistent object to read the BTF Content from the database. It then decompresses the content and passes it to the BTF Document Object.
method read_text.
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
*@03QException@ CX_PARAMETER_INVALID_RANGE Parameter with Invalid Range
*@03QException@ CX_SY_BUFFER_OVERFLOW System Exception: Buffer too Short
*@03QException@ CX_SY_COMPRESSION_ERROR System Exception: Compression Error
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ CX_BTF_PARAMETER_ERROR BTF Exception: Parameter Error
****Load the Persistent Object
clear btf_content.
try.
btf_content = zca_es_pers_zes_btf_content=>agent->get_persistent(
i_btf_obj = me->btf_object
i_btf_id = me->btf_tdid
i_btf_spras = me->btf_spras
i_btf_name = me->btf_name ).
catch cx_os_object_not_found.
raise exception type zcx_es_btf_framework
exporting btf_name = me->btf_name
btf_object = me->btf_object
btf_id = me->btf_tdid
textid = zcx_es_btf_framework=>zcs_es_btf_name_doesnt_exists.
endtry.
me->content = btf_content->get_btf_content( ).
****Un-Compress the Object
if me->content is not initial.
call method cl_abap_gzip=>decompress_binary
exporting
gzip_in = me->content
importing
raw_out = me->content.
endif.
****Set Content into the BTF Document
if document is initial.
raise exception type zcx_es_btf_framework
exporting textid = zcx_es_btf_framework=>zcx_es_btf_id_doc_init.
endif.
data: l_encoding type string.
l_encoding = btf_content->get_encoding( ).
document->set_content( text = me->content
encoding = l_encoding ).
endmethod.

SAVE_TEXT
Equally important is our SAVE_TEXT method. This method will retrieve the BTF Content from the Document Object, compress it, and then use the persistent object to write it back into the database. Please note that an application object that uses this method, must follow this call with a commit work for the data base updates to actually be performed.
method save_text.
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
*@03QException@ CX_PARAMETER_INVALID_RANGE Parameter with Invalid Range
*@03QException@ CX_SY_BUFFER_OVERFLOW System Exception: Buffer too Short
*@03QException@ CX_SY_COMPRESSION_ERROR System Exception: Compression Error
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ CX_BTF_PARAMETER_ERROR BTF Exception: Parameter Error
****Get the Content from the BTF Document
if document is initial.
raise exception type zcx_es_btf_framework
exporting textid = zcx_es_btf_framework=>zcx_es_btf_id_doc_init.
endif.
document->get_content( importing
text = me->content ).
****Uncompress the Content
call method cl_abap_gzip=>compress_binary
exporting
raw_in = me->content
importing
gzip_out = me->content.
****Setup the Persistent Object
btf_content->set_btf_content( me->content ).
btf_content->set_btf_uuser( sy-uname ).
btf_content->set_btf_udate( sy-datum ).
btf_content->set_btf_utime( sy-uzeit ).
endmethod.

CREATE_TEXT
Obviously we need a way to create new text objects as well. Here we will validate that the business object name is valid, then initialize the BTF document, and finally create a new persistent object instance.
method create_text.
*@78QImporting@ I_NAME TYPE TDOBNAME
*@78QImporting@ I_TEXT TYPE STRING
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ CX_BTF_PARAMETER_ERROR BTF Exception: Parameter Error
****Is the Text Name already specified in the object
if btf_name is not initial.
raise exception type zcx_es_btf_framework
exporting btf_name = me->btf_name
textid = zcx_es_btf_framework=>zcs_es_btf_name_spec.
endif.
****Where we given a duplicate Name?
data: l_btf_id like me->btf_tdid.
select single btf_id from zes_btf_content into l_btf_id
where btf_obj = me->btf_object
and btf_id = me->btf_tdid
and btf_spras = me->btf_spras
and btf_name = i_name.
if sy-subrc = 0.
raise exception type zcx_es_btf_framework
exporting btf_name = i_name
btf_object = me->btf_object
btf_id = me->btf_tdid
textid = zcx_es_btf_framework=>zcs_es_btf_name_exists.
endif.
****Set Parameters and intialize the BTF Document
clear me->content.
me->btf_name = i_name.
me->intialize_document( i_text ).
****Initialize the Persistent Object
clear btf_content.
try.
data: l_encoding type cpattr.
l_encoding = me->btf_encoding.
btf_content = zca_es_pers_zes_btf_content=>agent->create_persistent(
i_btf_obj = me->btf_object
i_btf_id = me->btf_tdid
i_btf_spras = me->btf_spras
i_btf_name = me->btf_name
i_btf_user = sy-uname
i_btf_date = sy-datum
i_btf_time = sy-uzeit
i_encoding = l_encoding
i_btf_content = me->content ).
catch cx_os_object_existing.
raise exception type zcx_es_btf_framework
exporting btf_name = me->btf_name
btf_object = me->btf_object
btf_id = me->btf_tdid
textid = zcx_es_btf_framework=>zcs_es_btf_name_exists.
endtry.
endmethod.

DELETE_TEXT
To complete our quartet of obvious methods, we have the ever popular method to delete a text object. The code here is really quite simple because we use the persistent object to delete the database entries. The same rule about Commit Work from the Save method applies here as well.
method delete_text.
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
try.
zca_es_pers_zes_btf_content=>agent->delete_persistent(
i_btf_obj = me->btf_object
i_btf_id = me->btf_tdid
i_btf_spras = me->btf_spras
i_btf_name = me->btf_name ).
catch cx_os_object_not_existing.
raise exception type zcx_es_btf_framework
exporting btf_name = me->btf_name
btf_object = me->btf_object
btf_id = me->btf_tdid
textid = zcx_es_btf_framework=>zcs_es_btf_name_doesnt_exists.
endtry.
endmethod.

SERIALIZE_ME
Now we come to the serialization/deserialization static methods. This helper method takes in an instance of the same class and serializes it XML. Like we said before this provides interesting persistence options for stateless BSP applications. Now earlier I added the IF_SERIALIZABLE_OBJECT interface to my persistent class as well. Therefore this inner object reference will be serialized/deserialized as well.
method serialize_me.
*@78QImporting@ I_ME TYPE REF TO ZCL_ES_BTF_FRAMEWORK Kimball BTF Text Framework
*@7BQReturning@ VALUE( R_XML ) TYPE XSTRING
*@03QException@ CX_XSLT_EXCEPTION XSLT Exception
call transformation id
source model = i_me
result xml r_xml
options
data_refs = 'embedded'.
endmethod.

DESERIALIZE_ME
Just the opposite as the Serialize class. However the BTF objects didn't have the IF_SERIALIZABLE_OBJECT interface so their object instance was lost. Therefore we will restore them in this method with a little bit of code.
method deserialize_me.
*@78QImporting@ I_XML TYPE XSTRING
*@7BQReturning@ VALUE( R_ME ) TYPE REF TO ZCL_ES_BTF_FRAMEWORK Kimball BTF Text Framework
call transformation id
source xml i_xml
result model = r_me.
r_me->initialize_btf( ).
endmethod.

DOWNLOAD_BTF_TEXT
Now we come to a set of methods that aren't really designed to be consumed by a calling application. There are really only here for testing and to serve as example code (consequently, they don't do quite as good a job of error handling as the rest of the class). Also because these methods interact with the User's Front end through the SAPGui framework, you would never be able to call these from BSP. This first one will download the content of the BTF Document to your front end.
method download_btf_text.
*@78QImporting@ I_FILE TYPE CSEQUENCE
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ CX_BTF_PARAMETER_ERROR BTF Exception: Parameter Error
data: l_file type string.
l_file = i_file.
if l_file is initial.
data: l_filename type string,
l_path type string,
l_fullpath type string,
window_t type string.
window_t = 'Download BTF Text'(001).
call method cl_gui_frontend_services=>file_save_dialog
exporting
window_title = window_t
default_extension = 'HTM'
file_filter = 'HTML files (*.HTML, *.HTM)|*.HTML;*.HTM|'
changing
filename = l_filename
path = l_path
fullpath = l_fullpath
exceptions
cntl_error = 1
error_no_gui = 2
not_supported_by_gui = 3
others = 4.
if sy-subrc <> 0.
endif.
l_file = l_fullpath.
endif.
check l_file is not initial.
****Get the Content from the BTF Document
if document is initial.
raise exception type zcx_es_btf_framework
exporting textid = zcx_es_btf_framework=>zcx_es_btf_id_doc_init.
endif.
document->get_content( importing
text = me->content ).
types: t_data(255) type x.
data: data_tab type table of t_data.
call function 'SCMS_XSTRING_TO_BINARY'
exporting
buffer = me->content
tables
binary_tab = data_tab.
call method cl_gui_frontend_services=>gui_download
exporting
filename = l_file
filetype = 'BIN'
write_lf = ''
changing
data_tab = data_tab
exceptions
file_write_error = 1
no_batch = 2
gui_refuse_filetransfer = 3
invalid_type = 4
no_authority = 5
unknown_error = 6
header_not_allowed = 7
separator_not_allowed = 8
filesize_not_allowed = 9
header_too_long = 10
dp_error_create = 11
dp_error_send = 12
dp_error_write = 13
unknown_dp_error = 14
access_denied = 15
dp_out_of_memory = 16
disk_full = 17
dp_timeout = 18
file_not_found = 19
dataprovider_exception = 20
control_flush_error = 21
not_supported_by_gui = 22
error_no_gui = 23
others = 24.
if sy-subrc <> 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
* WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
endif.
endmethod.

VIEW_BTF_TEXT
This method downloads the BTF and starts up your browser for viewing it.
method view_btf_text.
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ CX_BTF_PARAMETER_ERROR BTF Exception: Parameter Error
data: l_file type string.
data: temp_dir type string.
call method cl_gui_frontend_services=>get_temp_directory
changing
temp_dir = temp_dir
exceptions
cntl_error = 1
error_no_gui = 2
not_supported_by_gui = 3
others = 4.
if sy-subrc <> 0.
endif.
cl_gui_cfw=>flush( ).
concatenate temp_dir
' tf.html'
into l_file.
me->download_btf_text( l_file ).
call method cl_gui_frontend_services=>execute
exporting
document = l_file
exceptions
cntl_error = 1
error_no_gui = 2
bad_parameter = 3
file_not_found = 4
path_not_found = 5
file_extension_unknown = 6
error_execute_failed = 7
synchronous_failed = 8
not_supported_by_gui = 9
others = 10.
if sy-subrc <> 0.
endif.
cl_gui_cfw=>flush( ).
endmethod.

EDIT_BTF_TEXT
This method will load the current BTF Document content into a SAPGui based BTF editor. This editor will open in a control based dialog box.
method edit_btf_text.
*@03QException@ ZCX_ES_BTF_FRAMEWORK Exception Class for BTF Framework
*@03QException@ CX_BTF_RUNTIME_ERROR BTF Exception: Runtime Error
*@03QException@ CX_BTF_PARAMETER_ERROR BTF Exception: Parameter Error
****Get the Content from the BTF Document
if document is initial.
raise exception type zcx_es_btf_framework
exporting textid = zcx_es_btf_framework=>zcx_es_btf_id_doc_init.
endif.
clear dialogbox.
create object dialogbox
exporting
* PARENT =
width = 30
height = 50
* STYLE =
* REPID =
* DYNNR =
* LIFETIME = lifetime_default
* TOP = 0
* LEFT = 0
* CAPTION =
* NO_AUTODEF_PROGID_DYNNR =
* METRIC = 0
* NAME =
exceptions
cntl_error = 1
cntl_system_error = 2
create_error = 3
lifetime_error = 4
lifetime_dynpro_dynpro_link = 5
event_already_registered = 6
error_regist_event = 7
others = 8.
if sy-subrc <> 0.
endif.
set handler me->on_dialogbox_close for
dialogbox.
try.
data: btf_system_exception type ref to cx_btf_system_error,
btf_exception_text type string.
data: design_mode type i.
clear btf_editor.
clear btf_editor_options.
design_mode = if_btf_editor_constants=>co_design_mode_on.
btf_editor = btf->create_editor( document ).
call method btf_editor->initialize
exporting
ctrl_parent = dialogbox
design_mode = design_mode.
btf_editor_options ?= btf_editor.
btf_editor_options->set_local_printing( if_btf_editor_options=>co_local_printing_on ).
btf_editor_options->set_local_operations( if_btf_editor_options=>co_local_operations_on ).
btf_editor_options->set_windows_fonts( if_btf_editor_options=>co_windows_fonts_on ).
catch cx_btf_system_error into btf_system_exception.
btf_exception_text = btf_system_exception->get_text( ).
if btf_system_exception->textid = cx_btf_system_error=>unsupported_gui_for_action.
message i208(00) with btf_exception_text.
else.
message e208(00) with btf_exception_text.
endif.
endtry.
endmethod.

ON_DIALOGBOX_CLOSE
Our final method serves as an event handler for the CLOSE event of the CL_GUI_DIALOGBOX_CONTAINER class. If you are interested in how to code Control Framework event handlers in Global ABAP Object classes, I suggest ABAP OO mixed with Classic Dynpro
method on_dialogbox_close.
call method btf_editor->get_content( ).
document = btf_editor->get_document( ).
cl_gui_cfw=>flush( ).
call method document->get_content
importing
text = content.
btf_editor->free( ).
clear btf_editor.
free btf_editor.
call method dialogbox->free
exceptions
others = 1.
free dialogbox.
clear dialogbox.
endmethod.

The ABAP Unit Class
The other day it was suggested that I should look into the ABAP Unit functionality for Unit Testing. As I was looking over this class in preparation for writing this weblog, I thought it might be the perfect opportunity to write my first ABAP Unit Class. It took me about 3 and half hours to create this Unit testing class. However a good portion of that time was spent on research. I read the online help and several weblogs on SDN that I found by searching on "ABAP Unit". I also did a good amount of trial and error coding. The end result is that I ended up with 4 test methods. The first one tests the proper initialization of the class including reading/writing content to/from the database with the persistent object. The second method tests my static method for loading encoding type by language. The third method tests the serialization/deserialization functionality. And the final method tests create and delete.

The ABAP Unit Class Definition
If you aren't familiar with ABAP Unit, this is simply another type of ABAP Object Class. It is declared as a local class in the object that you want to test. Therefore this is a local class, lcl_my_unit_test, housed in my global class, ZCL_ES_BTF_FRAMEWORK. The following is the definition of my test class.
class lcl_my_unit_test definition for testing.
private section.
methods test_init_class for testing.
methods test_encoding_from_lang for testing.
methods test_serialize for testing.
methods test_create_delete for testing.
endclass. "zcl_my_unit_test DEFINITION

The ABAP Unit Class Implementation
This is where the little bit of test data that I first setup in the weblog comes in handy. We will setup test conditions for a small amount of data that I preloaded into the development system. Since Unit Tests are only designed to run in a development system, we can predict that this data will be there. You will definitely want to have a look at the class cl_aunit_assert. This class contains all the comparison methods that really make up ABAP Unit. The best advice I can give you for writing an ABAP Unit class is to just have at it. I learned tons by making mistakes while trying to create this class. I'm sure that my ABAP Unit classes will get better as I write more of them.
*---------------------------------------------------------------------*
* CLASS lcl_my_unit_test IMPLEMENTATION
*---------------------------------------------------------------------*
**---------------------------------------------------------------------*
class lcl_my_unit_test implementation.
method test_init_class.
data: btf type ref to zcl_es_btf_framework.
try.
create object btf
exporting
i_object = '640_TRAIN'
i_tdid = '0001'
i_name = 'TEST01'.
endtry.
data: l_object type zzes_btf_ttxob,
l_tdid type zzes_btf_tdid,
l_spras type spras,
l_name type tdobname,
l_encoding type string.
call method btf->get_current_keys
importing
e_object = l_object
e_tdid = l_tdid
e_name = l_name
e_spras = l_spras
e_encoding = l_encoding.
cl_aunit_assert=>assert_equals( act = l_object
exp = '640_TRAIN2'
msg = 'Incorrect BTF Text Object'(a03) ).
cl_aunit_assert=>assert_equals( act = l_tdid
exp = '0001'
msg = 'Incorrect BTF Text ID'(a04) ).
cl_aunit_assert=>assert_equals( act = l_name
exp = 'TEST01'
msg = 'Incorrect BTF Text Name'(a05) ).
cl_aunit_assert=>assert_equals( act = l_spras
exp = sy-langu
msg = 'Incorrect BTF Text Language'(a06) ).
cl_aunit_assert=>assert_equals( act = l_encoding
exp = 'utf-8'
msg = 'Incorrect BTF Text Encoding'(a07) ).
cl_aunit_assert=>assert_not_initial( act = btf->document
msg = 'BTF Document Object is initial'(a08) ).
btf->read_text( ).
data: l_text type string.
data: l_date(10) type c.
data: l_time(8) type c.
write sy-datum to l_date.
write sy-uzeit to l_time.
concatenate 'Unit Test Text inserted on'
l_date
l_time
'by'
sy-uname
into l_text
separated by space.
btf->intialize_document( l_text ).
btf->save_text( ).
commit work and wait.
data: icontent type zes_btf_content.
select single * from zes_btf_content into icontent
where btf_obj = '640_TRAIN'
and btf_id = '0001'
and btf_name = 'TEST01'
and btf_spras = sy-langu.
cl_aunit_assert=>assert_equals( act = icontent-encoding
exp = 'utf-8'
msg = 'BTF Encoding Not Set'(a20) ).
cl_aunit_assert=>assert_equals( act = icontent-btf_uuser
exp = sy-uname
msg = 'BTF Last Updated By Not Set'(a09) ).
cl_aunit_assert=>assert_equals( act = icontent-btf_udate
exp = sy-datum
msg = 'BTF Last Updated Date Not Set'(a10) ).
cl_aunit_assert=>assert_not_initial( act = icontent-btf_content
msg = 'BTF Content on Update is initial'(a16) ).
data: begin_time type syuzeit.
begin_time = sy-uzeit - 60.
if icontent-btf_utime < begin_time or
icontent-btf_utime > sy-uzeit.
cl_aunit_assert=>fail( msg = 'BTF Last Update Time is not within acceptable bounds'(a11) ).
endif.
* IF sy-batch IS INITIAL.
* CALL METHOD btf->view_btf_text( ).
* ENDIF.
endmethod. "test_init_class

method test_encoding_from_lang.
data: lang_latin1 type sylangu.
data: lang_latin2 type sylangu.
data: output_encode type string.
lang_latin1 = 'E'. "English
clear output_encode.
output_encode = zcl_es_btf_framework=>get_encoding_from_language( lang_latin1 ).
cl_aunit_assert=>assert_equals( act = output_encode
exp = 'iso-8859-1'
msg = 'Incorrect encoding for English'(a01) ).
lang_latin2 = 'L'. "Polish
clear output_encode.
output_encode = zcl_es_btf_framework=>get_encoding_from_language( lang_latin2 ).
cl_aunit_assert=>assert_equals( act = output_encode
exp = 'iso-8859-2'
msg = 'Incorrect encoding for Polish'(a02) ).
endmethod. "test_encoding_from_lang
method test_serialize.
data: btf type ref to zcl_es_btf_framework.
data: btf2 type ref to zcl_es_btf_framework.
try.
create object btf
exporting
i_object = '640_TRAIN'
i_tdid = '0001'
i_name = 'TEST01'.
endtry.
btf->read_text( ).
data: xml type xstring.
call method zcl_es_btf_framework=>serialize_me
exporting
i_me = btf
receiving
r_xml = xml.
cl_aunit_assert=>assert_not_initial( act = xml
msg = 'Serialization Failed'(a13) ).
clear btf.
call method zcl_es_btf_framework=>deserialize_me
exporting
i_xml = xml
receiving
r_me = btf2.
cl_aunit_assert=>assert_not_initial( act = btf2
msg = 'Deserialization Failed'(a14) ).
data: l_object type zzes_btf_ttxob,
l_tdid type zzes_btf_tdid,
l_spras type spras,
l_name type tdobname,
l_encoding type string.
call method btf2->get_current_keys
importing
e_object = l_object
e_tdid = l_tdid
e_name = l_name
e_spras = l_spras
e_encoding = l_encoding.
cl_aunit_assert=>assert_equals( act = l_object
exp = '640_TRAIN'
msg = 'Incorrect BTF Text Object'(a03) ).
cl_aunit_assert=>assert_equals( act = l_tdid
exp = '0001'
msg = 'Incorrect BTF Text ID'(a04) ).
cl_aunit_assert=>assert_equals( act = l_name
exp = 'TEST01'
msg = 'Incorrect BTF Text Name'(a05) ).
cl_aunit_assert=>assert_equals( act = l_spras
exp = sy-langu
msg = 'Incorrect BTF Text Language'(a06) ).
cl_aunit_assert=>assert_equals( act = l_encoding
exp = 'utf-8'
msg = 'Incorrect BTF Text Encoding'(a07) ).
cl_aunit_assert=>assert_not_initial( act = btf2->document
msg = 'BTF Document Object is initial'(a08) ).
endmethod. "test_serialize
method test_create_delete.
data: btf type ref to zcl_es_btf_framework.
try.
create object btf
exporting
i_object = '640_TRAIN'
i_tdid = '0001'.
endtry.
data: l_name type tdobname.
data: i_number type i.
data: s_number(25) type c.
data: random_int type ref to cl_abap_random.
data: l_seed type i.
l_seed = cl_abap_random=>seed( ).
random_int = cl_abap_random=>create( l_seed ).
i_number = random_int->intinrange( low = 1
high = 999999999 ).
write i_number to s_number.
concatenate 'ABAP_UNIT_CREATE_TEST_'
s_number
into l_name.
condense l_name no-gaps.
data: l_text type string.
data: l_date(10) type c.
data: l_time(8) type c.
write sy-datum to l_date.
write sy-uzeit to l_time.
concatenate 'Unit Test Text inserted on'
l_date
l_time
'by'
sy-uname
into l_text
separated by space.
call method btf->create_text
exporting
i_name = l_name
i_text = l_text.
btf->save_text( ).
commit work and wait.
data: icontent type zes_btf_content.
select single * from zes_btf_content into icontent
where btf_obj = '640_TRAIN
' and btf_id = '0001'
and btf_name = l_name
and btf_spras = sy-langu.
cl_aunit_assert=>assert_equals( act = icontent-encoding
exp = 'utf-8'
msg = 'BTF Encoding Not Set'(a20) ).
cl_aunit_assert=>assert_equals( act = icontent-btf_user
exp = sy-uname
msg = 'BTF Created By Not Set'(a17) ).
cl_aunit_assert=>assert_equals( act = icontent-btf_date
exp = sy-datum
msg = 'BTF Created Date Not Set'(a18) ).
cl_aunit_assert=>assert_equals( act = icontent-btf_uuser
exp = sy-uname
msg = 'BTF Last Updated By Not Set'(a09) ).
cl_aunit_assert=>assert_equals( act = icontent-btf_udate
exp = sy-datum
msg = 'BTF Last Updated Date Not Set'(a10) ).
cl_aunit_assert=>assert_not_initial( act = icontent-btf_content
msg = 'BTF Content on Create is initial'(a15) ).
data: begin_time type syuzeit.
begin_time = sy-uzeit - 60.
if icontent-btf_utime < begin_time or
icontent-btf_utime > sy-uzeit.
cl_aunit_assert=>fail( msg = 'BTF Last Update Time is not within acceptable bounds'(a11) ).
endif.
begin_time = sy-uzeit - 60.
if icontent-btf_time < begin_time or
icontent-btf_time > sy-uzeit.
cl_aunit_assert=>fail( msg = 'BTF Create Time is not within acceptable bounds'(a19) ).
endif.
btf->delete_text( ).
commit work and wait.
select single * from zes_btf_content into icontent
where btf_obj = '640_TRAIN'
and btf_id = '0001'
and btf_name = l_name
and btf_spras = sy-langu.
if sy-subrc = 0.
cl_aunit_assert=>fail( msg = 'BTF Delete Failed'(a21) ).
endif.
endmethod. "test_create_delete
endclass. "lcl_my_unit_test IMPLEMENTATION

The ABAP Unit Use
Executing the ABAP Unit test is really quite simple. It can be ran from the test options in SE80 or as part of the Code Inspector (transaction SCI). The following shows one menu path to reach the Unit Test.


If your unit test passes, you receive a status message to that effect. However I wanted to demonstrate the screens you see when a test condition fails. That turned out to be not that difficult. The first time I executed my Unit Test after adding it to my class, I actually failed on a valid condition I had never caught before. The following are some screen shots of the test failure screens.




 

Closing
For those of you who are interested in putting the BTF technology to work, I hope this weblog will serve as one more example to learn from. Even if you aren't interested in BTF, I hope that you took away some learnings on persistent objects or maybe even ABAP Unit.
8 Comments