Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
ennowulff
Active Contributor
There was a requirement to log complex data. That's quite easy. Use the Application Log BAL_LOG_CREATE, add messages with BAL_LOG_MSG_ADD and save the protocol with BAL_DB_SAVE when finished.

The task then was to save the data even if a rollback was initiated.

ABAP Messaging Channels


The only way to solve this task was: AMC - ABAP messaging Channels.

The idea:

  • create a listener job in the background

  • send the messages to the listener

  • save the log data by any means in the background job


The listener runs in its own task and can do whatever it wants to do (wait, save, commit, ...).

Create A Messaging Channel


To create a messaging channel you will have to start transaction SAMC and define the channels, listening program and sending program (or class).



There are different Message type IDs (Text, binary and PCP - Push Channel Protocol) where I chose TEXT for demonstration purposes.

Create A Listener


The listener can simply be created with the following code:
        cl_amc_channel_manager=>get_consumer_session_id( ).
cl_amc_channel_manager=>create_message_consumer(
i_application_id = 'ZTEST'
i_channel_id = '/prot'
i_channel_extension_id = CONV #( id )
)->start_message_delivery( i_receiver = mo_current ).

I used a unique identification to make sure that each sender has it's own listener.

To make sure that the data can be received by the listener, you will have to create a class which implements the interface IF_AMC_MESSAGE_RECEIVER_TEXT.

The interface method IF_AMC_MESSAGE_RECEIVER_TEXT~RECEIVE will receive the text string.

Create A Sender


