Note: A new version for creating application jobs is available and will be covered in a new article. This article uses legacy interfaces.
As a reminder, in ABAP Cloud (the only version of ABAP available for S/4 HANA Cloud, Public Edition and the one recommended for other S/4 HANA Private Edition and On Premise versions), GUI screens are no longer available, and any UI must be Fiori.
What then of the REPORTs we used to schedule every day? every hour?
A solution exists: Application Jobs available on S/4 HANA cloud, Public/ Private Edition, On-Premise and BTP, Abap Environment. As indicated in the documentation, it’s a standard Fiori application for scheduling and monitoring jobs.
A set of standard applications is already available to be scheduled in the background, but it is also possible to create your own jobs, which is what we’ll break down here.
Replace REPORT
If you used to program Reports on your SAP systems, this is no longer possible with the ABAP Cloud version.
Instead, we’ll create :
- A class containing all the logic we want to implement for this job (this class must implement the IF_APJ_DT_EXEC_OBJECT and IF_APJ_RT_EXEC_OBJECT interfaces).
- Once this class has been created, we’ll create a “Job Catalog Entry”, which as specified in the documentation: contains the information needed to plan and execute an “application job”. This contains the name of the previously created class and information on how the selection fields are made available in the application.
- Finally, for the job to be available in the Fiori Application Jobs application, a “Job Template” must be created, which refers to the “Job Catalog Entry” and contains values for some or all of the selection fields. In the Fiori Application Jobs tile, the selection fields appear pre-filled with the values defined in the template. These can be overwritten by the user.
- Finally, it is possible to program the job in the Application Jobs tile OR use the CL_APJ_RT_API API to do the programming.
- Generally speaking, a business user will always use the Application Jobs tile to program jobs, whereas a developer may use a piece of code to program his job.
- It is also possible to create jobs during the execution of different processes (as could be done in the past with JOB_OPEN, JOB_CLOSE, etc. function modules, but now via the CL_APJ_RT_API API).
Create logic
As mentioned above, the first thing to do is to create a class that implements the IF_APJ_DT_EXEC_OBJECT and IF_APJ_RT_EXEC_OBJECT interfaces.
CLASS zjob_test DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_apj_dt_exec_object.
INTERFACES if_apj_rt_exec_object.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zjob_test IMPLEMENTATION.
METHOD if_apj_dt_exec_object~get_parameters.
" Return the supported selection parameters here
et_parameter_def = VALUE #(
( selname = 'S_ID' kind = if_apj_dt_exec_object=>select_option datatype = 'C' length = 10 param_text = 'My ID' changeable_ind = abap_true )
( selname = 'P_DESCR' kind = if_apj_dt_exec_object=>parameter datatype = 'C' length = 80 param_text = 'My Description' lowercase_ind = abap_true changeable_ind = abap_true )
( selname = 'P_COUNT' kind = if_apj_dt_exec_object=>parameter datatype = 'I' length = 10 param_text = 'My Count' changeable_ind = abap_true )
( selname = 'P_SIMUL' kind = if_apj_dt_exec_object=>parameter datatype = 'C' length = 1 param_text = 'My Simulate Only' checkbox_ind = abap_true changeable_ind = abap_true )
).
" Return the default parameters values here
et_parameter_val = VALUE #(
( selname = 'S_ID' kind = if_apj_dt_exec_object=>select_option sign = 'I' option = 'EQ' low = '4711' )
( selname = 'P_DESCR' kind = if_apj_dt_exec_object=>parameter sign = 'I' option = 'EQ' low = 'My Default Description' )
( selname = 'P_COUNT' kind = if_apj_dt_exec_object=>parameter sign = 'I' option = 'EQ' low = '200' )
( selname = 'P_SIMUL' kind = if_apj_dt_exec_object=>parameter sign = 'I' option = 'EQ' low = abap_true )
).
ENDMETHOD.
METHOD if_apj_rt_exec_object~execute.
TYPES ty_id TYPE c LENGTH 10.
DATA s_id TYPE RANGE OF ty_id.
DATA p_descr TYPE c LENGTH 80.
DATA p_count TYPE i.
DATA p_simul TYPE abap_boolean.
DATA: jobname type cl_apj_rt_api=>TY_JOBNAME.
DATA: jobcount type cl_apj_rt_api=>TY_JOBCOUNT.
DATA: catalog type cl_apj_rt_api=>TY_CATALOG_NAME.
DATA: template type cl_apj_rt_api=>TY_TEMPLATE_NAME.
" Getting the actual parameter values
LOOP AT it_parameters INTO DATA(ls_parameter).
CASE ls_parameter-selname.
WHEN 'S_ID'.
APPEND VALUE #( sign = ls_parameter-sign
option = ls_parameter-option
low = ls_parameter-low
high = ls_parameter-high ) TO s_id.
WHEN 'P_DESCR'. p_descr = ls_parameter-low.
WHEN 'P_COUNT'. p_count = ls_parameter-low.
WHEN 'P_SIMUL'. p_simul = ls_parameter-low.
ENDCASE.
ENDLOOP.
try.
* read own runtime info catalog
cl_apj_rt_api=>GET_JOB_RUNTIME_INFO(
importing
ev_jobname = jobname
ev_jobcount = jobcount
ev_catalog_name = catalog
ev_template_name = template ).
catch cx_apj_rt.
endtry.
" Implement the job execution
ENDMETHOD.
ENDCLASS.
The if_apj_dt_exec_object~get_parameters method specifies the parameters available for Job and their default values, if any.
In the if_apj_rt_exec_object~execute method, the entire logic is executed. Here, it doesn’t do anything special except return the information for the JOB in question, but it would have been possible to put in all the logic required.
Create Job Catalog Entry
Create the Job Catalog Entry and select the previously created class to be executed
Here you will find the parameters and default values that can be configured:
Job scheduling
- Via API
Create a class that can be executed directly by implementing the if_oo_adt_classrun interface.
CLASS zcl_job_api_tst DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS zcl_job_api_tst IMPLEMENTATION.
" <SIGNATURE>---------------------------------------------------------------------------------------+
" | Instance Public Method Z_CL_RT_API_DEMO->IF_OO_ADT_CLASSRUN~MAIN
" +-------------------------------------------------------------------------------------------------+
" | [--->] OUT TYPE REF TO IF_OO_ADT_CLASSRUN_OUT
" +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD if_oo_adt_classrun~main.
DATA lv_job_text TYPE cl_apj_rt_api=>ty_job_text VALUE 'Demo_Job'.
DATA lv_template_name TYPE cl_apj_rt_api=>ty_template_name.
DATA ls_start_info TYPE cl_apj_rt_api=>ty_start_info.
DATA ls_scheduling_info TYPE cl_apj_rt_api=>ty_scheduling_info.
DATA ls_end_info TYPE cl_apj_rt_api=>ty_end_info.
DATA lt_job_parameters TYPE cl_apj_rt_api=>tt_job_parameter_value.
DATA ls_job_parameters TYPE cl_apj_rt_api=>ty_job_parameter_value.
DATA ls_value TYPE cl_apj_rt_api=>ty_value_range.
DATA lv_jobname TYPE cl_apj_rt_api=>ty_jobname.
DATA lv_jobcount TYPE cl_apj_rt_api=>ty_jobcount.
DATA lv_status TYPE cl_apj_rt_api=>ty_job_status.
DATA lv_statustext TYPE cl_apj_rt_api=>ty_job_status_text.
DATA lv_txt TYPE string.
DATA ls_ret TYPE bapiret2.
" Choose the name of the existing job template.
lv_template_name = 'ZJOB_TEMPLATE_TEST'.
" The immediate start can't be used when being called from within a RAP business object
" because the underlying API performs an implicit COMMIT WORK.
" ls_start_info-start_immediately = 'X'.
" Start the job using a timestamp instead. This will start the job immediately but can have a delay depending on the current workload.
GET TIME STAMP FIELD DATA(ls_ts1).
" Add 1 hour
DATA(ls_ts2) = cl_abap_tstmp=>add( tstmp = ls_ts1
secs = 3600 ).
ls_start_info-timestamp = ls_ts2.
" Periodicity
ls_scheduling_info-periodic_granularity = 'D'.
ls_scheduling_info-periodic_value = 1.
ls_scheduling_info-test_mode = abap_false.
ls_scheduling_info-timezone = 'CET'.
ls_end_info-type = 'NUM'.
ls_end_info-max_iterations = 3.
" Fill parameter table:
" Fill the table only if you want to overrule the parameter values
" which are stored in the template.
" The field names in this program must match the field names of the template.
ls_job_parameters-name = 'P_TEST1'.
ls_value-sign = 'I'.
ls_value-option = 'EQ'.
ls_value-low = 'Test 1'.
APPEND ls_value TO ls_job_parameters-t_value.
APPEND ls_job_parameters TO lt_job_parameters.
CLEAR ls_job_parameters.
ls_job_parameters-name = 'P_TEST2'.
ls_value-sign = 'I'.
ls_value-option = 'BT'.
ls_value-low = 'ATEST'.
ls_value-high = 'ZTEST'.
APPEND ls_value TO ls_job_parameters-t_value.
ls_job_parameters-name = 'P_TEST2'.
ls_value-sign = 'I'.
ls_value-option = 'BT'.
ls_value-low = '11111'.
ls_value-high = '99999'.
APPEND ls_value TO ls_job_parameters-t_value.
APPEND ls_job_parameters TO lt_job_parameters.
CLEAR ls_job_parameters.
ls_job_parameters-name = 'P_TEST3'.
ls_value-sign = 'I'.
ls_value-option = 'EQ'.
ls_value-low = '220'.
APPEND ls_value TO ls_job_parameters-t_value.
APPEND ls_job_parameters TO lt_job_parameters.
CLEAR ls_job_parameters.
TRY.
" Some scenarios require that the job key ( = jobname, jobcount) is already known
" before the job is created. The method generate_jobkey creates a valid job key.
" This key can then be passed later on to the method schedule_job, and a job with
" exactly this key is created.
" Optional. You need this call only if you have to know the job key in advance.
" cl_apj_rt_api=>generate_jobkey(
" importing
" ev_jobname = lv_jobname
" ev_jobcount = lv_jobcount ).
" If you pass the table lt_job_parameters , then the parameters
" contained in this table are used.
" If you don't pass the table, the parameters contained in the
" job template are used.
cl_apj_rt_api=>schedule_job(
EXPORTING
iv_job_template_name = lv_template_name
iv_job_text = lv_job_text
is_start_info = ls_start_info
is_scheduling_info = ls_scheduling_info
is_end_info = ls_end_info
it_job_parameter_value = lt_job_parameters
" The following two parameters are optional. If you pass them, they must have been generated
" with the optional call generate_jobkey above.
" iv_jobname = lv_jobname
" iv_jobcount = lv_jobcount
IMPORTING
ev_jobname = lv_jobname
ev_jobcount = lv_jobcount
).
out->write( lv_jobname ).
out->write( lv_jobcount ).
cl_apj_rt_api=>get_job_status(
EXPORTING
iv_jobname = lv_jobname
iv_jobcount = lv_jobcount
IMPORTING
ev_job_status = lv_status
ev_job_status_text = lv_statustext
).
out->write( lv_status ).
out->write( lv_statustext ).
" Via the following method you can cancel the job
" in the application job context 'cancel' means (as in the Fiori app):
" 1. if the job is running, it will be canceled
" 2. if the job has not yet started, it will be deleted.
" In case the job is periodic, the whole periodicity chain is deleted.
* To comment if you want to keep the job
cl_apj_rt_api=>cancel_job(
exporting
iv_jobname = lv_jobname "change with the job name you want to cancel
iv_jobcount = lv_jobcount "change with the job count you want to cancel
).
catch cx_apj_rt into data(exc).
lv_txt = exc->get_longtext( ).
ls_ret = exc->get_bapiret2( ).
out->write( 'ERROR:' ).
out->write( lv_txt ).
out->write( 'msg type =' ).
out->write( ls_ret-type ).
out->write( 'msg id =' ).
out->write( ls_ret-id ).
out->write( 'msg number =' ).
out->write( ls_ret-number ).
out->write( 'msg message =' ).
out->write( ls_ret-message ).
COMMIT WORK. "NOT NECESARY IN CASE OF RAP APPLICATION AS THE COMMIT IS HANDLED WITH THE RAP FRAMEWORK
ENDTRY.
ENDMETHOD.
ENDCLASS.
- Via the Fiori Application Jobs tile
If you’re on an On Premise or Private Cloud system, you can even check in SM37 that it’s been correctly programmed.
Note that for BTP, ABAP environment and S/4 Cloud Public edition, an IAM application must be created to assign authorizations. Authorization management for On-Premise and S/4 Cloud Private Edition can be found here.
Fiori application: https://fioriappslibrary.hana.ondemand.com/sap/fix/externalViewer/#/detail/Apps(%27F1240%27)/S16OP