Last active
November 6, 2019 21:29
-
-
Save mydoghasworms/08ea60e95dd1fa90c90a to your computer and use it in GitHub Desktop.
ABAP arbitrary value store (registry) as seen at http://ceronio.net/2015/05/arbitrary-value-store-registry-for-abap/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*&---------------------------------------------------------------------* | |
*& 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*&---------------------------------------------------------------------* | |
*& 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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*&---------------------------------------------------------------------* | |
*& 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