Sending the data is also quite easy:
DATA(amc_producer) = CAST if_amc_message_producer_text(
cl_amc_channel_manager=>create_message_producer(
i_application_id = 'ZTEST'
i_channel_id = '/prot'
i_channel_extension_id = CONV #( id ) ) ).
amc_producer->send( i_message = 'This is an important message!' ).

Create A Job


To create the background job we use do the follwing:

  • call function module JOB_OPEN

  • SUBMIT the listener program with the unique ID

  • call function module JOB_CLOSE



Protocol


There are two logs created:

  • The main log (where all the fuzz is about) using the Application log

  • The job protocol of the listener.


The Application Log



The job protocol


Each text string is "raised" by MESSAGE TYPE "S" to appear in the job log.


Information For Usage


The following program starts itself in background (which created the listener for the given id) and then sends some messages, DOES A ROLLBACK, sends further messages and ends the communication.

Variant A - ROLLBACK


If you prepared saving data before the rollback, all data updated in the background job a re not affected.

Variant B - COMMIT


You cannot do a COMMIT in the background job (you will receive the short dump AMC_ILLEGAL_STATEMENT) to make sure the application data will be saved but you can be sure that the session will be available till the end to save the data in a controlled way.

Stop Message Delivery!


I am not sure if there should be somewhere called a STOP_MESSAGE_DELIVERY... In this case the job runs up to 10 seconds and with the end of the job, the messaging channels will also be stopped.

Code


REPORT zzenno184.

PARAMETERS batch TYPE c LENGTH 1 NO-DISPLAY.
PARAMETERS id TYPE numc10 DEFAULT sy-uzeit.

CLASS amc DEFINITION.
PUBLIC SECTION.
INTERFACES if_amc_message_receiver_text.
CLASS-METHODS listen IMPORTING id TYPE numc10.
CLASS-METHODS create_listener_job IMPORTING id TYPE numc10.
DATA end TYPE boolean_flg.

PRIVATE SECTION.
DATA mv_log_handle TYPE balloghndl.
DATA mv_end TYPE boolean_flg.
ENDCLASS.

CLASS amc IMPLEMENTATION.
METHOD if_amc_message_receiver_text~receive.
DATA ls_msg TYPE bal_s_msg.

MESSAGE i_message TYPE 'S'.
IF mv_log_handle IS INITIAL.
CALL FUNCTION 'BAL_LOG_CREATE'
EXPORTING
i_s_log = VALUE bal_s_log( extnumber = id object = 'BCT1' )
IMPORTING
e_log_handle = mv_log_handle
EXCEPTIONS
OTHERS = 1.
IF sy-subrc <> 0.
MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.
ENDIF.

IF i_message = 'end'.
CALL FUNCTION 'BAL_DB_SAVE'
EXPORTING
i_save_all = ' '
i_t_log_handle = VALUE bal_t_logh( ( mv_log_handle ) )
EXCEPTIONS
log_not_found = 1
save_not_allowed = 2
numbering_error = 3
OTHERS = 4.
IF sy-subrc <> 0.
MESSAGE |error SAVE_LOG { sy-subrc }| TYPE 'S'.
ENDIF.
mv_end = abap_true.
RETURN.
ENDIF.

ls_msg-msgno = '000'.
ls_msg-msgid = 'OO'.
ls_msg-msgty = 'I'.
ls_msg-msgv1 = i_message.

CALL FUNCTION 'BAL_LOG_MSG_ADD'
EXPORTING
i_log_handle = mv_log_handle
i_s_msg = ls_msg
EXCEPTIONS
log_not_found = 1
msg_inconsistent = 2
log_is_full = 3
OTHERS = 4.
IF sy-subrc > 0.
MESSAGE |error MSG_ADD { sy-subrc }| TYPE 'S'.
ENDIF.

ENDMETHOD.

METHOD create_listener_job.

DATA jobcount TYPE tbtcjob-jobcount.
DATA ret TYPE i.
DATA jobname TYPE tbtcjob-jobname.
DATA job_was_released TYPE btch0000-char1.

jobname = |PARAPROT_ID_{ id }|.

CALL FUNCTION 'JOB_OPEN'
EXPORTING
jobname = jobname
IMPORTING
jobcount = jobcount
CHANGING
ret = ret
EXCEPTIONS
cant_create_job = 1
invalid_job_data = 2
jobname_missing = 3
OTHERS = 4.
IF sy-subrc <> 0 OR ret <> 0.
WRITE: / 'JOB_OPEN failed', sy-subrc.
RETURN.
ENDIF.

SUBMIT zzenno184
WITH id = id
WITH batch = abap_true
VIA JOB jobname NUMBER jobcount
AND RETURN.

CALL FUNCTION 'JOB_CLOSE'
EXPORTING
jobcount = jobcount
jobname = jobname
strtimmed = 'X'
IMPORTING
job_was_released = job_was_released
CHANGING
ret = ret
EXCEPTIONS
cant_start_immediate = 1
invalid_startdate = 2
jobname_missing = 3
job_close_failed = 4
job_nosteps = 5
job_notex = 6
lock_failed = 7
invalid_target = 8
OTHERS = 9.
IF sy-subrc <> 0.
WRITE: / 'JOB_CLOSE failed', sy-subrc.
RETURN.
ENDIF.

"let the job some time to start...
WAIT UP TO 1 SECONDS.

ENDMETHOD.

METHOD listen.

DATA(lo_amc) = NEW amc( ).


TRY.
"Create listener Channel
cl_amc_channel_manager=>get_consumer_session_id( ).
cl_amc_channel_manager=>create_message_consumer(
i_application_id = 'ZTEST'
i_channel_id = '/prot'
i_channel_extension_id = CONV #( id )
)->start_message_delivery( i_receiver = lo_amc ).

CATCH cx_amc_error INTO DATA(text_exc).
MESSAGE text_exc TYPE 'S'.
ENDTRY.

"Wait for messages
WAIT FOR MESSAGING CHANNELS
UNTIL lo_amc->mv_end = abap_true
UP TO 10 SECONDS.

ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.

CASE batch.
WHEN space.

"Create the listener job
amc=>create_listener_job( id ).

TRY.
"Prepare sending AMC messages
DATA(amc_producer) = CAST if_amc_message_producer_text(
cl_amc_channel_manager=>create_message_producer(
i_application_id = 'ZTEST'
i_channel_id = '/prot'
i_channel_extension_id = CONV #( id ) ) ).
amc_producer->send( i_message = 'This is an important message!' ).
amc_producer->send( i_message = 'Oh no!! I have to roll back...' ).
ROLLBACK WORK.
amc_producer->send( i_message = 'Rollback done.' ).
amc_producer->send( i_message = 'this is the end, don''t you know it?' ).
amc_producer->send( i_message = 'end' ).

CATCH cx_amc_error INTO DATA(text_exc).
cl_demo_output=>display( text_exc->get_text( ) ).
ENDTRY.

"Call created protocol
SUBMIT sbal_display WITH balobj = 'BCT1' WITH balext = id AND RETURN .

WHEN 'X'.

"Start the listener
amc=>listen( id ).

ENDCASE.

Improvement


In this simple version (proof of concept) it is only possible to send simple text strings. You can also use the message type ID "PCP" (Interface IF_AC_MESSAGE_TYPE_PCP) to send serialized complex data.

 

SAP Demo Programs


If you are interested in the ABAP Messaging Channels, have a look at the programs DEMO_RECEIVE_AMC and DEMO_SEND_AMC.
21 Comments