![BTP Application Job](/user_content/content/btp-application-job.png)
BTP - Application Jobs (Anlage)
Wie legt man nun einen neuen Job im System an? In diesem Artikel schauen wir uns zwei Methoden an, um Jobs anzulegen.
Inhaltsverzeichnis
In der letzten Woche hatten wir dir eine Einführung in das Thema Application Job gegeben und wie Jobs in der Cloud und dem ABAP Environment aussehen und funktionieren. In diesem Artikel schauen wir uns an, wie so ein Job erstellt wird und wie wir dann darauf zugreifen können.
Einleitung
Bevor wir die Objekte anlegen, wollen wir noch einen kleinen Exkurs in die Vergangenheit machen. Als die ersten Jobs eingeführt wurden, gab es nur die Möglichkeit über die ABAP API die Objekte im System anzulegen. Mittlerweile gibt es auch die Möglichkeit in den ADTs alle Objekte anzulegen, diese Möglichkeit wollen wir auch genauer aufzeigen, da dies der Standardweg zur Neuanlage ist.
Vorbereitung
In unserer Demo werden wir eine Tabelle verwenden, um die Läufe zu protokollieren und Informationen sichtbar zu machen. Die Ausführung des Jobs kann nicht leicht debuggt werden, deshalb greifen wir vor allem auf diese Informationen zu, um vergangene Läufe und deren Inhalte zu tracken.
@EndUserText.label : 'Tracking for Jobs'
define table zbs_dmo_jtrack {
key client : abap.clnt not null;
key identifier : sysuuid_x16 not null;
content : abap.char(200);
test : abap_boolean;
rdate : abap.dats;
rtime : abap.tims;
}
Im Feld "Content" können wir verschiedene Inhalte speichern, die restlichen Felder dienen für die automatische Befüllung aus den Werten und aus dem Kontext.
Anlage (ADT)
Die erste Anlage erfolgt über die ABAP Development Tools, kurz ADT, und nutzt dafür die Standard ADT Objekte. Dazu legen wir nun die folgenden Objekte an, um unseren ersten Job zu definieren.
Klasse anlegen
Zuerst einmal müssen wir die Ausführungsklasse anlegen, die später die Funktionalität implementiert, um unseren Job mit Parametern auszustatten und Einstiegspunkt für die Ausführung zu sein. Dazu implementieren wir bei der Anlage auch die beiden Interface:
- IF_APJ_DT_EXEC_OBJECT - Bereitstellung der Methode GET_PARAMETERS, um die Parameter des Jobs zu definieren.
- IF_APJ_RT_EXEC_OBJECT - Bereitstellung der Methode EXECUTE, die später bei der Jobausführung gestartet wird.
Nach der Ausführung und der Zuordnung des Transportes, steht die leere Klasse zur Verfügung, die wir nun mit Inhalt befüllen können.
CLASS zcl_bs_demo_job_adt_exe DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_apj_dt_exec_object.
INTERFACES if_apj_rt_exec_object.
ENDCLASS.
CLASS zcl_bs_demo_job_adt_exe IMPLEMENTATION.
METHOD if_apj_dt_exec_object~get_parameters.
ENDMETHOD.
METHOD if_apj_rt_exec_object~execute.
ENDMETHOD.
ENDCLASS.
Parameter anlegen
Im nächsten Schritt wollen wir einige Parameter im Job anlegen, die wir später dann für Einstellungen verwenden wollen, aber auch um zu zeigen, wie man den Job gestalten kann. Dazu einmal die Signatur der Methode und welche Tabellen uns zur Verfügung stehen:
Zuerst einmal definieren wir einige Standard-Parameter, die wir dann im späteren Lauf des Jobs verwenden wollen. Dazu geben wir alle Informationen in der Tabelle mit, die die Felder besser beschreiben und wie sie später funktionieren sollen.
et_parameter_def = VALUE #( datatype = 'C'
changeable_ind = abap_true
( selname = 'TEXT'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Free text'
length = 50
lowercase_ind = abap_true
mandatory_ind = abap_true )
( selname = 'COUNTRY'
kind = if_apj_dt_exec_object=>select_option
param_text = 'Country'
length = 3 )
( selname = 'R_TEXT'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Text'
length = 1
radio_group_ind = abap_true
radio_group_id = 'R1' )
( selname = 'R_LAND'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Country'
length = 1
radio_group_ind = abap_true
radio_group_id = 'R1' )
( selname = 'TEST'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Test'
length = 1
checkbox_ind = abap_true ) ).
Dazu einige der Elemente besser beschrieben:
- SELNAME - Name des Feldes, darf maximal 8 Stellen haben.
- KIND - Feld ist Parameter oder Select-Option. Im Interface IF_APJ_DT_EXEC_OBJECT gibt es die beiden Konstanten, die dazu verwendet werden können.
- PARAM_TEXT - Text des Parameters der später in der App erscheinen soll, hier sollten übersetzbare Texte verwendet werden.
- DATATYPE - Grundtyp mit dem das Feld definiert wird.
- LENGTH - Länge des Feldes für spätere Eingaben in der Maske.
- DECIMALS - Anzahl der Kommastellen, falls ein Dezimaler Datentype verwendet wird.
- LOWERCASE_IND - Auf ABAP_TRUE setzen, wenn der Inhalt des Feldes Groß-/Kleinschreibung unterstützen soll.
- CHECKBOX_IND - Auf ABAP_TRUE setzen, wenn das Feld als Checkbox angezeigt werden soll.
- RADIO_GROUP_IND - Auf ABAP_TRUE setzen, wenn das Feld als Radiobutton angezeigt werden soll.
- RADIO_GROUP_ID - Alle Felder in einer Gruppe (Radiobuttons) sollten die gleiche ID bekommen.
- MANDATORY_IND - Auf ABAP_TRUE setzen, wenn das Feld ein Pflichtfeld sein soll. Dann ist eine Eingabe immer nötig.
Zum Abschluss definieren wir noch einige Vorschlagswerte, die später in das Template übernommen werden sollen.
et_parameter_val = VALUE #( sign = 'I'
option = 'EQ'
( selname = 'TEST' low = abap_true )
( selname = 'R_TEXT' low = abap_false )
( selname = 'R_LAND' low = abap_true ) ).
Nach dem Generieren aller Objekte, sieht das Parameter Bild dann wie folgt aus. Unsere Standard-Werte sind gesetzt (Checkbox und Radiobuttons) und der Text ist als Pflichtfeld definiert. Die Eingabe kann aber erst nach dem Schritt "Job-Template" getestet werden.
Job-Katalog
Wenn du ein entsprechendes Paket hast oder ein neues Paket angelegt hast, kannst du nun den Job-Katalog anlegen. Dieser definiert die Hülle für die ausführbare Klasse und die Einstellungen, die für den Start definiert werden können. Dazu auf das Paket Rechts-Klicken gehen und über "New -> Other ABAP Repository Object" nach dem "Job Catalog" suchen.
Die Ausführungsklasse ist Pflichtfeld und wird für die Anlage des Katalogs benötigt. Nach Befüllung der Informationen, kann der Wizard abgeschlossen werden.
Im Job-Katalog stehen dann noch verschiedene Exits zur Verfügung, die wir uns im nächsten Artikel einmal genauer anschauen werden. Von hier aus, kannst du aber auch in die ausführende Klasse abspringen.
Job-Template
Bevor wir allerdings den Job nun in der "Application Jobs" App finden, müssen wir erst noch ein Template anlegen. Wie beim Job-Katalog können wir dies über den Wizard als neues Objekt machen, es handelt sich dabei um ein Objekt vom Typ "Application Job Template".
Das Template wird angelegt und du findest alle definierten Elemente der Parameter in der Liste wieder. Dort werden die Vorschlagswerte für das Template gesichert und bei Ausführung geladen.
Es wird nun eine Meldung angezeigt, dass der Pflichtparameter nicht vorbelegt ist, wir können das Objekt allerdings trotzdem aktivieren. Die Prüfung wird vor allem bei der Einplanung des Jobs relevant.
Logik
Der wichtigste Bestandteil des Jobs ist allerdings die Ausführungslogik, diese haben wir bisher noch gar nicht berücksichtigt und müssen diese noch in unserer Klasse implementieren. Dazu gehen wir in die Klasse und erweitern die Logik um zwei Schritte.
Parameter parsen
Im ersten Schritt erhalten wir alle Eingaben als Tabelle und sollten diese für unseren Prozess aufbereiten. Dieser Schritt ist optional, kann allerdings für die Folgeschritte für mehr Übersichtlichkeit sorgen. Zuerst einmal definieren wir Felder, die die Inhalte unserer Parameter aufnehmen.
DATA ld_text TYPE c LENGTH 50.
DATA lt_r_country TYPE RANGE OF I_Country-Country.
DATA ld_radio_text TYPE abap_boolean.
DATA ld_radio_country TYPE abap_boolean.
DATA ld_test TYPE abap_boolean.
Nun können wir die Inhalte der Tabelle parsen und auf die verschiedenen Felder verteilen. Bei Parametern reicht eine einfache Zuweisung der Inhalte, bei den SELECT-OPTIONS müssen wir jede Zeile anhängen, da es hier mehr Einträge geben kann, die zu einer Select-Option gehören.
LOOP AT it_parameters INTO DATA(ls_parameter).
CASE ls_parameter-selname.
WHEN 'TEXT'.
ld_text = ls_parameter-low.
WHEN 'COUNTRY'.
INSERT CORRESPONDING #( ls_parameter ) INTO TABLE lt_r_country.
WHEN 'R_TEXT'.
ld_radio_text = ls_parameter-low.
WHEN 'R_LAND'.
ld_radio_country = ls_parameter-low.
WHEN 'TEST'.
ld_test = ls_parameter-low.
ENDCASE.
ENDLOOP.
Hinweis: Die Namen der Parameter können maximal 8-stellig sein, hier gibt es offensichtlich noch Abhängigkeiten zu echten Selektionsbildern. Es empfiehlt sich die Nutzung eines Konstanten-Interface für die Definition der Parameter und das Auflösen in Variablen.
Logik ausführen
Nachdem nun alle Inhalte auf Variablen verteilt wurden, können wir nun mit der Implementierung der eigentlichen Logik beginnen. Hierbei handelt es sich um eine Demo Logik, es kann genauso gut eine andere Klasse oder Methode aufgerufen werden, der Einfachheit halber machen wir dies in der gleichen Methode. Im ersten Schritt befüllen wir die Standardfelder für jeden Datensatz.
DATA(ls_database) = VALUE zbs_dmo_jtrack( identifier = cl_system_uuid=>create_uuid_x16_static( )
rdate = cl_abap_context_info=>get_system_date( )
rtime = cl_abap_context_info=>get_system_time( )
test = ld_test ).
Dann wählen wir, welche Logik ausgeführt werden soll. Ist der Radiobutton für Text gesetzt, dann übernehmen wir den Text, ansonsten werden die Länder über die Selektion gelesen und die Namen als Komma getrennte Liste übernommen.
CASE abap_true.
WHEN ld_radio_text.
ls_database-content = ld_text.
WHEN ld_radio_country.
SELECT FROM I_CountryText
FIELDS CountryName
WHERE Country IN @lt_r_country
and Language = @sy-langu
INTO TABLE @DATA(lt_names).
ls_database-content = concat_lines_of( table = lt_names sep = `, ` ).
ENDCASE.
Zum Abschluss schreiben wir den neuen Datensatz auf die Datenbank, um später den Lauf prüfen zu können und ob die Logik, sowie die Übergabe, sauber funktioniert hat.
INSERT zbs_dmo_jtrack FROM @ls_database.
COMMIT WORK.
Application Jobs
Nachdem wir nun die Klasse, den Katalog und das Template angelegt haben, können wir in die App "Application Jobs" gehen, um das Ergebnis der Generierung zu prüfen und unseren ersten Job zu starten. Über die Suchhilfe können wir nun das Job-Template wählen.
Die Ausführung setzen wir auf "Sofort", um das Ergebnis direkt zu bekommen. Nun können wir auch unsere Parameter zum ersten Mal sehen und entsprechende Eingaben für den Job durchführen.
Über "Schedule" startet der Job entsprechend. Nun führen wir noch einen zweiten Lauf aus, setzen allerdings das Testkennzeichen und auf Text, um Änderungen im Ergebnis zu sehen. Nach dem Lauf der beiden Jobs, können wir in unsere Logging-Tabelle schauen.
Komplettes Beispiel
Hier noch einmal die komplette Implementierung der Job-Klasse, damit kannst du diesen Job in deinem System nachbauen.
CLASS zcl_bs_demo_job_adt_exe DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_apj_dt_exec_object.
INTERFACES if_apj_rt_exec_object.
ENDCLASS.
CLASS zcl_bs_demo_job_adt_exe IMPLEMENTATION.
METHOD if_apj_dt_exec_object~get_parameters.
et_parameter_def = VALUE #( datatype = 'C'
changeable_ind = abap_true
( selname = 'TEXT'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Free text'
length = 50
lowercase_ind = abap_true
mandatory_ind = abap_true )
( selname = 'COUNTRY'
kind = if_apj_dt_exec_object=>select_option
param_text = 'Country'
length = 3 )
( selname = 'R_TEXT'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Text'
length = 1
radio_group_ind = abap_true
radio_group_id = 'R1' )
( selname = 'R_LAND'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Country'
length = 1
radio_group_ind = abap_true
radio_group_id = 'R1' )
( selname = 'TEST'
kind = if_apj_dt_exec_object=>parameter
param_text = 'Test'
length = 1
checkbox_ind = abap_true ) ).
et_parameter_val = VALUE #( sign = 'I'
option = 'EQ'
( selname = 'TEST' low = abap_true )
( selname = 'R_TEXT' low = abap_false )
( selname = 'R_LAND' low = abap_true ) ).
ENDMETHOD.
METHOD if_apj_rt_exec_object~execute.
DATA ld_text TYPE c LENGTH 50.
DATA lt_r_country TYPE RANGE OF I_CountryText-Country.
DATA ld_radio_text TYPE abap_boolean.
DATA ld_radio_country TYPE abap_boolean.
DATA ld_test TYPE abap_boolean.
LOOP AT it_parameters INTO DATA(ls_parameter).
CASE ls_parameter-selname.
WHEN 'TEXT'.
ld_text = ls_parameter-low.
WHEN 'COUNTRY'.
INSERT CORRESPONDING #( ls_parameter ) INTO TABLE lt_r_country.
WHEN 'R_TEXT'.
ld_radio_text = ls_parameter-low.
WHEN 'R_LAND'.
ld_radio_country = ls_parameter-low.
WHEN 'TEST'.
ld_test = ls_parameter-low.
ENDCASE.
ENDLOOP.
DATA(ls_database) = VALUE zbs_dmo_jtrack( identifier = cl_system_uuid=>create_uuid_x16_static( )
rdate = cl_abap_context_info=>get_system_date( )
rtime = cl_abap_context_info=>get_system_time( )
test = ld_test ).
CASE abap_true.
WHEN ld_radio_text.
ls_database-content = ld_text.
WHEN ld_radio_country.
SELECT FROM I_CountryText
FIELDS CountryName
WHERE Country IN @lt_r_country
and Language = @sy-langu
INTO TABLE @DATA(lt_names).
ls_database-content = concat_lines_of( table = lt_names sep = `, ` ).
ENDCASE.
INSERT zbs_dmo_jtrack FROM @ls_database.
COMMIT WORK.
ENDMETHOD.
ENDCLASS.
Anlage (API)
Für die Anlage der Objekte gibt es im System auch eine ABAP API, die vor allem zu Zeiten genutzt wurde, als die ABAP Development Tools die Objekte für die Jobs noch nicht unterstützt haben. Dazu benötigst du allerdings ebenfalls schon die Ausführungsklasse für den Job, bevor du dir mit der API den Katalog und das Template generieren lassen kannst. Dazu einmal ein Stück Beispiel-Code:
CLASS zcl_bs_demo_poc_job_cre DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_oo_adt_classrun.
ENDCLASS.
CLASS zcl_bs_demo_poc_job_cre IMPLEMENTATION.
METHOD if_oo_adt_classrun~main.
CONSTANTS lc_transport TYPE cl_apj_dt_create_content=>ty_transport_request VALUE '<TRANSPORT_NUMBER>'.
CONSTANTS lc_package TYPE cl_apj_dt_create_content=>ty_package VALUE '<PACKAGE_NAME>'.
TRY.
DATA(lo_dt) = cl_apj_dt_create_content=>get_instance( ).
lo_dt->create_job_cat_entry( iv_catalog_name = 'ZBS_DEMO_POC_JOB_CATAPI'
iv_class_name = 'ZCL_BS_DEMO_POC_JOB_EXE'
iv_text = 'DEMO: Job API'
iv_catalog_entry_type = cl_apj_dt_create_content=>class_based
iv_transport_request = lc_transport
iv_package = lc_package ).
out->write( |Job catalog entry created successfully| ).
CATCH cx_apj_dt_content INTO DATA(lo_apj_dt_content).
out->write( |Creation of job catalog entry failed: { lo_apj_dt_content->get_text( ) }| ).
ENDTRY.
TRY.
NEW zcl_bs_demo_job_adt_exe( )->if_apj_dt_exec_object~get_parameters(
IMPORTING et_parameter_val = DATA(lt_parameters) ).
IF 1 = 1.
lo_dt->create_job_template_entry( iv_template_name = 'ZBS_DEMO_POC_JOB_TEMPAPI'
iv_catalog_name = 'ZBS_DEMO_POC_JOB_CATAPI'
iv_text = 'DEMO: Job API'
it_parameters = lt_parameters
iv_transport_request = lc_transport
iv_package = lc_package ).
out->write( |Job template created successfully| ).
ELSE.
lo_dt->delete_job_template_entry( iv_template_name = 'ZBS_DEMO_JOB_TEMPLATE_DEFAULT'
iv_transport_request = lc_transport ).
out->write( |Job template deleted successfully| ).
ENDIF.
CATCH cx_apj_dt_content INTO lo_apj_dt_content.
out->write( |Creation of job template failed: { lo_apj_dt_content->get_text( ) }| ).
RETURN.
ENDTRY.
ENDMETHOD.
ENDCLASS.
Der Code ruft die API auf und legt zuerst den Job-Katalog an, im zweiten Schritt werden aus der Ausführungsklasse die Parameter gelesen und damit das Template generiert, die API setzt aber keine Default-Werte im Template.
Hinweis: Die Anlage von Job-Katalog und Template über die API wird nur noch bedingt empfohlen, es sollte auf die Standardwerkzeuge im ADT zurückgegriffen werden, da hier immer die neuesten Features implementiert sind.
Fazit
Für die Erstellung eines Jobs sind einige Schritte und Objekte nötig, am Ende ist aber alles viel einfacher als man denkt und man hat eine sehr übersichtliche Möglichkeit, Jobs für Aufgaben zu erstellen. Mit den Parametern hat man die maximale Flexibilität für Eingaben von außen, dies ähneln sehr stark den Selektionsbildern von Reports.