Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
ABAP arbitrary value store (registry) as seen at http://ceronio.net/2015/05/arbitrary-value-store-registry-for-abap/
*&---------------------------------------------------------------------*
*& Include ZLIBREGISTRY
*&---------------------------------------------------------------------*
* Implementation of a registry for storing arbitrary values (similar
* to the MS Windows registry)
* Author: Martin Ceronio (2015), http://ceronio.net
* Released under MIT License: http://opensource.org/licenses/MIT
class lcx_registry_err definition inheriting from cx_dynamic_check.
endclass. "lcx_registry_err DEFINITION
*----------------------------------------------------------------------*
* CLASS lcx_registry_lock DEFINITION
*----------------------------------------------------------------------*
class lcx_registry_lock definition inheriting from lcx_registry_err.
endclass. "lcx_registry_lock DEFINITION
*----------------------------------------------------------------------*
* CLASS lcx_registry_noentry DEFINITION
*----------------------------------------------------------------------*
class lcx_registry_noentry definition inheriting from lcx_registry_err.
endclass. "lcx_registry_noentry DEFINITION
*----------------------------------------------------------------------*
* CLASS lcx_registry_entry_exists DEFINITION
*----------------------------------------------------------------------*
class lcx_registry_entry_exists definition inheriting from lcx_registry_err.
endclass. "lcx_registry_entry_exists DEFINITION
*----------------------------------------------------------------------*
* CLASS lcx_registry_entry_deleted DEFINITION
*----------------------------------------------------------------------*
class lcx_registry_entry_deleted definition inheriting from lcx_registry_err.
endclass. "lcx_registry_entry_deleted DEFINITION
*----------------------------------------------------------------------*
* CLASS lcx_registry_invalid_char DEFINITION
*----------------------------------------------------------------------*
class lcx_registry_invalid_char definition inheriting from lcx_registry_err.
endclass. "lcx_registry_invalid_char DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_registry_entry DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
class lcl_registry_entry definition create protected.
public section.
* Predefined key for the registry root:
class-data: registry_root type indx_srtfd read-only value 'REGISTRY_ROOT'.
types: begin of ts_keyval,
key type string,
value type string,
end of ts_keyval.
types: tt_keyval type sorted table of ts_keyval with unique key key.
* For keeping track of references to sub-entries, we maintain a shadow
* table with the same keys
types: begin of ts_keyobj,
key type string,
value type ref to lcl_registry_entry,
end of ts_keyobj.
types: tt_keyobj type sorted table of ts_keyobj with unique key key.
data: sub_entries type tt_keyval read-only.
data: values type tt_keyval read-only.
data: internal_key type indx_srtfd read-only.
data: parent_key type indx_srtfd read-only.
data: entry_id type string read-only. "User-friendly ID of the subnode
methods:
constructor
importing
internal_key type any,
reload,
* lock raising lcx_registry_err,
* Saves entry and all dirty sub-entries
save raising lcx_registry_err,
get_parent
returning value(parent) type ref to lcl_registry_entry,
create_by_path
importing path type string
returning value(entry) type ref to lcl_registry_entry
raising lcx_registry_err,
*--------------------------------------------------------------------*
* Methods dealing with sub-entries of the registry entry
get_subentry
importing key type clike
returning value(entry) type ref to lcl_registry_entry,
add_subentry
importing key type clike
returning value(entry) type ref to lcl_registry_entry
raising lcx_registry_entry_exists,
* Removes sub-entry and all entries underneath
remove_subentry
importing key type clike
raising lcx_registry_err,
remove_subentries
raising lcx_registry_err,
copy_subentry
importing source_key type clike
target_key type clike
returning value(target_entry) type ref to lcl_registry_entry
raising lcx_registry_err,
get_subentry_keys
returning value(keys) type string_table,
get_subentries
returning value(sub_entries) type tt_keyobj,
* Methods for dealing with values in the registry entry:
* Get keys of all values
get_value_keys
returning value(keys) type string_table,
* Get all values
get_values
returning value(values) type tt_keyval,
* Set all values in one go:
set_values
importing values type tt_keyval,
* Get single value by key
get_value
importing key type clike
returning value(value) type string
raising lcx_registry_noentry,
* Set/overwrite single value
set_value
importing key type clike
value type any,
* Delete single value by key
delete_value
importing key type clike.
class-methods:
get_entry_by_internal_key
importing key type any
returning value(entry) type ref to lcl_registry_entry,
get_root
returning value(root) type ref to lcl_registry_entry.
protected section.
methods:
set_optimistic_lock
raising lcx_registry_lock,
promote_lock
raising lcx_registry_lock,
release_lock,
copy_subentry_deep
importing source type ref to lcl_registry_entry
target type ref to lcl_registry_entry,
* Remove the registry entry from the database:
* The DELETE method is protected because you must always delete an entry
* as the sub-entry of its parent so that the link is removed from the
* parent
delete
raising lcx_registry_err.
data: deleted type abap_bool.
* data: sub_entrobj type tt_keyobj.
* Class-wide buffer of instances of registry entries
class-data: registry_entries type tt_keyobj.
endclass. "lcl_registry_entry DEFINITION
*----------------------------------------------------------------------*
* CLASS lcl_registry_entry IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
class lcl_registry_entry implementation.
*--------------------------------------------------------------------*
* CONSTRUCTOR - new instance of registry key
*--------------------------------------------------------------------*
method constructor.
me->internal_key = internal_key.
* Load the entry from the database
reload( ).
* Object inserts itself into registry of entries
data: ko type ts_keyobj.
ko-key = me->internal_key.
ko-value = me.
insert ko into table registry_entries.
endmethod. "constructor
*--------------------------------------------------------------------*
* RELOAD - reload values and sub-entries from database, set new lock
*--------------------------------------------------------------------*
method reload.
* Reload the values and sub-entries from the database
import values = me->values sub_entries = me->sub_entries parent = parent_key entry_id = entry_id
from database indx(zr) id internal_key.
if sy-subrc ne 0.
raise exception type lcx_registry_noentry.
endif.
set_optimistic_lock( ).
endmethod. "reload
*--------------------------------------------------------------------*
* GET_ROOT - retrieve root entry of registry
*--------------------------------------------------------------------*
method get_root.
* If the root doesn't exist yet, create it
data: values type tt_keyval.
data: sub_entries type tt_keyval.
data: parent_key type indx_srtfd value space.
data: entry_id type string.
import values = values sub_entries = sub_entries
from database indx(zr) id registry_root.
if sy-subrc ne 0.
entry_id = registry_root.
export values = values sub_entries = sub_entries parent = parent_key entry_id = entry_id
to database indx(zr) id registry_root.
endif.
* Retrieve the root entry of the registry
root = get_entry_by_internal_key( registry_root ).
endmethod. "get_root
*--------------------------------------------------------------------*
* GET_ENTRY_BY_INTERNAL_KEY - retrieve reg. entry by internal ID
*--------------------------------------------------------------------*
method get_entry_by_internal_key.
data: ko type ts_keyobj.
* Search global index of registry entry instances
read table registry_entries into ko with key key = key.
if sy-subrc = 0.
* Reference already exists; return that
entry = ko-value.
else.
* Create new reference to sub-entry
create object entry
exporting
internal_key = key.
* Will insert itself into registry entries
endif.
endmethod. "get_entry_by_internal_key
*--------------------------------------------------------------------*
* CREATE_BY_PATH - convenience method, analogous to mkdir -p that
* allows you to create a path of registry entries if they do not yet
* exist; paths must be separated by forward slash ('/')
* Sub-entries are created from the current registry entry
*--------------------------------------------------------------------*
method create_by_path.
data: keys type string_table.
data: key type string.
data: sub_entry type ref to lcl_registry_entry.
split path at '/' into table keys.
delete keys where table_line is initial.
entry = me.
loop at keys into key.
sub_entry = entry->get_subentry( key ).
if sub_entry is not bound.
sub_entry = entry->add_subentry( key ).
endif.
entry = sub_entry.
endloop.
* After successful processing of chain, ENTRY will
* contain the last-created node
endmethod. "create_by_path
*--------------------------------------------------------------------*
* GET_PARENT - retrieve parent entry of this entry
*--------------------------------------------------------------------*
method get_parent.
* Return the parent of the current key
parent = get_entry_by_internal_key( parent_key ).
endmethod. "get_parent
*--------------------------------------------------------------------*
* GET_SUBENTRY - return single child entry by key
*--------------------------------------------------------------------*
method get_subentry.
data: kv type ts_keyval.
data: ko type ts_keyobj.
* Read internal store of sub-entries
read table sub_entries into kv with key key = key.
if sy-subrc ne 0.
* Entry does not exist; exit
return.
endif.
* Search global index of registry entry instances
read table registry_entries into ko with key key = kv-value.
* read table sub_entrobj into ko with key key = kv-value.
if sy-subrc = 0.
* Reference already exists; return that
entry = ko-value.
else.
* Create new reference to sub-entry
create object entry
exporting
internal_key = kv-value.
* Will insert itself into registry entries
endif.
endmethod. "get_subentry
*--------------------------------------------------------------------*
* GET_SUBENTRIES - return immediate children registry entries
*--------------------------------------------------------------------*
method get_subentries.
data: ko type ts_keyobj.
data: subkeys type string_table.
data: subkey type string.
subkeys = get_subentry_keys( ).
loop at subkeys into subkey.
ko-key = subkey.
ko-value = get_subentry( subkey ).
insert ko into table sub_entries.
endloop.
endmethod. "get_subentries
*--------------------------------------------------------------------*
* ADD_SUBENTRY - add a child entry with new key and save
*--------------------------------------------------------------------*
method add_subentry.
data: kv type ts_keyval.
data: ko type ts_keyobj. "Shadow table with object references
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
* Check that only allowed characters are used. Will help for making
* sensible paths and string handling in other applications
* Most of all, we want to avoid spaces and slashes (although those
* square and curly brackets could cause problems for JSON...)
if not key co 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890@#$%^_+-(){}[]'.
raise exception type lcx_registry_invalid_char.
endif.
* Read internal store of sub-entries
read table sub_entries into kv with key key = key.
if sy-subrc = 0.
raise exception type lcx_registry_entry_exists.
endif.
* Create unique ID for key in INDX for the new entry
kv-key = key.
try.
kv-value = cl_system_uuid=>create_uuid_c22_static( ).
catch cx_uuid_error.
raise exception type lcx_registry_err.
endtry.
insert kv into table sub_entries.
* Create an entry on the database for the new entry
data: lt_empty_vals type tt_keyval.
data: lv_srtfd type indx_srtfd.
lv_srtfd = kv-value.
export values = lt_empty_vals sub_entries = lt_empty_vals
parent = internal_key entry_id = key
to database indx(zr) id lv_srtfd.
create object entry
exporting
internal_key = kv-value.
* Will insert itself into registry entries
** Set current entry as the parent of the new entry
* entry->parent_key = internal_key.
** Set short ID on the new entry
* entry->entry_id = key.
** Save the new entry
* entry->save( ).
* Save the current entry to update the list of sub-keys
save( ).
endmethod. "add_subentry
*--------------------------------------------------------------------*
* COPY_SUBENTRY - copy a child registry entry at the same level,
* including all values, by passing a source and target key
*--------------------------------------------------------------------*
method copy_subentry.
data: source_entry type ref to lcl_registry_entry.
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
source_entry = get_subentry( source_key ).
if source_entry is not bound.
raise exception type lcx_registry_noentry.
endif.
target_entry = add_subentry( target_key ).
* Using the source and the new target, do a deep copy that includes
* copies of sub-entries and values
copy_subentry_deep( source = source_entry target = target_entry ).
endmethod. "copy_subentry
*--------------------------------------------------------------------*
* COPY_SUBENTRY_DEEP - (protected) - copy a branch of the registry
* at the same level, including all values
*--------------------------------------------------------------------*
method copy_subentry_deep.
data: ls_subentry type ts_keyval.
data: lr_source type ref to lcl_registry_entry.
data: lr_target type ref to lcl_registry_entry.
* Copy values from source to target
target->values = source->values.
* Copy sub-entries from source to target
loop at source->sub_entries into ls_subentry.
lr_source = source->get_subentry( ls_subentry-key ).
lr_target = target->add_subentry( ls_subentry-key ).
copy_subentry_deep( source = lr_source target = lr_target ).
endloop.
* Ensure that values are also saved
save( ).
endmethod. "copy_subentry_deep
*--------------------------------------------------------------------*
* REMOVE_SUBENTRIES - remove all child entries of this entry
*--------------------------------------------------------------------*
method remove_subentries.
data: kv type ts_keyval.
data: ko type ts_keyobj. "Shadow table with object references
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
loop at sub_entries into kv.
remove_subentry( kv-key ).
endloop.
endmethod. "remove_subentries
*--------------------------------------------------------------------*
* DELETE - delete the current entry from the database and mark it,
* preventing any further operations on this entry
*--------------------------------------------------------------------*
method delete.
data: sub_entry type ts_keyval.
data: entry type ref to lcl_registry_entry.
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
* Delete all sub-entries before deleting this entry
loop at sub_entries into sub_entry.
entry = get_subentry( sub_entry-key ).
entry->delete( ).
delete sub_entries.
endloop.
* Remove DB entry for the current entry
promote_lock( ).
delete from database indx(zr) id internal_key.
* Object removes itself from the global table too so that that reference no longer exists
delete registry_entries where key = internal_key.
* Set the object to deleted to prevent any operations on any remaining
* references to the object
deleted = abap_true.
* Release lock held on this key
release_lock( ).
endmethod. "delete
*--------------------------------------------------------------------*
* REMOVE_SUBENTRY - remove a single child registry entry by key
*--------------------------------------------------------------------*
method remove_subentry.
data: kv type ts_keyval.
field-symbols: <ko> type ts_keyobj. "Shadow table with object references
data: sub_entry type ref to lcl_registry_entry.
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
* Read internal store of sub-entries
read table sub_entries into kv with key key = key.
if sy-subrc ne 0.
* Entry does not exist; exit with error
raise exception type lcx_registry_noentry.
endif.
* Remove all sub-entries of the sub-entry before removing the sub-entry
sub_entry = get_subentry( key ).
if sub_entry is bound.
* Delete the sub_entry (which deletes its sub-entries)
sub_entry->delete( ).
* Remove entry from sub-entry table and shadow table
delete sub_entries where key = key.
save( ). "Save current entry to remove subentry that has been removed
endif.
endmethod. "remove_subentry
*--------------------------------------------------------------------*
* SAVE - save the current entry, with concurrency control
*--------------------------------------------------------------------*
method save.
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
promote_lock( ).
export values = me->values sub_entries = me->sub_entries parent = parent_key entry_id = entry_id
to database indx(zr) id internal_key.
set_optimistic_lock( ).
endmethod. "save
*--------------------------------------------------------------------*
* GET_SUBENTRY_KEYS - retrieve keys of all child registry entries
*--------------------------------------------------------------------*
method get_subentry_keys.
data: kv type ts_keyval.
loop at sub_entries into kv.
append kv-key to keys.
endloop.
endmethod. "get_subentry_keys
*--------------------------------------------------------------------*
* GET_VALUE_KEYS - retrieve keys of all values
*--------------------------------------------------------------------*
method get_value_keys.
data: kv type ts_keyval.
loop at values into kv.
append kv-key to keys.
endloop.
endmethod. "get_value_keys
*--------------------------------------------------------------------*
* GET_VALUES - retrieve all values at once in key+value table
*--------------------------------------------------------------------*
method get_values.
values = me->values.
endmethod. "get_values
*--------------------------------------------------------------------*
* SET_VALUES - set all values at once with key+value table
*--------------------------------------------------------------------*
method set_values.
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
me->values = values.
endmethod. "set_values
*--------------------------------------------------------------------*
* GET_VALUE - return a single value by key
*--------------------------------------------------------------------*
method get_value.
data: kv type ts_keyval.
read table values into kv with key key = key.
if sy-subrc = 0.
value = kv-value.
endif.
endmethod. "get_value
method set_value.
data: kv type ts_keyval.
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
* Add the value to set of values if not existing or change if it does exist
read table values into kv with key key = key.
if sy-subrc ne 0.
kv-key = key.
kv-value = value.
insert kv into table values.
else.
kv-value = value.
modify table values from kv.
endif.
endmethod. "set_value
method delete_value.
* Prevent any changes if this entry is marked as deleted
if me->deleted = abap_true.
raise exception type lcx_registry_entry_deleted.
endif.
delete values where key = key.
endmethod. "delete_value
**********************************************************************
* CONCURRENCY HELPER METHODS
**********************************************************************
*--------------------------------------------------------------------*
* SET_OPTIMISTIC_LOCK - always set when (re-)reading an entry
*--------------------------------------------------------------------*
method set_optimistic_lock.
* Existing lock must be released before acquiring a new one
release_lock( ).
call function 'ENQUEUE_ESINDX'
exporting
mode_indx = 'O'
relid = 'ZR'
srtfd = internal_key
exceptions
foreign_lock = 1
system_failure = 2
others = 3.
if sy-subrc <> 0.
raise exception type lcx_registry_lock.
endif.
endmethod. "set_optimistic_lock
*--------------------------------------------------------------------*
* PROMOTE_LOCK - Get exclusive lock just before saving
*--------------------------------------------------------------------*
method promote_lock.
call function 'ENQUEUE_ESINDX'
exporting
mode_indx = 'R'
relid = 'ZR'
srtfd = internal_key
exceptions
foreign_lock = 1
system_failure = 2
others = 3.
if sy-subrc <> 0.
raise exception type lcx_registry_lock.
endif.
endmethod. "promote_lock
*--------------------------------------------------------------------*
* RELEASE_LOCK - called after deleting or before re-acquiring
*--------------------------------------------------------------------*
method release_lock.
call function 'DEQUEUE_ESINDX'
exporting
relid = 'ZR'
srtfd = internal_key.
endmethod. "release_lock
endclass. "lcl_registry_entry IMPLEMENTATION
*&---------------------------------------------------------------------*
*& Report ZUSR_REGISTRY_BROWSER
*&---------------------------------------------------------------------*
*& Viewer and editor for registry stored in INDX
*& (requires ZLIB_REGISTRY)
*&---------------------------------------------------------------------*
* Author: Martin Ceronio (2015), http://ceronio.net
* Released under MIT License: http://opensource.org/licenses/MIT
report zusr_registry_editor.
include zlib_registry.
* For tree control:
data: gr_tree type ref to cl_gui_alv_tree.
data: gr_tree_toolbar type ref to cl_gui_toolbar.
data: gs_node_layout type lvc_s_layn. "Layout for new nodes
* Table for registry entries on tree
types: begin of ts_tab,
key type string,
reg_entry type ref to lcl_registry_entry,
end of ts_tab.
* Container for ALV tree data:
data: gt_tab type table of ts_tab.
* For maintaining registry values in an an entry (ALV control):
data: gr_table type ref to cl_gui_alv_grid.
data: gt_value type standard table of lcl_registry_entry=>ts_keyval.
data: gt_value_ori type standard table of lcl_registry_entry=>ts_keyval. "Original data
* For splitter container
data: gr_splitter type ref to cl_gui_easy_splitter_container.
* For registry access:
data: gr_reg_root type ref to lcl_registry_entry.
data: gr_sel_reg_entry type ref to lcl_registry_entry. "Selected reg. entry
data: gv_sel_node_key type lvc_nkey. "Tree node key of currently selected node
* Single statement to generate a selection screen
parameters: dummy.
*----------------------------------------------------------------------*
* CLASS event_handler DEFINITION
*----------------------------------------------------------------------*
class event_handler definition.
public section.
class-methods:
handle_node_expand for event expand_nc of cl_gui_alv_tree
importing node_key sender,
handle_table_toolbar for event toolbar of cl_gui_alv_grid
importing e_object e_interactive sender,
handle_table_command for event user_command of cl_gui_alv_grid
importing e_ucomm,
handle_node_selected for event selection_changed of cl_gui_alv_tree
importing node_key,
handle_tree_command for event function_selected of cl_gui_toolbar
importing fcode.
* handle_values_changed for event DATA_CHANGE of CL_GUI_ALV_GRID
* importing er_data_changed e_onf4 e_onf4_before e_onf4_after e_ucomm.
endclass. "event_handler DEFINITION
*----------------------------------------------------------------------*
* CLASS event_handler IMPLEMENTATION
*----------------------------------------------------------------------*
class event_handler implementation.
* Handle commands to the tree toolbar
method handle_tree_command.
data: lv_new_key type string.
data: lr_reg_entry type ref to lcl_registry_entry.
data: lv_node_key type lvc_nkey.
data: lv_rc type char1.
data: lv_ntext type lvc_value.
data: ls_tab type ts_tab.
if gr_sel_reg_entry is not bound.
message 'Select a node from the tree first' type 'I'.
return.
endif.
* Create a new node under selected node, or copy a registry node on the same level
if fcode = 'INSE'.
* Dialog to capture name of new node
perform value_input_dialog using 'New registry entry key'(007)
changing lv_new_key lv_rc.
* Add the new key to the current registry entry if the user accepts
if lv_rc = space.
try.
* Update the tree by adding the new node
gr_sel_reg_entry->add_subentry( lv_new_key ).
perform refresh_subnodes using gv_sel_node_key.
catch lcx_registry_entry_exists.
message 'The registry entry already exists'(015) type 'I'.
return.
endtry.
endif.
* Copy the selected node at the same level
elseif fcode = 'COPY'.
* Dialog to capture name of new node
perform value_input_dialog using 'Target registry entry key'(006)
changing lv_new_key lv_rc.
* Perform deep copy of source to target node
if lv_rc = space.
data: lr_parent type ref to lcl_registry_entry.
try.
lr_parent = gr_sel_reg_entry->get_parent( ).
lr_parent->copy_subentry( source_key = gr_sel_reg_entry->entry_id target_key = lv_new_key ).
* Get the parent node in the tree to refresh it
call method gr_tree->get_parent
exporting
i_node_key = gv_sel_node_key
importing
e_parent_node_key = lv_node_key.
* Refresh the parent node
perform refresh_subnodes using lv_node_key.
catch lcx_registry_entry_exists.
message 'The registry entry already exists'(015) type 'I'.
return.
catch lcx_registry_err.
message 'Error updating registry'(017) type 'I'.
return.
endtry.
endif.
* Delete the selected node from the registry
elseif fcode = 'DELE'.
* Prevent deleting of the root entity, which would fail anyway when we try get its parent
if gr_sel_reg_entry->internal_key = lcl_registry_entry=>registry_root.
message 'Root node cannot be deleted' type 'I'.
return.
endif.
call function 'POPUP_TO_CONFIRM'
exporting
titlebar = 'Confirm deletion'(009)
text_question = 'Are you sure you want to delete the selected entry?'(010)
display_cancel_button = abap_false
importing
answer = lv_rc
exceptions
text_not_found = 1
others = 2.
if sy-subrc <> 0.
* Won't happen
endif.
* Check that the user selected OK on the confirmation
check lv_rc = '1'.
lr_reg_entry = gr_sel_reg_entry->get_parent( ).
check lr_reg_entry is bound.
lr_reg_entry->remove_subentry( gr_sel_reg_entry->entry_id ).
* Get the parent node in the tree to refresh it
call method gr_tree->get_parent
exporting
i_node_key = gv_sel_node_key
importing
e_parent_node_key = lv_node_key.
* Refresh the parent node
perform refresh_subnodes using lv_node_key.
endif.
endmethod. "handle_tree_command
* Handle commands on the values table
method handle_table_command.
data: lv_node_key type lvc_nkey.
data: ls_tab type ts_tab.
data: lt_value type lcl_registry_entry=>tt_keyval.
data: ls_value type lcl_registry_entry=>ts_keyval.
* Save current values
if e_ucomm = 'SAVE'.
perform save_values.
endif.
endmethod. "handle_table_command
* Handle selection of a node in the tree
method handle_node_selected.
data: ls_tab type ts_tab.
data: lt_val type lcl_registry_entry=>tt_keyval.
* Check whether data has changed before
data: lv_answer type char01.
data: lv_refresh type char01.
* Check for changed data. The CHECK_CHANGED_DATA() method and
* neither the DATA_CHANGED or DATA_CHANGED_FINISHED
* events of CL_GUI_ALV_GRID seem to fit the bill, so we keep our own copy
* of the original data and compare it
if gr_table is bound.
* Refresh data in local table (GT_VALUE)
call method gr_table->check_changed_data.
if gt_value ne gt_value_ori.
call function 'POPUP_TO_CONFIRM'
exporting
titlebar = 'Confirm data loss'(017)
text_question = 'Data has changed. Save first?'(018)
display_cancel_button = abap_false
importing
answer = lv_answer
exceptions
text_not_found = 1
others = 2.
if sy-subrc <> 0.
* Not going to happen; not using a text
endif.
if lv_answer = '1'. "Save data before moving on
perform save_values.
endif.
endif.
endif.
* Set up the table
if gr_table is not bound.
perform create_table.
endif.
call method gr_tree->get_outtab_line
exporting
i_node_key = node_key
importing
e_outtab_line = ls_tab " Line of Outtab
exceptions
node_not_found = 1
others = 2.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.
* Read the values of the selected registry entry
gt_value = ls_tab-reg_entry->get_values( ).
gt_value_ori = gt_value. "Store last values
* Ensure column widths are correct on every update
* Settings on table
* data: ls_layout type lvc_s_layo.
* ls_layout-cwidth_opt = abap_true.
* gr_table->set_frontend_layout( ls_layout ).
gr_table->refresh_table_display( ).
* Keep track of selected reg. entry for update
gr_sel_reg_entry = ls_tab-reg_entry.
gv_sel_node_key = node_key.
endmethod. "handle_node_selected
* Expand nodes of the registry tree to add sub-entries
method handle_node_expand.
data: lr_reg_entry type ref to lcl_registry_entry.
data: ls_tab type ts_tab.
call method sender->get_outtab_line
exporting
i_node_key = node_key
importing
e_outtab_line = ls_tab " Line of Outtab
exceptions
node_not_found = 1
others = 2.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.
data: lt_sub_entries type lcl_registry_entry=>tt_keyobj.
data: ls_sub_entry type lcl_registry_entry=>ts_keyobj.
data: lv_expander type abap_bool.
data: lv_node_text type lvc_value.
* Add sub-entries to selected node in tree
lt_sub_entries = ls_tab-reg_entry->get_subentries( ).
loop at lt_sub_entries into ls_sub_entry.
lr_reg_entry = ls_sub_entry-value.
perform add_node using node_key lr_reg_entry.
endloop.
endmethod. "EXPAND_EMPTY_FOLDER
* Modify toolbar entries for table/grid
method handle_table_toolbar.
data: ls_tbe type stb_button.
* Keep only the local editing features
loop at e_object->mt_toolbar into ls_tbe.
if ls_tbe-function(7) ne '&LOCAL&'.
delete e_object->mt_toolbar.
endif.
endloop.
* Add a function for saving the values
ls_tbe-function = 'SAVE'.
ls_tbe-icon = '@2L@'.
ls_tbe-butn_type = '0'.
ls_tbe-quickinfo = 'Save'.
append ls_tbe to e_object->mt_toolbar.
endmethod. "handle_table_toolbar
endclass. "event_handler IMPLEMENTATION
*&---------------------------------------------------------------------*
*& Form refresh_subnodes
*&---------------------------------------------------------------------*
* Delete and refresh subnodes of a node
*----------------------------------------------------------------------*
form refresh_subnodes using pv_nkey type lvc_nkey.
data: ls_tab type ts_tab.
data: ls_subentry type lcl_registry_entry=>ts_keyval.
data: lr_reg_entry type ref to lcl_registry_entry.
data: lt_children type lvc_t_nkey.
data: lv_nkey type lvc_nkey.
* Delete subnodes of node. This means: getting all children and deleting
* them individually!
call method gr_tree->get_children
exporting
i_node_key = pv_nkey
importing
et_children = lt_children
exceptions
historic_error = 1
node_key_not_found = 2
others = 3.
if sy-subrc <> 0.
message 'Error building tree'(014) type 'E'.
endif.
loop at lt_children into lv_nkey.
call method gr_tree->delete_subtree
exporting
i_node_key = lv_nkey
i_update_parents_expander = abap_true
exceptions
node_key_not_in_model = 1
others = 2.
if sy-subrc <> 0.
message 'Error building tree'(014) type 'E'.
endif.
endloop.
* With the children deleted, proceed to re-add registry entries
* Get the registry entry on the node
call method gr_tree->get_outtab_line
exporting
i_node_key = pv_nkey
importing
e_outtab_line = ls_tab
exceptions
node_not_found = 1
others = 2.
if sy-subrc ne 0.
message 'Error building tree'(014) type 'E'.
endif.
* Add a subnode for each sub-entry
loop at ls_tab-reg_entry->sub_entries into ls_subentry.
lr_reg_entry = ls_tab-reg_entry->get_subentry( ls_subentry-key ).
perform add_node using pv_nkey lr_reg_entry.
endloop.
* Expand parent node
call method gr_tree->expand_node
exporting
i_node_key = pv_nkey
exceptions
failed = 1
illegal_level_count = 2
cntl_system_error = 3
node_not_found = 4
cannot_expand_leaf = 5
others = 6.
if sy-subrc <> 0.
message 'Error building tree'(014) type 'E'.
endif.
* Update tree display
gr_table->refresh_table_display( ).
endform. "refresh_subnodes
*&---------------------------------------------------------------------*
*& Form save_values
*&---------------------------------------------------------------------*
* Save current values in table to currently selected reg. node
*----------------------------------------------------------------------*
form save_values.
if gr_table is bound and gr_sel_reg_entry is bound.
data: lt_value type lcl_registry_entry=>tt_keyval.
data: ls_value type lcl_registry_entry=>ts_keyval.
* Normalize the values; duplicate keys are overwritten, with possible loss of data!
loop at gt_value into ls_value.
insert ls_value into table lt_value.
endloop.
gr_sel_reg_entry->set_values( lt_value ).
try.
gr_sel_reg_entry->save( ).
catch lcx_registry_lock.
message 'Values have been overwritten since last change and are refreshed'(004) type 'I'.
gr_sel_reg_entry->reload( ).
gt_value = gr_sel_reg_entry->get_values( ).
endtry.
gr_table->refresh_table_display( ).
endif.
endform. "save_values
*&---------------------------------------------------------------------*
*& Form add_node
*&---------------------------------------------------------------------*
* Add single node to tree
*----------------------------------------------------------------------*
* -->PV_NKEY Node of tree to which to add node
* -->PS_TAB Table entry (with reg. entry) to add as child
*----------------------------------------------------------------------*
form add_node
using pv_nkey type lvc_nkey pr_regentry type ref to lcl_registry_entry.
data: lv_node_text type lvc_value.
data: ls_node_layout type lvc_s_layn. "Layout for new nodes
data: ls_tab type ts_tab.
if pr_regentry is not bound.
message 'Error building tree'(014) type 'E'.
endif.
ls_tab-reg_entry = pr_regentry.
* Add node as folder always
ls_node_layout-isfolder = abap_true.
* Add expander only if there are more sub-entries
if lines( pr_regentry->get_subentry_keys( ) ) > 0.
ls_node_layout-expander = abap_true.
else.
ls_node_layout-expander = abap_false.
endif.
lv_node_text = pr_regentry->entry_id.
call method gr_tree->add_node
exporting
i_relat_node_key = pv_nkey
i_relationship = cl_gui_column_tree=>relat_last_child
is_outtab_line = ls_tab
i_node_text = lv_node_text
is_node_layout = ls_node_layout
exceptions
relat_node_not_found = 1
node_not_found = 2
others = 3.
if sy-subrc <> 0.
message 'Error building tree'(014) type 'E'.
endif.
endform. "add_node
*&---------------------------------------------------------------------*
*& Form create_table
*&---------------------------------------------------------------------*
* Initialize table for showing values in a registry entry
*----------------------------------------------------------------------*
form create_table raising cx_salv_msg.
data: lr_func type ref to cl_salv_functions_list.
data: lr_cols type ref to cl_salv_columns_table.
data: lt_fcat type lvc_t_fcat.
data: ls_fcat type lvc_s_fcat.
create object gr_table
exporting
i_parent = gr_splitter->bottom_right_container
i_appl_events = abap_true " Register Events as Application Events
* i_fcat_complete = SPACE " Boolean Variable (X=True, Space=False)
exceptions
error_cntl_create = 1
error_cntl_init = 2
error_cntl_link = 3
error_dp_create = 4
others = 5.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.
* Add fields to catalog
ls_fcat-fieldname = 'KEY'.
ls_fcat-edit = abap_true.
ls_fcat-key = abap_true.
ls_fcat-scrtext_s = 'Key'(001).
ls_fcat-outputlen = 35. "Because colwidth opt is not always great
append ls_fcat to lt_fcat.
ls_fcat-fieldname = 'VALUE'.
ls_fcat-edit = abap_true.
ls_fcat-key = abap_false.
ls_fcat-scrtext_s = 'Value'(002).
ls_fcat-outputlen = 35. "Because colwidth opt is not always great
append ls_fcat to lt_fcat.
call method gr_table->set_table_for_first_display
changing
it_outtab = gt_value[]
it_fieldcatalog = lt_fcat
exceptions
invalid_parameter_combination = 1
program_error = 2
too_many_lines = 3
others = 4.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.
* Toolbar to hold only functions for editing
set handler event_handler=>handle_table_toolbar for gr_table.
set handler event_handler=>handle_table_command for gr_table.
gr_table->set_toolbar_interactive( ).
** Settings on table
* data: ls_layout type lvc_s_layo.
* ls_layout-cwidth_opt = abap_true.
* gr_table->set_frontend_layout( ls_layout ).
endform. "create_table
*&---------------------------------------------------------------------*
*& Form create_tree
*&---------------------------------------------------------------------*
* Initialize tree showing the registry hierarchy
*----------------------------------------------------------------------*
form create_tree.
data: lt_fcat type lvc_t_fcat.
data: ls_fcat type lvc_s_fcat.
data: lt_event type cntl_simple_events,
ls_event type cntl_simple_event.
gr_reg_root = lcl_registry_entry=>get_root( ).
* Create tree
create object gr_tree
exporting
parent = gr_splitter->top_left_container
node_selection_mode = cl_gui_column_tree=>node_sel_mode_single
item_selection = abap_false
no_toolbar = abap_false
no_html_header = abap_true
exceptions
cntl_error = 1
cntl_system_error = 2
create_error = 3
lifetime_error = 4
illegal_node_selection_mode = 5
failed = 6
illegal_column_name = 7
others = 8.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.
* Add key so that there is *something* in the field catalog
ls_fcat-fieldname = 'KEY'.
ls_fcat-no_out = abap_true.
append ls_fcat to lt_fcat.
call method gr_tree->set_table_for_first_display
changing
it_outtab = gt_tab
it_fieldcatalog = lt_fcat.
* Get handle on tree toolbar
data: lt_ttb type ttb_button.
data: ls_ttb type stb_button.
gr_tree->get_toolbar_object( importing er_toolbar = gr_tree_toolbar ).
gr_tree_toolbar->delete_all_buttons( ).
* Add custom buttons for registry entry operations
ls_ttb-function = 'INSE'. "Insert entry
ls_ttb-icon = '@17@'. "ICON_INSERT_ROW
append ls_ttb to lt_ttb.
ls_ttb-function = 'DELE'. "Delete entry
ls_ttb-icon = '@18@'. "ICON_DELETE_ROW
append ls_ttb to lt_ttb.
ls_ttb-function = 'COPY'. "Copy Entry
ls_ttb-icon = '@14@'. "ICON_COPY_OBJECT
append ls_ttb to lt_ttb.
call method gr_tree_toolbar->add_button_group
exporting
data_table = lt_ttb
exceptions
dp_error = 1
cntb_error_fcode = 2
others = 3.
if sy-subrc <> 0.
message 'Error when setting up registry toolbar'(005) type 'E'.
endif.
* Add root node
perform add_node using '' gr_reg_root.
* Register events and set handlers
* ls_event-eventid = cl_gui_simple_tree=>eventid_node_double_click.
ls_event-eventid = cl_gui_simple_tree=>eventid_selection_changed.
ls_event-appl_event = 'X'.
append ls_event to lt_event.
ls_event-eventid = cl_gui_simple_tree=>eventid_expand_no_children.
ls_event-appl_event = 'X'.
append ls_event to lt_event.
call method gr_tree->set_registered_events
exporting
events = lt_event
exceptions
cntl_error = 1
cntl_system_error = 2
illegal_event_combination = 3.
if sy-subrc <> 0.
message id sy-msgid type sy-msgty number sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
endif.
set handler event_handler=>handle_node_expand for gr_tree.
set handler event_handler=>handle_node_selected for gr_tree.
set handler event_handler=>handle_tree_command for gr_tree_toolbar.
call method gr_tree->frontend_update.
endform. "create_tree
*&---------------------------------------------------------------------*
*& Form value_input_dialog
*&---------------------------------------------------------------------*
* Get single value from user
*----------------------------------------------------------------------*
form value_input_dialog using title changing value returncode.
data: lt_fld type table of sval.
data: ls_fld type sval.
ls_fld-tabname = 'OJFIELDS'.
ls_fld-fieldname = 'INPUT'.
append ls_fld to lt_fld.
call function 'POPUP_GET_VALUES'
exporting
no_value_check = abap_true
popup_title = title
importing
returncode = returncode
tables
fields = lt_fld
exceptions
error_in_fields = 1
others = 2.
if sy-subrc <> 0.
message 'Error during request for value'(008) type 'E'.
endif.
read table lt_fld into ls_fld index 1.
value = ls_fld-value.
endform. "value_input_dialog
start-of-selection.
at selection-screen output.
* Disable Execute and Save functions on report selection screen
perform insert_into_excl(rsdbrunt) using 'ONLI'.
perform insert_into_excl(rsdbrunt) using 'SPOS'.
* Initialize the display on the first dynpro roundtrip
if gr_splitter is not bound.
data: gv_dynnr type sydynnr.
data: gv_repid type syrepid.
gv_dynnr = sy-dynnr.
gv_repid = sy-repid.
create object gr_splitter
exporting
link_dynnr = gv_dynnr
link_repid = gv_repid
parent = cl_gui_easy_splitter_container=>default_screen
orientation = 1 " Orientation: 0 = Vertical, 1 = Horizontal
sash_position = 30 " Position of Splitter Bar (in Percent)
with_border = 0 " With Border = 1; Without Border = 0
exceptions
cntl_error = 1
cntl_system_error = 2
others = 3.
if sy-subrc <> 0.
exit.
endif.
perform create_tree.
* Table creation is deferred until the first node is selected
endif.
*&---------------------------------------------------------------------*
*& Report ZUSR_REGISTRY_EXAMPLE
*&
*&---------------------------------------------------------------------*
*& Example program to demonstrate the usage of the API for the registry.
*&---------------------------------------------------------------------*
* Author: Martin Ceronio (2015), http://ceronio.net
* Released under MIT License: http://opensource.org/licenses/MIT
report zusr_registry_example.
* Make the registry API available to our program
include zlib_registry.
data: reg_root type ref to lcl_registry_entry.
data: reg_entry type ref to lcl_registry_entry.
data: lv_customer type kunnr.
data: lv_run_date type d.
data: lv_timestamp type timestamp.
start-of-selection.
* Get the root entry of the registry
reg_root = lcl_registry_entry=>get_root( ).
* If we want to ensure, on startup, that a certain entry exists, we
* could do the following (e.g. in LOAD-OF-PROGRAM):
reg_root->create_by_path( 'Sales/Enhancements/Process_XYZ' ).
* Retrieval of a specific entry. If we did not have the above line,
* we would have to check that the result of each call to GET_SUBENTRY( )
* to ensure it is bound.
reg_entry = reg_root->get_subentry( 'Sales' )->get_subentry( 'Enhancements' )->get_subentry( 'Process_XYZ' ).
* Getting a specific value from the entry:
lv_customer = reg_entry->get_value( 'ProcessCustomer' ).
* Writing values to the entry:
lv_run_date = sy-datum.
reg_entry->set_value( key = 'LastRunDate' value = lv_run_date ).
get time stamp field lv_timestamp.
reg_entry->set_value( key = 'LastRunDateTime' value = lv_timestamp ).
* Saving the entry
reg_entry->save( ).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.