ABAP code for implementation of ServiceNow alert

In the previous blog on the ServiceNow outbound integration connection we deomonstrated the of the connection. This blog will explain the technical ABAP implementation.

The implementation below is based on the Restful implementation using the ServiceNow midserver concept. At the end the basic steps are explained if you want to use the ServiceNow webservice implementation.

Questions that will be answered in this blog are:

  • How do I connect from the ABAP stack towards the midserver?
  • Which BADI do I need to activate for the outbound integration?
  • How do I call the midserver connection from the BADI?
  • How do I deal with the differences in severity definition between ServiceNow and SAP Focused Run?
  • If I want to set up the connection via web services, what do I need to do?
  • How can I include application logging in such a way that I can monitor the calls and issues in SLG1?

Set up the RFC destination

In SM59 setup the RFC connection towards the MID server as type H RFC connection:

Activation of the enhancement spot and BADI

The details of the enhancement spot and BADI implementation are in the SAP document published on the SAP Focused Run Expert Portal.

Use transaction SE18 or SE80 to activate enhancement spot ACC_REACTION_EXTERNAL and then activate BADI BADI_ACC_REACTION_EXT.

The result looks as follows:

Double click on the implementation:

Double click on the REACT_TO_ALERT interface to go to the code. The code we implemented looks as below:

  METHOD if_acc_reaction_ext~react_to_alert.

    IF is_alert-ref->get_type( ) <> 'ALERT'.
      RETURN.
    ENDIF.

    " Init. the application log.
    me->zgo_logger = NEW zcl_snow_bi_logger( ).

    " Add info message to notify reaction was triggered
    me->zgo_logger->bal_log_add_message(
      EXPORTING
        ziv_msgty = zif_snow_constants=>zgc_message_types-info
        ziv_msgno = '002'
    ).

    " Send message to service now
    NEW zcl_snow_bi_common( )->zif_snow_bi_common~send_message_to_snow(
      EXPORTING
        zii_logger              = me->zgo_logger
        zii_snow_message        = NEW zcl_snow_event_message( zii_alert = is_alert-ref  )
        ziv_resolution_state    = zif_snow_constants=>zgc_resolution_state-new
    ).

    " Save the application log
    me->zgo_logger->bal_log_save( ).

  ENDMETHOD.

What do we do in the code:

  1. We only react on type ALERT
  2. We make an entry in the application log (so we can check later on in SLG1)
  3. We call the actual interface which we have implemented in class ZCL_SNOW_BI_COMMON class

The implementation code

The sending code in method ZIF_SNOW_BI_COMMON~SEND_MESSAGE_TO_SNOW that was just called looks as follows:

  METHOD zif_snow_bi_common~send_message_to_snow.

    " Retrieve JSON body for the request
    DATA(zlv_snow_event_json) = zii_snow_message->get_json( ziv_resolution_state ).

    me->add_json_to_bal_log( EXPORTING zii_logger   = zii_logger
                                       ziv_json     = zlv_snow_event_json ).


    TRY.
        " Execute HTTP Post, send data to the MiD API
        DATA(zlv_http_status_code) = NEW zcl_snow_mid_api( )->post( ziv_event_messages = zlv_snow_event_json ).

        " Add HTTP response code to the log..
        IF zlv_http_status_code-code >= 200 AND zlv_http_status_code-code < 300.
          DATA(zlv_msgty) = zif_snow_constants=>zgc_message_types-success.
        ELSE.
          zlv_msgty = zif_snow_constants=>zgc_message_types-error.
        ENDIF.

        zii_logger->bal_log_add_message(
           EXPORTING
             ziv_msgty = zlv_msgty
             ziv_msgno = '001'
             ziv_msgv1 = |{ zlv_http_status_code-code } { zlv_http_status_code-reason }|
         ).

      CATCH zcx_snow_mid_api INTO DATA(zlo_exception).
        "Add exception to the application log
        zii_logger->bal_log_add_message(
          EXPORTING
            ziv_msgid = zlo_exception->if_t100_message~t100key-msgid
            ziv_msgno = zlo_exception->if_t100_message~t100key-msgno
            ziv_msgv1 = CONV #( zlo_exception->if_t100_message~t100key-attr1 )
            ziv_msgv2 = CONV #( zlo_exception->if_t100_message~t100key-attr2 )
            ziv_msgv3 = CONV #( zlo_exception->if_t100_message~t100key-attr3 )
            ziv_msgv4 = CONV #( zlo_exception->if_t100_message~t100key-attr4 )
        ).
    ENDTRY.

  ENDMETHOD.

What happens here:

  1. Data object is build in the data definition (details follow below)
  2. This is logged
  3. The actual call is performed by calling class ZCL_SNOW_MID_API (details follow below)
  4. The result is checked (200 is http code for Ok)
  5. Error result is logged in case of issues

The code for the message content

For the message content, we first define the message event type:

