BTP - Application Job (Interne API)
Wie plant man einen Job im System ein, wenn wir mitten in der Verarbeitung unserer Anwendung sind? In diesem Artikel erfährst du mehr zur API.
Inhaltsverzeichnis
Jobs werden nicht nur über die Fiori Apps eingeplant, sondern müssen auch in Anwendungen einplanbar sein und zwar ohne den Eingriff des Nutzers. In diesem Artikel zeigen wir dir, wie du die API nutzen kannst und wie du damit einfach Jobs im System ausführen kannst.
Einleitung
Geht es um die Einplanung und Ausführung von Jobs im System, gibt es die freigegebene API in der Form der Klasse CL_APJ_RT_API. Diese Klassen stellt Methoden zur Verfügung, mit denen man eine einfache Ausführung oder wiederkehrende Pattern einplanen kann. Möchtest du einen Application Job einplanen, dann solltest du diese API nutzen.
Vorbereitung
Bevor wir mit dem Aufruf der API starten, benötigen wir für unseren Job noch einige Parameter. Teilweise sind Parameter im Template Pflicht und werden durch Validierungen überprüft, deshalb bereiten wir einen Satz Parameter vor der Einplanung für alle Beispiele vor. Die Parameter halten wir in einer Tabelle als Attribut (Member) der Klasse.
DATA mt_parameter TYPE cl_apj_rt_api=>tt_job_parameter_value.
mt_parameter = VALUE cl_apj_rt_api=>tt_job_parameter_value(
( name = 'TEXT' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = 'Planned Job' ) ) )
( name = 'COUNTRY' t_value = VALUE #( ( sign = 'I' option = 'BT' low = 'DE' high = 'EN' ) ) )
( name = 'R_TEXT' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = abap_false ) ) )
( name = 'R_LAND' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = abap_true ) ) )
( name = 'TEST' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = abap_false ) ) ) ).
Den Job den wir in diesem Beispiel verwenden, haben wir bereits in einem anderen Artikel erstellt. Über den Link findest du weitere Hinweise zur Erstellung und Nutzung von Application Jobs.
Einplanung
Für die Einplanung eines Jobs benötigen wir die Methode SCHEDULE_JOB der Klasse, diese bietet die nötige Schnittstelle, um den Job im System einzuplanen.
Einmalig
Im ersten Schritt wollen wir den Job einmal Ausführen lassen und das direkt nach dem Aufruf der Methode. Dazu befüllen wir die Schnittstelle mit den benötigten Informationen. Dazu zählt auch das Job-Template, welches wir angelegt haben, plus die entsprechenden Parameter und die Startoptionen.
DATA(ls_start) = VALUE cl_apj_rt_api=>ty_start_info( start_immediately = abap_true ).
cl_apj_rt_api=>schedule_job( EXPORTING iv_job_template_name = 'ZBS_DEMO_JOB_ADT_TEMPLATE'
iv_job_text = 'Single run from Code (Immediately)'
is_start_info = ls_start
it_job_parameter_value = mt_parameter
IMPORTING ev_jobname = DATA(ld_jobname)
ev_jobcount = DATA(ld_jobcount) ).
out->write( |Job planned under Jobname { ld_jobname } and Jobcount { ld_jobcount }| ).
Nach Ausführung des Codings erhalten wir in der Konsole die Erfolgsmeldung und die Ausgabe von Jobname und Jobcount. Über die beiden Werte kommen wir später an weitere Informationen zum Job:
Prüfen wir dann die Fiori App "Application Jobs", dann finden wir dort den Job mit der entsprechenden Bezeichnung. Da der Job eine sehr kurze Laufzeit hat, wurde dieser bereits durchgeführt und steht auf "Finished".
Regelmäßig
Als nächstes planen wir einen wiederkehrenden Job ein, dieser soll sofort starten und dann zwei Mal wiederholt werden. Der Quellcode für die Einplanung des Jobs sieht nun wie folgt aus:
DATA(ls_start) = VALUE cl_apj_rt_api=>ty_start_info( ).
GET TIME STAMP FIELD ls_start-timestamp.
ls_start-timestamp = cl_abap_tstmp=>add( tstmp = ls_start-timestamp
secs = 120 ).
DATA(ls_end) = VALUE cl_apj_rt_api=>ty_end_info( type = 'NUM' max_iterations = 3 ).
DATA(ls_scheduling_info) = VALUE cl_apj_rt_api=>ty_scheduling_info(
periodic_granularity = 'W'
periodic_value = 1
test_mode = abap_false
timezone = 'CET'
weekday_info = VALUE #( on_tuesday = abap_true on_saturday = abap_true ) ).
cl_apj_rt_api=>schedule_job( EXPORTING iv_job_template_name = 'ZBS_DEMO_JOB_ADT_TEMPLATE'
iv_job_text = 'Recurring run'
is_start_info = ls_start
it_job_parameter_value = mt_parameter
is_end_info = ls_end
is_scheduling_info = ls_scheduling_info
IMPORTING ev_jobname = DATA(ld_jobname)
ev_jobcount = DATA(ld_jobcount) ).
Dazu die folgende Erklärung, um die Teile besser einordnen zu können:
- Start - Dieses Mal setzen wir den Zeitstempel, um den Job zu einer bestimmten Uhrzeit und nicht sofort einzuplanen. Dabei rechnen wir auf die aktuelle Uhrzeit zwei Minuten dazu.
- Ende - Wir möchten, dass der Job nach zwei Wiederholungen abbricht, dazu setzen wir den TYPE auf 'NUM', also Anzahl, und die Anzahl der Wiederholungen auf 3. Der aktuelle Lauf zählt ebenfalls, deshalb benötigen wir für zwei Wiederholungen die Anzahl von drei Jobs.
- Einplanung - Wir setzen die Einplanung mit 'W' auf wöchentlich und die Frequenz auf 1, also alle einer Woche. Der Job soll nur an den Wochentagen Dienstag und Samstag laufen. Dabei ist allerdings wichtig, dass der Sofortstart auch an einem der beiden Tage läuft, sonst bekommst du einen Abbruch in der Verarbeitung.
Nach der Einplanung können die Jobs wieder in der App gefunden werden. Wenn du die Einschränkung auch auf die Zukunft vornimmst, dann findest du auch die eingeplanten Jobs.
Konstanten
Für die Einplanung der Jobs werden verschiedene Werte benötigt, die im Interface IF_APJ_RT_TYPES als Konstantenstruktur abgebildet werden. Da das Interface im Moment nicht freigegeben ist, müssen die Werte im Code hart hinterlegt werden, können aber hier eingesehen werden. Über die Strukturen und die Domänen, findet man die Werte in den Festwerten ebenfalls. Über die Element Info kannst du sie auch in den ABAP Development Tools anzeigen:
Informationen
An einigen Stellen wollen wir den aktuellen Status eines Jobs prüfen oder vielleicht weitere Informationen zu einem Job lesen, dazu gibt es ebenfalls verschiedene Methoden, um an die Informationen zu kommen. Benötigen wir einfach nur den Status und den entsprechenden Text dazu, steht die Methode GET_JOB_STATUS zur Verfügung:
cl_apj_rt_api=>get_job_status( EXPORTING iv_jobname = 'FEDC1903ACDA1EEEABE45F7233854BD6'
iv_jobcount = 'Bsgasykr'
IMPORTING ev_job_status = DATA(ld_status)
ev_job_status_text = DATA(ld_text) ).
out->write( |Status: { ld_status } - { ld_text }| ).
Dadurch erhalten wir das folgende Ergebnis als Ausgabe in der Console:
Benötigst du einige Informationen mehr zum Job, vielleicht auch die Laufzeit, den User oder Informationen zum genutzten Template, dann kannst du die Methode GET_JOB_DETAILS verwenden:
DATA(ls_job_details) = cl_apj_rt_api=>get_job_details( iv_jobname = 'FEDC1903ACDA1EEEABE45F7233854BD6'
iv_jobcount = 'Bsgasykr' ).
Das Ergebnis der Abfrage hier einmal in der Variablenansicht in den ABAP Development Tools:
Weitere Methoden
Alle Methoden der API zu erklären, würde den Rahmen des Artikels sprengen. Die Klasse bietet allerdings noch verschiedene Methoden an, um zum Beispiel weiter Aktionen durchzuführen:
- CANCEL_JOB - Abbrechen eines laufenden Jobs.
- FIND_JOBS_WITH_JCE - Such nach Jobs die das entsprechende Template verwenden. Dabei werden nur Jobs zurück gegeben die laufen oder in der Zukunft eingeplant sind.
- COPY_JOB - Kopieren eines Jobs.
- GENERATE_JOBKEY - Generierung von Jobname und Jobcount, bereits vor der Einplanung des Jobs. Mit diesen Informationen kann ein Job eingeplant werden und die automatische Vergabe unterbunden werden.
Vollständiges Beispiel
Hier findest du noch einmal die komplette ausführbare Klasse zum Testen. Die Methode READ_JOB_INFO muss entsprechend mit Werten aus deinem System befüllt werden, hier findest du noch unsere Dummy Werte des Joblaufs:
CLASS zcl_bs_demo_job_api DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
PRIVATE SECTION.
DATA mt_parameter TYPE cl_apj_rt_api=>tt_job_parameter_value.
METHODS plan_single_run
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS plan_recurring_runs
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
METHODS read_job_info
IMPORTING io_out TYPE REF TO if_oo_adt_classrun_out.
ENDCLASS.
CLASS zcl_bs_demo_job_api IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
mt_parameter = VALUE cl_apj_rt_api=>tt_job_parameter_value(
( name = 'TEXT' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = 'Planned Job' ) ) )
( name = 'COUNTRY' t_value = VALUE #( ( sign = 'I' option = 'BT' low = 'DE' high = 'EN' ) ) )
( name = 'R_TEXT' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = abap_false ) ) )
( name = 'R_LAND' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = abap_true ) ) )
( name = 'TEST' t_value = VALUE #( ( sign = 'I' option = 'EQ' low = abap_false ) ) ) ).
plan_single_run( out ).
plan_recurring_runs( out ).
read_job_info( out ).
ENDMETHOD.
METHOD plan_single_run.
TRY.
DATA(ls_start) = VALUE cl_apj_rt_api=>ty_start_info( start_immediately = abap_true ).
cl_apj_rt_api=>schedule_job( EXPORTING iv_job_template_name = 'ZBS_DEMO_JOB_ADT_TEMPLATE'
iv_job_text = 'Single run from Code (Immediately)'
is_start_info = ls_start
it_job_parameter_value = mt_parameter
IMPORTING ev_jobname = DATA(ld_jobname)
ev_jobcount = DATA(ld_jobcount) ).
io_out->write( |Job planned under Jobname { ld_jobname } and Jobcount { ld_jobcount }| ).
CATCH cx_apj_rt INTO DATA(lo_error).
io_out->write( lo_error->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD plan_recurring_runs.
TRY.
DATA(ls_start) = VALUE cl_apj_rt_api=>ty_start_info( ).
GET TIME STAMP FIELD ls_start-timestamp.
ls_start-timestamp = cl_abap_tstmp=>add( tstmp = ls_start-timestamp
secs = 120 ).
DATA(ls_end) = VALUE cl_apj_rt_api=>ty_end_info( type = 'NUM' max_iterations = 3 ).
DATA(ls_scheduling_info) = VALUE cl_apj_rt_api=>ty_scheduling_info(
periodic_granularity = 'W'
periodic_value = 1
test_mode = abap_false
timezone = 'CET'
weekday_info = VALUE #( on_tuesday = abap_true on_saturday = abap_true ) ).
cl_apj_rt_api=>schedule_job( EXPORTING iv_job_template_name = 'ZBS_DEMO_JOB_ADT_TEMPLATE'
iv_job_text = 'Recurring run'
is_start_info = ls_start
it_job_parameter_value = mt_parameter
is_end_info = ls_end
is_scheduling_info = ls_scheduling_info
IMPORTING ev_jobname = DATA(ld_jobname)
ev_jobcount = DATA(ld_jobcount) ).
io_out->write( |Job planned under Jobname { ld_jobname } and Jobcount { ld_jobcount }| ).
CATCH cx_apj_rt INTO DATA(lo_error).
io_out->write( lo_error->get_text( ) ).
ENDTRY.
ENDMETHOD.
METHOD read_job_info.
cl_apj_rt_api=>get_job_status( EXPORTING iv_jobname = 'FEDC1903ACDA1EEEABE45F7233854BD6'
iv_jobcount = 'Bsgasykr'
IMPORTING ev_job_status = DATA(ld_status)
ev_job_status_text = DATA(ld_text) ).
io_out->write( |Status: { ld_status } - { ld_text }| ).
DATA(ls_job_details) = cl_apj_rt_api=>get_job_details( iv_jobname = 'FEDC1903ACDA1EEEABE45F7233854BD6'
iv_jobcount = 'Bsgasykr' ).
io_out->write( ls_job_details ).
ENDMETHOD.
ENDCLASS.
ABAP Cloud
Funktionsbausteine wie JOB_OPEN funktionieren in der Welt von ABAP Cloud nicht mehr, weil die Objekte für den Entwickler in TIER-1 nicht zur Verfügung stehen. Im Fall der Application Jobs hat sich auch das zu automatisierende Objekt geändert (Report zu Klasse). Über den Cloudification Repository Viewer findest du die Nachfolgeobjekte bzw. API um wie gewohnt Jobs über deine Verarbeitung einplanen zu können.
Fazit
Die Einplanung von Application Jobs ist recht einfach mit einem Methodenaufruf erledigt. Die bereitgestellte API erlaubt uns alle Grundoperationen durchzuführen, um einen Job einzuplanen, auszuplanen und Informationen dazu zu lesen.