* Class : zcl_validator_rest_handler * Title : The REST handler for SICF services - validations * Description : "! This is the service resource class for validation requests via REST service calls (POST). "! It uses header parameters CLASS, GROUP and PROCESS (and the optional LANGUAGE), it takes "! any JSON content provided and it involves customised checks to compose the response. "! The response is either errors=0 (which means, continue with the next service call) or "! error (which means: stop here - and focus on the errors). CLASS zcl_validator_rest_resource DEFINITION PUBLIC INHERITING FROM cl_rest_resource FINAL CREATE PUBLIC . PUBLIC SECTION. "! The class prefix will be applied for the execution of check methods. CONSTANTS co_class_prefix TYPE string VALUE 'ZCL_VALIDATOR_'. TYPES: BEGIN OF gty_check_credentials, _class TYPE c LENGTH 20, _group TYPE c LENGTH 20, _process TYPE c LENGTH 20, _method TYPE c LENGTH 20, END OF gty_check_credentials. TYPES: BEGIN OF gty_validation_message, _number TYPE int2, _msg_code TYPE c LENGTH 30, _message TYPE char120, END OF gty_validation_message. TYPES gty_validation_messages TYPE SORTED TABLE OF gty_validation_message WITH UNIQUE KEY _number. TYPES: BEGIN OF gty_check_result, _method TYPE c LENGTH 20, _messages TYPE gty_validation_messages, END OF gty_check_result. TYPES gty_check_results TYPE SORTED TABLE OF gty_check_result WITH UNIQUE KEY _method. TYPES: BEGIN OF gty_json_response, _errors TYPE int1, _check_results TYPE gty_check_results, _class TYPE c LENGTH 20, _group TYPE c LENGTH 20, _process TYPE c LENGTH 20, _system TYPE c LENGTH 20, _language TYPE sy-langu, END OF gty_json_response. CLASS-DATA gt_check_methods TYPE STANDARD TABLE OF gty_check_credentials. CLASS-DATA go_i18n TYPE REF TO zcl_i18n. CLASS-METHODS class_constructor. "Adds a message to the rt_validation_messages returning parameter. CLASS-METHODS set_message IMPORTING iv_code TYPE char20 iv_par TYPE any DEFAULT '' iv_par2 TYPE any DEFAULT '' iv_par3 TYPE any DEFAULT '' io_i18n TYPE REF TO zcl_i18n CHANGING ct_validation_messages TYPE gty_validation_messages. METHODS if_rest_resource~get REDEFINITION . METHODS if_rest_resource~post REDEFINITION . PRIVATE SECTION. CLASS-METHODS set_error_status IMPORTING is_response TYPE REF TO if_rest_response iv_code TYPE i iv_reason TYPE string. ENDCLASS. CLASS zcl_validator_rest_resource IMPLEMENTATION. METHOD class_constructor. "Internationalization on the settings for the validator - purpose: reporting only. go_i18n = zcl_i18n=>get_instance( 'rest_validator' ). go_i18n->feed( VALUE #( ( id = 'class.CONTRACT' nl = 'Contract' en = 'Contract' ) ( id = 'class.PERSON' nl = 'Persoon' en = 'Person' ) ( id = 'group.GENERAL' nl = 'Algemeen' en = 'General' ) ( id = 'process.READ' nl = 'Inzien' en = 'Read' ) ) ). "List of supported checks gt_check_methods = VALUE #( ( _class = 'CONTRACT' _group = 'GENERAL' _process = 'READ' _method = 'REQUESTEDBY' ) ( _class = 'PERSON' _group = 'GENERAL' _process = 'READ' _method = 'REQUESTEDBY_MOVE' ) ( _class = 'PERSON' _group = 'GENERAL' _process = 'READ' _method = 'NSR' ) ). ENDMETHOD. METHOD if_rest_resource~get. ENDMETHOD. METHOD if_rest_resource~post. DATA: BEGIN OF ls_seoclass, class TYPE seoclass-clsname, method TYPE c LENGTH 32, END OF ls_seoclass. DATA lr_data TYPE REF TO data. DATA lr_descr TYPE REF TO cl_abap_datadescr. DATA lo_validation_object TYPE REF TO object. DATA lt_validation_messages TYPE gty_validation_messages. DATA lt_check_results TYPE gty_check_results. DATA ls_check_results TYPE gty_check_result. DATA ls_json_response TYPE gty_json_response. FIELD-SYMBOLS: TYPE any. CLEAR ls_json_response. ls_json_response-_class = to_upper( mo_request->get_header_field( 'CLASS' ) ). ls_json_response-_group = to_upper( mo_request->get_header_field( 'GROUP' ) ). ls_json_response-_process = to_upper( mo_request->get_header_field( 'PROCESS' ) ). ls_json_response-_system = |{ sy-sysid } { sy-mandt } { sy-host }|. "Support for the language of return messages ls_json_response-_language = mo_request->get_header_field( 'LANGUAGE' ). ls_json_response-_language = COND #( WHEN ls_json_response-_language IS INITIAL THEN sy-langu ELSE ls_json_response-_language ). "Further determination logic for the language - the communication language of the requestedBy "... "Ensure the header credentials are all available IF ls_json_response-_class IS INITIAL OR ls_json_response-_group IS INITIAL OR ls_json_response-_process IS INITIAL. set_error_status( is_response = mo_response iv_code = 400 iv_reason = 'Missing data - header (class, group, process)' ). RETURN. ENDIF. "Ensure the header credentials are valid - at least 1 check module should be available IF NOT line_exists( gt_check_methods[ _class = ls_json_response-_class _group = ls_json_response-_group _process = ls_json_response-_process ] ). set_error_status( is_response = mo_response iv_code = 400 iv_reason = |Invalid combination of credentials (class={ ls_json_response-_class }, group={ ls_json_response-_group }, process={ ls_json_response-_process })| ). RETURN. ENDIF. "Get the JSON body from the request DATA(lo_entity) = mo_request->get_entity( ). DATA(lv_json) = lo_entity->get_string_data( ). IF lv_json IS INITIAL. set_error_status( is_response = mo_response iv_code = 400 iv_reason = 'Missing data - No JSON content (body)' ). RETURN. ENDIF. TRY. cl_abap_typedescr=>describe_by_name( EXPORTING p_name = |{ co_class_prefix }{ ls_json_response-_class }=>GTY_REQUEST_CONTENT| RECEIVING p_descr_ref = DATA(lo_descr) EXCEPTIONS type_not_found = 4 ). IF sy-subrc = 4. set_error_status( is_response = mo_response iv_code = 400 iv_reason = |Class { co_class_prefix }{ ls_json_response-_class } unavailable| ). RETURN. ENDIF. lr_descr ?= lo_descr. CREATE DATA lr_data TYPE HANDLE lr_descr. ASSIGN lr_data->* TO . "Map the JSON content onto the ABAP content /ui2/cl_json=>deserialize( EXPORTING json = lv_json pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = ). IF IS INITIAL. set_error_status( is_response = mo_response iv_code = 400 iv_reason = |Faulty data - no valid content (body) - JSON structured ok ? | ). RETURN. ENDIF. CATCH cx_sy_move_cast_error INTO DATA(lx). set_error_status( is_response = mo_response iv_code = 400 iv_reason = |{ lx->get_text( ) } (internal error)| ). RETURN. ENDTRY. ls_seoclass-class = |{ co_class_prefix }{ ls_json_response-_class }|. TRY. "Create an instance of the validation class - and set it's is_request_content "attribute with the data from *================================================================ CREATE OBJECT lo_validation_object TYPE (ls_seoclass-class) EXPORTING is_request_content = iv_language = ls_json_response-_language. *================================================================ "Execute validations for the group and process LOOP AT gt_check_methods INTO DATA(ls_check_method) WHERE _class = ls_json_response-_class AND _group = ls_json_response-_group AND _process = ls_json_response-_process. ls_seoclass-method = |CHECK_{ ls_check_method-_method }|. *================================================================ CALL METHOD lo_validation_object->(ls_seoclass-method) RECEIVING rt_validation_messages = lt_validation_messages. *================================================================ "Merge the messages into the grand message list. IF lines( lt_validation_messages ) > 0. ls_check_results-_method = ls_check_method-_method. ls_check_results-_messages = lt_validation_messages. INSERT ls_check_results INTO TABLE lt_check_results. "Count the number of error messages * ls_json_response-errors = ls_json_response-errors + lines( lt_validation_messages ). "Count the number of errored checks ls_json_response-_errors = ls_json_response-_errors + 1. ENDIF. ENDLOOP. CATCH cx_sy_create_object_error cx_sy_dyn_call_illegal_method INTO DATA(lx2). set_error_status( is_response = mo_response iv_code = 500 iv_reason = |{ lx2->get_text( ) } (internal error)| ). RETURN. ENDTRY. ls_json_response-_check_results = lt_check_results. "Map the ABAP results onto JSON content and set response content type and content mo_response->set_header_field( iv_name = 'Content-Type' iv_value = 'application/json' ). mo_response->create_entity( )->set_string_data( /ui2/cl_json=>serialize( data = ls_json_response ts_as_iso8601 = abap_true pretty_name = /ui2/cl_json=>pretty_mode-camel_case ) ). "Log faulty calls - when sensorboard sensortype is listening TRY. "Why the dynamic calls ? This service should also work without the sensorboard. "Feel free to remove it completely - if the sensorboard is not going to be available SELECT COUNT( * ) FROM ('ZSENSORS') WHERE sensor = 'VALIDATOR' AND status <> 'X' INTO @DATA(lv_counter). IF lv_counter > 0. CALL METHOD ('ZCL_SENSORS_VALIDATOR')=>('LOG_FAILED_VALIDATION') EXPORTING iv_credentials = |{ ls_json_response-_class }/{ ls_json_response-_group }/{ ls_json_response-_process }| iv_json = lv_json. ENDIF. CATCH cx_sy_dynamic_osql_semantics cx_sy_dyn_call_illegal_class cx_sy_dyn_call_illegal_method. "Sensorboard functionality unavailable ENDTRY. ENDMETHOD. METHOD set_error_status. is_response->set_status( iv_status = iv_code iv_reason_phrase = iv_reason ). "For production systems - the error message is reset to 'Internal error (123#)' SELECT SINGLE cccategory FROM t000 INTO @DATA(lv_category) WHERE mandt = @sy-mandt. IF lv_category = 'P'. "Production system is_response->create_entity( )->set_string_data( 'Internal error (#123)' ). ELSE. is_response->create_entity( )->set_string_data( iv_reason ). ENDIF. ENDMETHOD. METHOD set_message. INSERT VALUE #( _number = lines( ct_validation_messages ) + 1 _msg_code = iv_code _message = io_i18n->get( iv_id = iv_code iv_par = iv_par iv_par2 = iv_par2 iv_par3 = iv_par3 ) ) INTO TABLE ct_validation_messages. ENDMETHOD. ENDCLASS.