INTERFACE zif_snow_message
  PUBLIC .

  TYPES:
    BEGIN OF zgts_event,
      "! Source
      source           TYPE string,
      "! Name of the object
      node             TYPE string,
      "! Type of object, host, instance
      type             TYPE string,
      "! Severity
      severity         TYPE string,
      "! Date/Time(YYYY-MM-DD HH:MM:SS)
      time_of_event    TYPE string,
      "! Alert description/name
      description      TYPE string,
      "! SAP System ID
      event_class      TYPE string,
      "! Unique ID
      message_key      TYPE string,
      "! Alert state
      resolution_state TYPE string,
      "! Resource
      resource         TYPE string,
    END OF zgts_event.

  METHODS get_json  IMPORTING ziv_resoultion_state TYPE string
                    RETURNING VALUE(zrv_json)      TYPE /ui2/cl_json=>json.

ENDINTERFACE.

This event is used in the actual message build code:

  METHOD zif_snow_message~get_json.

    DATA zlv_events TYPE /ui2/cl_json=>json.

    " Get current time stamp
    GET TIME STAMP FIELD DATA(zlv_time_stamp_now).

    DATA(zlv_event_json) = /ui2/cl_json=>serialize(
      EXPORTING
        data        =  VALUE zif_snow_message~zgts_event(
          source              = |{ syst-sysid } - FRUN |
          node                = zgi_alert->get_managed_object_name( )
          type                = zgi_alert->get_managed_object_type( )
          severity            = me->convert_severity( zgi_alert->get_severity( ) )
          time_of_event       = me->convert_alert_timestamp( ziv_timestamp = zlv_time_stamp_now )
          description         = COND #( LET custom_description = me->remove_html_tags( zgi_alert->get_custom_description( ) ) IN
                                        WHEN strlen( custom_description ) > 0 THEN custom_description
                                        ELSE me->remove_html_tags( zgi_alert->get_sap_description(  ) ) )
          event_class         = substring( val = zgi_alert->get_managed_object_name( ) off = 0 len = 3 )
          message_key         = zgi_alert->get_type_id( )
          resolution_state    = ziv_resoultion_state
          resource            = zgi_alert->get_name( )
        )
        pretty_name = /ui2/cl_json=>pretty_mode-low_case
    ).

    IF zlv_events IS INITIAL.
      zlv_events = zlv_event_json.
    ELSE.
      zlv_events = zlv_events && ',' && zlv_event_json.
    ENDIF.

    IF zlv_events IS NOT INITIAL.
      zrv_json =  '{ "records": [' && zlv_events && '] }'.
    ENDIF.

  ENDMETHOD.

Simple method codes:

  METHOD if_acc_mea~get_managed_object_name.
    rv_managed_object_name = ms_mea-context_name.
  ENDMETHOD.
  METHOD if_acc_mea~get_managed_object_type.
    rv_managed_object_type = ms_mea-context_type.
  ENDMETHOD.
  METHOD if_acc_mea~get_severity.
    rv_severity = ms_mea-severity.
  ENDMETHOD.
  METHOD convert_alert_timestamp.

    " Convert the timestamp
    CONVERT TIME STAMP ziv_timestamp TIME ZONE 'UTC'
      INTO DATE DATA(zlv_date) TIME DATA(zlv_time)
      DAYLIGHT SAVING TIME DATA(zlv_dls_time).


    zrv_date_time = |{ zlv_date+0(4) }-{ zlv_date+4(2) }-{ zlv_date+6(2) } { zlv_time+0(2) }:{ zlv_time+2(2) }:{ zlv_time+4(2) }|.

  ENDMETHOD.
  METHOD if_acc_mea~get_name.
    rv_name = ms_mea-name.
  ENDMETHOD.
  METHOD if_acc_mea~get_type_id.
    rv_type_id = ms_mea-type_id.
  ENDMETHOD.

Helper method to remove HTML tags:

  METHOD remove_html_tags.

    IF ziv_description IS INITIAL.
      RETURN.
    ENDIF.

    DATA(zlv_description) = ziv_description.

    DATA(zlv_newline) = cl_abap_char_utilities=>newline.

    REPLACE ALL OCCURRENCES OF '<h2>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</h2>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<strong>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</strong>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<p>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</p>' IN zlv_description WITH zlv_newline IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<b>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</b>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<u>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</u>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<i>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</i>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<ul>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</ul>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<li>' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '</li>' IN zlv_description WITH zlv_newline IGNORING CASE.
    REPLACE ALL OCCURRENCES OF '<a href="' IN zlv_description WITH '' IGNORING CASE.
    REPLACE ALL OCCURRENCES OF REGEX '">[A-Za-z0-9_\~\-+=&[:space:]]*</a>' IN  zlv_description WITH '' IGNORING CASE.

    REPLACE ALL OCCURRENCES OF ' ' IN zlv_description WITH '_'  IGNORING CASE.
    REPLACE ALL OCCURRENCES OF ':' IN zlv_description WITH ':'     IGNORING CASE.

    zrv_desription = zlv_description.


  ENDMETHOD.

