"! SAPilot Operation: admin "! Manages the object whitelist registry via REST API. "! "! Endpoints: "! POST /admin/objects — Register new object "! POST /admin/objects/{type}/{name} — Update object metadata "! DELETE /admin/objects/{type}/{name} — Delete object from registry "! POST /admin/objects/{type}/{name}/active — Toggle active status "! "! All operations require SAP administrator authority. CLASS zcl_sapilot_op_admin DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. METHODS execute IMPORTING iv_operation TYPE string iv_object_type TYPE string OPTIONAL iv_object_name TYPE string OPTIONAL iv_request_body TYPE string OPTIONAL RETURNING VALUE(rv_json) TYPE string RAISING zcx_sapilot_error. PRIVATE SECTION. METHODS check_admin_authority RAISING zcx_sapilot_error. METHODS register_object IMPORTING iv_request_body TYPE string RETURNING VALUE(rv_json) TYPE string RAISING zcx_sapilot_error. METHODS update_object IMPORTING iv_object_type TYPE string iv_object_name TYPE string iv_request_body TYPE string RETURNING VALUE(rv_json) TYPE string RAISING zcx_sapilot_error. METHODS delete_object IMPORTING iv_object_type TYPE string iv_object_name TYPE string RETURNING VALUE(rv_json) TYPE string RAISING zcx_sapilot_error. METHODS toggle_active IMPORTING iv_object_type TYPE string iv_object_name TYPE string iv_request_body TYPE string RETURNING VALUE(rv_json) TYPE string RAISING zcx_sapilot_error. METHODS extract_json_field IMPORTING iv_json TYPE string iv_field TYPE string RETURNING VALUE(rv_value) TYPE string. METHODS build_result_json IMPORTING iv_action TYPE string iv_type TYPE string iv_name TYPE string RETURNING VALUE(rv_json) TYPE string. ENDCLASS. CLASS zcl_sapilot_op_admin IMPLEMENTATION. METHOD execute. " ----------------------------------------------------------------- " Admin operation dispatcher " ----------------------------------------------------------------- check_admin_authority( ). CASE iv_operation. WHEN zcl_sapilot_constants=>gc_op_admin_reg. rv_json = register_object( iv_request_body = iv_request_body ). WHEN zcl_sapilot_constants=>gc_op_admin_upd. rv_json = update_object( iv_object_type = iv_object_type iv_object_name = iv_object_name iv_request_body = iv_request_body ). WHEN zcl_sapilot_constants=>gc_op_admin_del. rv_json = delete_object( iv_object_type = iv_object_type iv_object_name = iv_object_name ). WHEN zcl_sapilot_constants=>gc_op_admin_act. rv_json = toggle_active( iv_object_type = iv_object_type iv_object_name = iv_object_name iv_request_body = iv_request_body ). WHEN OTHERS. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 400 iv_error_code = 'UNKNOWN_ADMIN_OPERATION' iv_message = |Unknown admin operation: { iv_operation }|. ENDCASE. ENDMETHOD. METHOD check_admin_authority. " ----------------------------------------------------------------- " Verify the current user has SAP development/admin authority " Check S_DEVELOP with object type 'ZS' (custom namespace) " ----------------------------------------------------------------- AUTHORITY-CHECK OBJECT 'S_DEVELOP' ID 'DEVCLASS' DUMMY ID 'OBJTYPE' FIELD 'ZS' ID 'OBJNAME' DUMMY ID 'P_GROUP' DUMMY ID 'ACTVT' FIELD '02'. IF sy-subrc <> 0. " Fallback: check if user has S_RFC with * AUTHORITY-CHECK OBJECT 'S_RFC' ID 'RFC_TYPE' FIELD '*' ID 'RFC_NAME' FIELD '*'. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 403 iv_error_code = 'ADMIN_ACCESS_DENIED' iv_message = |User { sy-uname } does not have admin authority|. ENDIF. ENDIF. ENDMETHOD. METHOD register_object. " ----------------------------------------------------------------- " POST /admin/objects " Body: {"type": "RFC", "name": "BAPI_USER_GET_DETAIL", "category": "...", "description": "..."} " Actual ZSAPILOT_OBJ fields: object_type, object_name, category, " description, http_method, status " ----------------------------------------------------------------- IF iv_request_body IS INITIAL. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 400 iv_error_code = 'EMPTY_BODY' iv_message = |Request body is required. Expected: \{"type", "name"\}|. ENDIF. DATA(lv_type) = extract_json_field( iv_json = iv_request_body iv_field = 'type' ). DATA(lv_name) = extract_json_field( iv_json = iv_request_body iv_field = 'name' ). IF lv_type IS INITIAL OR lv_name IS INITIAL. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 400 iv_error_code = 'MISSING_REQUIRED_FIELD' iv_message = |Both 'type' and 'name' are required|. ENDIF. lv_type = to_upper( lv_type ). lv_name = to_upper( lv_name ). " Validate object type IF lv_type <> 'RFC' AND lv_type <> 'CDS' AND lv_type <> 'ODATA' AND lv_type <> 'CLAS'. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 400 iv_error_code = 'INVALID_OBJECT_TYPE' iv_message = |Object type '{ lv_type }' is not supported. Use: RFC, CDS, ODATA, CLAS|. ENDIF. " Check if already registered (old OpenSQL for NW 7.40) DATA: lv_exists TYPE zsapilot_object_name. SELECT SINGLE object_name FROM zsapilot_obj WHERE object_type = lv_type AND object_name = lv_name INTO lv_exists. IF sy-subrc = 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 409 iv_error_code = 'OBJECT_ALREADY_REGISTERED' iv_message = |Object { lv_type }/{ lv_name } is already registered|. ENDIF. " Build registry entry DATA: ls_registry TYPE zsapilot_obj. ls_registry-object_type = lv_type. ls_registry-object_name = lv_name. ls_registry-category = extract_json_field( iv_json = iv_request_body iv_field = 'category' ). ls_registry-description = extract_json_field( iv_json = iv_request_body iv_field = 'description' ). ls_registry-status = 'ACTIVE'. " Determine default HTTP method based on type CASE lv_type. WHEN 'RFC' OR 'CLAS'. ls_registry-http_method = 'POST'. WHEN OTHERS. ls_registry-http_method = 'GET'. ENDCASE. INSERT zsapilot_obj FROM ls_registry. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 500 iv_error_code = 'REGISTER_FAILED' iv_message = |Failed to register object { lv_type }/{ lv_name }|. ENDIF. rv_json = build_result_json( iv_action = 'registered' iv_type = lv_type iv_name = lv_name ). ENDMETHOD. METHOD update_object. " ----------------------------------------------------------------- " POST /admin/objects/{type}/{name} (acting as PUT) " Body: {"category": "...", "description": "..."} " ----------------------------------------------------------------- DATA: lv_otype TYPE zsapilot_object_type_de, lv_oname TYPE zsapilot_object_name. lv_otype = iv_object_type. lv_oname = iv_object_name. " Verify object exists (old OpenSQL) DATA: ls_registry TYPE zsapilot_obj. SELECT SINGLE * FROM zsapilot_obj WHERE object_type = lv_otype AND object_name = lv_oname INTO ls_registry. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 404 iv_error_code = 'OBJECT_NOT_FOUND' iv_message = |Object { iv_object_type }/{ iv_object_name } not found|. ENDIF. " Update fields from request body IF iv_request_body IS NOT INITIAL. DATA(lv_category) = extract_json_field( iv_json = iv_request_body iv_field = 'category' ). DATA(lv_description) = extract_json_field( iv_json = iv_request_body iv_field = 'description' ). IF lv_category IS NOT INITIAL. ls_registry-category = lv_category. ENDIF. IF lv_description IS NOT INITIAL. ls_registry-description = lv_description. ENDIF. ENDIF. UPDATE zsapilot_obj FROM ls_registry. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 500 iv_error_code = 'UPDATE_FAILED' iv_message = |Failed to update object { iv_object_type }/{ iv_object_name }|. ENDIF. rv_json = build_result_json( iv_action = 'updated' iv_type = iv_object_type iv_name = iv_object_name ). ENDMETHOD. METHOD delete_object. " ----------------------------------------------------------------- " DELETE /admin/objects/{type}/{name} " ----------------------------------------------------------------- DATA: lv_otype TYPE zsapilot_object_type_de, lv_oname TYPE zsapilot_object_name. lv_otype = iv_object_type. lv_oname = iv_object_name. DATA: lv_exists TYPE zsapilot_object_name. SELECT SINGLE object_name FROM zsapilot_obj WHERE object_type = lv_otype AND object_name = lv_oname INTO lv_exists. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 404 iv_error_code = 'OBJECT_NOT_FOUND' iv_message = |Object { iv_object_type }/{ iv_object_name } not found|. ENDIF. " Delete associated field records first (old OpenSQL) DELETE FROM zsapilot_objf WHERE object_type = lv_otype AND object_name = lv_oname. " Delete the main record DELETE FROM zsapilot_obj WHERE object_type = lv_otype AND object_name = lv_oname. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 500 iv_error_code = 'DELETE_FAILED' iv_message = |Failed to delete object { iv_object_type }/{ iv_object_name }|. ENDIF. rv_json = build_result_json( iv_action = 'deleted' iv_type = iv_object_type iv_name = iv_object_name ). ENDMETHOD. METHOD toggle_active. " ----------------------------------------------------------------- " POST /admin/objects/{type}/{name}/active " Body: {"active": true} or {"active": false} " Uses 'status' field: 'ACTIVE' or 'INACTIVE' " ----------------------------------------------------------------- DATA: lv_otype TYPE zsapilot_object_type_de, lv_oname TYPE zsapilot_object_name. lv_otype = iv_object_type. lv_oname = iv_object_name. DATA: ls_registry TYPE zsapilot_obj. SELECT SINGLE * FROM zsapilot_obj WHERE object_type = lv_otype AND object_name = lv_oname INTO ls_registry. IF sy-subrc <> 0. RAISE EXCEPTION TYPE zcx_sapilot_error EXPORTING iv_http_status = 404 iv_error_code = 'OBJECT_NOT_FOUND' iv_message = |Object { iv_object_type }/{ iv_object_name } not found|. ENDIF. " Parse active flag from body IF iv_request_body IS NOT INITIAL. DATA(lv_active) = extract_json_field( iv_json = iv_request_body iv_field = 'active' ). IF lv_active = 'true' OR lv_active = 'X'. ls_registry-status = 'ACTIVE'. ELSE. ls_registry-status = 'INACTIVE'. ENDIF. ELSE. " Toggle: if currently active, deactivate; if inactive, activate IF ls_registry-status = 'ACTIVE'. ls_registry-status = 'INACTIVE'. ELSE. ls_registry-status = 'ACTIVE'. ENDIF. ENDIF. UPDATE zsapilot_obj FROM ls_registry. DATA: lv_action TYPE string. IF ls_registry-status = 'ACTIVE'. lv_action = 'activated'. ELSE. lv_action = 'deactivated'. ENDIF. rv_json = build_result_json( iv_action = lv_action iv_type = iv_object_type iv_name = iv_object_name ). ENDMETHOD. METHOD extract_json_field. " ----------------------------------------------------------------- " Simple JSON field extractor: find "field": "value" or "field": value " Uses CONCATENATE to avoid backslash in string templates (NW 7.40) " ----------------------------------------------------------------- IF iv_json IS INITIAL OR iv_field IS INITIAL. RETURN. ENDIF. DATA: lv_bs TYPE string. FIELD-SYMBOLS: TYPE x. DATA: lv_bs_c TYPE c LENGTH 1. lv_bs_c = cl_abap_char_utilities=>horizontal_tab. ASSIGN lv_bs_c TO CASTING. = '5C'. lv_bs = lv_bs_c. " Pattern: "field": "value" DATA: lv_ws TYPE string. lv_ws = lv_bs && 's' && lv_bs && '*'. DATA(lv_field_l) = to_lower( iv_field ). CONCATENATE '"' lv_field_l '"' lv_ws ':' lv_ws '*"([^"]*)"' INTO DATA(lv_pattern). FIND REGEX lv_pattern IN to_lower( iv_json ) SUBMATCHES DATA(lv_val). IF sy-subrc = 0 AND lv_val IS NOT INITIAL. rv_value = lv_val. RETURN. ENDIF. " Pattern: "field": value (unquoted: true, false, number) CONCATENATE '"' lv_field_l '"' lv_ws ':' lv_ws '*([a-z0-9]+)' INTO lv_pattern. FIND REGEX lv_pattern IN to_lower( iv_json ) SUBMATCHES lv_val. IF sy-subrc = 0. rv_value = lv_val. ENDIF. ENDMETHOD. METHOD build_result_json. " ----------------------------------------------------------------- " Build a simple result JSON: {"action": "...", "type": "...", "name": "..."} " ----------------------------------------------------------------- rv_json = '{"action": "' && iv_action && '", "type": "' && iv_type && '", "name": "' && iv_name && '"}'. ENDMETHOD. ENDCLASS.