Method to convert severity:

  METHOD convert_severity.

    CASE ziv_sm_severity.
      WHEN 1 OR 2 OR 3 OR 4.
        zrv_sn_severity = 5. " Info
      WHEN 5.
        zrv_sn_severity = 4. " Warning
      WHEN 6.
        zrv_sn_severity = 3. " Minor
      WHEN 7.
        zrv_sn_severity = 2. " Major
      WHEN 8 OR 9.
        zrv_sn_severity = 1. " Critical
      WHEN OTHERS.
        zrv_sn_severity = 0. " Clear
    ENDCASE.

  ENDMETHOD.

This method convert the SAP Focused Run severity code from 1 to 9 towards the codes in ServiceNow. Adjusts the codes per your requirement.

The sending code

The sending code is as follows (for more details on ABAP REST calls, read this blog):

  METHOD post.

    " Initialize the HTTP client
    me->init_http_client( ).

    " Set header
    me->zgo_http_client->request->set_method( method = me->zgo_http_client->request->co_request_method_post ).

    me->zgo_http_client->request->set_content_type( content_type = zgc_content_type ).

    " Set body
    me->zgo_http_client->request->set_cdata( EXPORTING data = ziv_event_messages ).

    " Send the data (POST)
    me->send( ).

    " Receive the response; needed to get the http status code
    me->receive( ).

    " Get the status code
    me->zgo_http_client->response->get_status(
      IMPORTING
        code   = zrs_status-code      " HTTP status code
        reason = zrs_status-reason    " HTTP status description
    ).

  ENDMETHOD.

Subimplementations of the methods:

  METHOD init_http_client.

    cl_http_client=>create_by_destination(
      EXPORTING
        destination              = zgc_destination      " Logical destination (specified in function call)
      IMPORTING
        client                   = me->zgo_http_client  " HTTP Client Abstraction
      EXCEPTIONS
        argument_not_found       = 1
        destination_not_found    = 2
        destination_no_authority = 3
        plugin_not_active        = 4
        internal_error           = 5
        OTHERS                   = 6
    ).

    IF sy-subrc NE 0.
      me->raise_exception_for_sys_msg( ).
    ENDIF.

  ENDMETHOD.
  METHOD send.

    me->zgo_http_client->send(
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3
        http_invalid_timeout       = 4
        OTHERS                     = 5
    ).

    IF sy-subrc NE 0.
      me->raise_exception_for_sys_msg( ).
    ENDIF.

  ENDMETHOD.
  METHOD receive.

    me->zgo_http_client->receive(
      EXCEPTIONS
        http_communication_failure = 1
        http_invalid_state         = 2
        http_processing_failed     = 3
        OTHERS                     = 4
    ).

    IF sy-subrc NE 0.
      me->raise_exception_for_sys_msg( ).
    ENDIF.

  ENDMETHOD.
  METHOD raise_exception_for_sys_msg.

    RAISE EXCEPTION TYPE zcx_snow_mid_api
      EXPORTING
        textid = VALUE scx_t100key(
                      msgid = syst-msgid
                      msgno = syst-msgno
                      attr1 = syst-msgv1
                      attr2 = syst-msgv2
                      attr3 = syst-msgv3
                      attr4 = syst-msgv4
                  ).

  ENDMETHOD.

What is important here is that in the constant ZGC_DESTINATION is the definition of the H type RFC destination towards the midserver.

Using the ServiceNow web services

The ServiceNow webservices including instructions on how to download the WSDL are published on the ServiceNow help web pages.

Download the WSDL file and follow the instructions from this blog to import the WSDL file inso SE80 and generate the ABAP web service proxy object. In SOAMANAGER setup the logical port towards your ServiceNow installation and make sure the connection is working.

Then implement the ABAP code as above.

In stead of calling the REST service, you now call the ABAP proxy generated:

* Data Declarations
DATA: zcl_proxy TYPE REF TO zco_zbapidemowebservice, " Proxy Class
      zdata_in  TYPE zzbapidemo, " Proxy Input
      zdata_out TYPE zzbapidemoresponse, " Proxy Output
      zfault    TYPE REF TO cx_root. " Generic Fault

* Instantiate the proxy class providing the Logical port name
CREATE OBJECT zcl_proxy EXPORTING logical_port_name = 'ZDEMOWS'.

* Set Fixed Values
zdata_in-zimport = '1'.

TRY .
    zcl_proxy->zbapidemo( EXPORTING input = zdata_in
                          IMPORTING output = zdata_out ).
    WRITE: / zdata_out-zexport.
  CATCH cx_root INTO zfault.
* here is the place for error handling

ENDTRY.

Off course you will use the generated in and out data from the generated service.

Author: Frank Umans

Working in SAP since 1998. Started as ABAP developer and later moved to SAP basis and authorizations. Since 2020 working with SAP Focused Run.

Leave a Reply

Your email address will not be published. Required fields are marked *