Skriptbibliothek
Einführung
Die Skript-Bibliothek ist eine Sammlung für Skript-Klassen und Skript-Methoden, die unter anderem in Prozess-Skripten und in Masken-Skripten eingesetzt werden können. Die Skript-Klassen kapseln wiederverwendbaren Code aus verschiedenen Bereichen, bieten eine bestmögliche Dokumentation zu groovy-Skripten und stellen die Skript-Qualität durch eine automatisierbare Testabdeckung sicher.
Die zentralen Geschäftslogiken Ihrer individuellen CRM-Lösung können in der Skript-Bibliothek gesammelt werden. In der Skript-Bibliothek befinden sich (übersteuerbare) Standard-Klassen (C0), Klassen aus installierten (C1-)Modulen als auch Eigenimplementierungen (in der C2-Schicht).
Das Dashboard der Skript-Bibliothek zeigt die Übersicht aller verfügbaren Skript-Klassen, deren Methoden und Dokumentation.
Unterstützung der Tastaturbedienung in der Skriptbibliothek
Die Funktionalitäten der Skriptbibliothek kann nun weitestgehend mit der Tastatur bedient werden.
Der Hotkey einer Funktion wird im Tooltip des Schalters angezeigt.
Globale Hotkeys in der Skriptbibliothek
Neue Skriptklasse: STRG+EINFG
Neue Testklasse (sollte analog zur Neuanlage Skriptklasse geändert werden): STRG+ALT+EINFG
Importieren: STRG+ALT+I
Alle Test aller Skript-Klassen ausführen: STRG+ALT+UMSCHALT ⇧+T
Spezifische Hotkeys im Dashboard
Klasse Löschen STRG+ENTF
Klasse exportieren STRG+UMSCHALT ⇧+E
Namen ändern STRG+ALT+R
Bearbeiten STRG+ENTER
Lesen STRG+ALT+ENTER
Klasse testen STRG+ALT+T
Spezifische Hotkeys für eine geöffnete Skriptbibliothek
Skriptklasse speichern STRG+S
Den ausgewählten Skript-Code exportieren STRG+UMSCHALT ⇧+E
Klasse testen STRG+ALT+T
Aktions-/Befehlsübersicht F1
Methodenübersicht öffnen STRG+ALT+M
Status Minimap toggeln STRG+ALT+UMSCHALT ⇧+M
Dialoge können mit der Tastatur bedient werden: Fokussieren der Schalter mittels ↹ TAB und Auswahl mit EINGABE ↵.
Neue Skript-Klasse erstellen
Eine Skript-Klasse bietet die Möglichkeit, Einstellungen und Logiken eines Moduls oder einer Kundenanforderung in Form von groovy-Skripten zu sammeln und zu kapseln. Der Name der Skript-Klasse sollte das Thema und die Verwendungsart direkt widerspiegeln. Je nach Umfang der Anforderung kann es notwendig sein, den Code in mehreren Skript-Klassen aufzuteilen, besonders wenn Funktionen auch in anderen Modulen eingesetzt werden sollen.
Beispiele für gute Klassen-Namen:
TicketUtils
- Zusammenfassung mehrerer Ticket-bezogener Utilities (nützlicher Methoden)TaskController
- Controller-Klasse zum Zugriff auf Aufgaben und der Weiterleitung von Verarbeitungsanforderungen zu AufgabenQuoteServices
- Dienstmethoden für die Verarbeitung von Angeboten<SERVICEPROVIDER>ServiceController
- Controller-Klasse zum Zugriff auf Methoden eines Drittanbieters über Webservices
Generell sollten aber Themengebiete in einer Skript-Klasse zusammengefasst sein, da man in einer Skript-Klasse schneller navigieren kann und sich eine Vielzahl von Skript-Klassen auf die Startzeit von Applikationsserver und Windows-Client auswirkt.
Der Name der Skriptklasse beginnt immer mit SC
und in C1-Modulen mit der Partner-ID des Systems, wie z.B. SC11
. Dieser Präfix kann nicht entfernt werden. Es ist ratsam, bei mehreren Klassen zu einem Modul, den Namen des Moduls (evtl. als 2-3-stelliges Kürzel) als weiteren Präfix mit voranzustellen und die Ausprägung beginnend mit einem Großbuchstaben anzuhängen, wie zum Beispiel:
SCSurveyUtils
SCSurveyCloudUtils
SCSVCloudUtils
Die Beschreibung bzw. Dokumentation der Skript-Klasse dient dem Verwender oder späteren Bearbeiter der Skript-Klasse zur ersten Orientierung mit dieser Klasse. Einfache HTML-Formatierungselement (siehe Dokumentation) können in der Beschreibung eingesetzt werden, um sie für den Anwender lesbarer zu gestalten. Die Dokumentation der Skriptklasse kann auch später für andere Sprachen ergänzt werden. Der Schlüssel für den Klassen-Kommentar lautet in unserem Beispiel SCSurveyUtils.desc
.
Falls der Name der gerade angelegten Skript-Klasse geändert werden muss, kann er bis zur ersten Veröffentlichung (Customizing-Transport oder Modul-Export) noch angepasst werden.
Bestehende Skript-Klasse erweitern
Sind neue Anforderungen an ein Modul gestellt, muss entschieden werden, wo und wie die Erweiterung hinterlegt werden soll. Könnte der Skript-Code an mehreren Stellen Verwendung finden oder soll er in Kundenprojekten einfach angepasst (übersteuert) werden können, sollte der Code nicht im Masken- oder Prozess-Skript selbst, sondern in der Skript-Klasse als neue Methode hinterlegt werden. Manchmal ist es auch mit der neuen Anforderung notwendig, einen Teil des Skript-Codes aus einem Prozess in die Skript-Klasse zu verschieben.
Bei der Implementierung von Geschäftslogiken sind unter anderem folgende Fragestellungen hilfreich, um zu entscheiden, ob der Code in eine Skript-Klasse ausgelagert werden soll:
In Skript-Klasse auslagern | |
---|---|
Logik wird wiederverwendet / ist wiederverwendbar | |
Testfall kann für mehrzeilige Logik beschrieben werden | + Testfall |
Mehrzeilige Logik kann durch sprechenden Methodenbezeichner mit eindeutiger Ein-/Ausgabe beschrieben werden (z.B. delegateTicket(IContainer ticket, IContainer targetUser) | |
Kapselbare Logik soll als übersteuerbar bereitgestellt werden | |
Bestehende Standardlogiken werden erweitert |
Die passende Skript-Klasse kann im Dashboard ausgewählt werden. Das Filtern erlaubt es, die Ansicht einzuschränken und so die Klasse schneller zu finden. Beim Öffnen werden die Methoden in der Reihenfolge angezeigt, in der sie gespeichert worden sind. Außerdem werden Kommentare außerhalb des Methoden-Codes entfernt, sofern diese keine Kommentare zur Dokumentation sind.
Das Bearbeiten einer Klasse ist gleichzeitig nur von einer Person erlaubt. Solange die Skript-Klasse in einem eigenen Tab geöffnet ist, wird eine Sperre auf ihr eingerichtet. Der Versuch, die Skript-Klasse durch einen anderen Anwender oder in einer anderen Session zu öffnen, ist zwar möglich, aber der Schreibschutz verhindert eine gleichzeitige Bearbeitung und wird durch einen Hinweis auf den aktuellen Bearbeiter angemerkt.
Jede Veränderung an der Klasse oder den Methoden wird nach dem Speichern im System sofort sichtbar. Das gilt für den aktuellen Client, in dem die Bearbeitung stattfindet, sowie allen Prozessen, in dem die Skript-Klasse verwendet wurde. In anderen Client-Sessions wird die Änderung erst nach erneutem Anmelden sichtbar.
Es ist ratsam vor jedem Speichern einen kurzen Kommentar zu den Änderungen zu hinterlegen. Dieser Kommentar hilft, sich in der Änderungshistorie besser zurechtzufinden, nachfolgenden Skriptentwicklern einen Kurzüberblick über die Situation zu geben und besonders im Fehlerfall die betreffende Anpassung zu identifizieren.
Skript-Code aus der Historie vergleichen und wiederherstellen
Vor dem Speichern von umfangreichen Änderungen an der Skript-Klasse sollte man die Anpassungen mit dem letzten Stand vergleichen, z.B. um evtl. noch vorhandenen Debug-Code oder Ähnliches vorher noch zu korrigieren.
Der Vergleich ist zwischen verschiedenen Versionen aus der Historie oder mit den aktuellen, ungespeicherten Änderungen im Editor möglich.
Über die Auswahl-Felder kann zwischen den Versionen in der Historie gewechselt werden. Jeder Stand kann in die Zwischenablage kopiert, als Datei gespeichert oder im Editor wiederhergestellt werden. Ein direktes Bearbeiten des Skript-Codes ist im Vergleichs-Editor nicht möglich.
Über den Pfeil-Button <- "nach links" kann der Vergleichs-Editor wieder verlassen werden.
Neue Methode anlegen
Die Code-Vervollständigung (Autovervollständigung) des Editors kann mit STRG+LEERTASTE geöffnet werden. Wird diese außerhalb von Methoden, nicht direkt über dem Methodenkopf, ausgeführt, werden zwei Methoden-Templates angeboten.
Private Methode: Diese ist nur innerhalb der eigenen Skript-Klasse zu verwenden
new private method
GROOVY@BpmMaskScript @MaskScript @BpmScript private Type methodName() { ... }
Öffentliche Methode: Diese kann von weiteren Skript-Klassen, Prozessen und Maskenskripten verwendet werden (sofern die entsprechenden Annotations gepflegt sind, siehe unten)
new public method
GROOVY@BpmMaskScript @MaskScript @BpmScript private Type methodName() { ... }
Der Rückgabewert, der Name der Methode und Parameter können nun an der Methoden-Signatur angepasst werden. Der Name einer Methode muss innerhalb der Klasse eindeutig sein. Es ist nicht möglich denselben Methoden-Namen mit verschiedenen Parametern zu überladen.
Ungültige Datentypen werden im Editor als Fehler erkannt. Das Kontextmenü bietet die verfügbaren Datentypen zur Auswahl an. Besonders für die Generalisierung von Listen und Maps steht nur eine eingeschränkte Auswahl zur Verfügung. Die Platzhalter ("Type", "T", "K" oder "V") sollten vor dem Nachschlagen entfernt werden, damit die Auswahl korrekt angezeigt werden kann.
Ist kein Rückgabewert an der Methode notwendig, wird hierfür der Typ "void
" ausgewählt.
Verwendungsstellen der Methode anpassen
An welchen Stellen im CRM die Skript-Methode eingesetzt werden kann, bestimmen die Annotationen am Methoden-Kopf, welche mit dem @-Symbol gekennzeichnet sind und in der Zeile direkt über der Methode nachgeschlagen werden können. Nur Methoden mit denselben Verwendungsstellen können sich gegenseitig aufrufen.
@BpmScript
- Verwendung in Prozess-Skript-Aktionen@BpmMaskScript
- Verwendung im Masken-Skript von Prozessen@MaskScript
- Verwendung im Maskenskript@EventRuleScript
- Verwendung in der Startbedingung von Prozessen@ClientScript
- Verwendung im Prozess-Zwischenereignis vom Typ "Client-Skript"
Methode bearbeiten
Beim Anpassen von bestehenden Methoden gilt eine besondere Sorgfalt, da sich Änderungen auch auf schon laufende Prozess-Instanzen direkt auswirken können. Im Entwicklungssystem, in der in der Regel die Bearbeitung der Skript-Klassen stattfindet, sollten daher die Instanzen der betroffenen Prozesse vorsorglich beendet oder gelöscht werden.
Solange eine Methode noch nicht veröffentlicht wurde (durch Customizing-Transport oder Modul-Export), kann die Signatur der Methode überarbeitet werden. Eine veröffentlichte Methode ist mit der Annotation @Released
gekennzeichnet, welche auch nicht mehr entfernt werden kann.
Wurde die Methode mit der alten Signatur schon in Skripten verwendet, müssen diese Stellen manuell angepasst werden. Innerhalb der Skript-Klasse kann man die Referenzen über das Kontextmenü auf dieser Methode anzeigen lassen. Es gilt zu beachten, dass eine geänderte Methode auch Änderungen in den Test-Skripten nach sich ziehen kann.
Methode oder Parameter umbenennen
Das Umbenennen einer Methode ist nur möglich, wenn die Skript-Klasse und diese Methode in keinem Prozess, Maskenskript oder einer anderen Skript-Klasse als Abhängigkeit hinzugefügt wurde. Es ist dennoch notwendig, müssen die bestehenden Abhängigkeiten entfernt werden und später mit dem neuen Methodennamen wieder hinzugefügt werden.
Eine Methode oder ein Parameter sollte nur mit den Mitteln des Editors mit F2 umbenannt werden. Nur so ist sichergestellt, dass die vorhandene Dokumentation für den neuen Namen ebenfalls geändert wird und beim Speichern erhalten bleibt. Ebenso wird der Code im Test-Skript automatisch angepasst.
Hilfsklassen im Skript verwenden
Innerhalb der Methoden können die Skript-Befehle und Konstanten aufgerufen werden. Die Code-Vervollständigung bietet über STRG+LEERTASTE je nach definierten Verwendungsstellen der Methode verschiedene Klassen an. Die verfügbaren Methoden dieser Klasse sind auch von den definierten Verwendungsstellen abhängig.
Die bisherige Eingabe im Editor bestimmt, welche Methoden zur Auswahl angeboten werden. Dabei muss man nicht den Klassennamen der Hilfskasse kennen, um eine bestimmte Methode zu ermitteln und einzufügen.
Mit Eingabe der öffnenden Klammer einer Methode wird die Dokumentation des Parameters angezeigt. Für weitere Parameter erfolgt die Anzeige nach Eingabe des Kommas. Wurde die Parameterhilfe geschlossen, kann sie erneut mit STRG+UMSCHALT ⇧+LEERTASTE geöffnet werden. Um sich die gesamte Methoden-Dokumentation erneut anzuzeigen, kann sie mit der Mauszeiger über den Methodennamen als Tooltip wieder eingeblendet werden.
Methode ausblenden und durch neue Version ersetzen
Im Laufe der Erweiterung von Skript-Klassen und deren Methoden ist es manchmal notwendig, die Signatur einer Methode zu verändern, obwohl sie schon veröffentlicht ist. Da dies wegen der Abwärtskompatibilität verhindert wird, bleibt nur der Weg einer Kopie der Methode mit einem neuen, erweiterten Namen (z.B. method → methodNew
).
Die alte Methode bleibt bestehen und verweist evtl. im Code auf die neue Methode. Damit die alte Methode aber nicht mehr an anderer Stelle verwendet wird, kann sie mit der Annotation @Deprecated
als veraltet gekennzeichnet werden. Die Methode ist immer noch aufrufbar, wird aber in der Code-Vervollständigung nicht mehr angeboten. Stattdessen soll nun die neue Methode verwendet werden. Die Kennzeichnung @Deprecated
kann jederzeit hinzugefügt oder entfernt werden.
Methoden dokumentieren
Damit die Verwendung einer Methode auch ohne Einsicht des Codes möglich ist, sollte zu jeder öffentlichen Methode und zu den relevantesten privaten Methoden eine vollständige Dokumentation inkl. Code-Beispiel hinterlegt werden.
Die Dokumentation von Methoden wird über spezielle Kommentare (Javadoc) direkt an den Methoden hinterlegt. Die Sprache der Dokumentationskommentare wird über das Auswahlmenü in der Toolbar ausgewählt.
Dokumentationskommentare orientieren sich an dem Format von Javadoc folgen daher einem festen Aufbau, der im folgenden dargestellt ist.
Dokumentationskommentare
/**
* Allgemeine Beschreibung der Methode
*
* @param parameterName Beschreibung des Parameters
*
* @return Beschreibung des Rückgabewerts (entfällt im Fall von "void")
*
* @example
Codebeispiel zur Verwendung der Methode
...
*/
@BpmScript
Object method(String parameterName)
{
// ...
}
Folgende einfachen HTML-Tags können problemlos zur Formatierung eingesetzt werden.
fett:
<b>Text</b>
kursiv:
<i>Text</i>
Zeilenumbruch:
<br>
Aufzählung:
<ul> <li>Element1</li> <li>Element2</li> </ul>
Nummerierung:
<ol> <li>Element1</li> <li>Element2</li> </ol>
Weiterhin besteht die Möglichkeit die Dokumentation jederzeit über die entsprechende Internationalisierungs-Aktion anzupassen. Anpassungen in dieser Ansicht wirken sich direkt auf die Dokumentationskommentare an den Methoden aus.
Für jede Methode und jeden Methoden-Parameter existieren Schlüssel, für welche die Dokumentation mit einfacher HTML-Formatierung hinterlegt werden können.
methodName.desc
- Allgemeine Beschreibung der Methode.methodName.return
- Beschreibung des Rückgabewertes. Kann im falle von "void
" entfallen.methodName.code
- Codebeispiel zum Verwenden der Methode. Der Code sollte fehlerfrei sein und Vor- und Nachbereitung der Methode beinhalten, damit man ihn aus dem Kommentar zur direkten Verwendung herauskopieren kann.methodName.paramter.desc
- Die Beschreibung des Parameters
Zusätzlich existiert noch ein Schlüssel für die Beschreibung der gesamtem Skript-Klasse
scriptClassName.desc
- Die Beschreibung der Skript-Klasse
Für die Formatierung können dieselben HTML-Tags eingesetzt werden, die auch in den Dokumentationskommentaren verwendet werden können.
Methoden inkl. Dokumentation exportieren
Für eine gute Übersicht der Skript-Klasse kann der gesamte Code inkl. Klassen- und Methoden-Dokumentation exportiert werden.
Der Export ist abhängig von der gewählten Schicht und von angezeigtem Test-Code.
SCActivityUtils.groovy
/**
* Logik-Klasse für Aktivitäten-Verarbeitung
*/
class SCActivityUtils
{
/**
* Die Differenz zwischen Start- und Ende-Datum bei der Neuanlage einer Aktivität
*
* @return Die Standard-Differenz in Minuten
*/
Integer getDefaultDuration()
{
return 15;
}
/**
* Die Standard-Art für Aktivitäten
*
* @return Der Schlüssel für die Aktivitätenart
*/
String getDefaultType()
{
return "S_KEYTAB_TYPE_E";
}
}
Methode aus tieferer Schicht übersteuern
Die Skript-Klassen aus CURSOR-Standard-Modulen oder Partner-Modulen können in der höheren Schicht übersteuert werden. Eine überladene Methode wird mit der Annotation @Override
gekennzeichnet.
Dabei ist es nur erlaubt, den Code von Methoden zu verändern, nicht aber Signaturen anzupassen oder neue Methoden aufzunehmen.
Das Skript der übersteuerten Klasse kann schreibgeschützt geöffnet werden. Die Navigation am rechten Rand des Editor erlaubt die Navigation in den Schichten. Nur der Code der aktuellen Schicht des Clients kann bearbeitet werden.
Über die Code-Vervollständigung können noch nicht übersteuerte Methoden der aktuellen Schicht hinzugefügt werden. Entweder wird der Code aus der tieferen Schicht kopiert und verändert oder aber der Code der übersteuerten Methode wird mittels des super
-Schlüsselworts aufgerufen. Die Methode kann dann vor oder nach dem Aufruf erweitert werden.
Durch die Veröffentlichung der Methode ist sichergestellt, dass die Signatur der übersteuerten Methoden stabil bleibt. Dennoch kann es vorkommen, dass sich durch ein Update der Version oder ein Update des Modules der Sinnzusammenhang der Methode geändert hat und im Kundenprojekt die übersteuerte Logik angepasst werden muss. Die notwendigen Anpassungen an den Skripten sind in der Updatedokumentation der Module zu finden.
Den Code einer übersteuerten Methode aufrufen
Um aus der übersteuerten Methode in der C2-Schicht in die tiefere Schicht zu springen, gibt es zwei Möglichkeiten.
Die erste Möglichkeit erlaubt es über den super-Aufruf mit Strg+linke Maustaste in die referenzierte Methode zu springen.
Die Tastenkombination Strg+F12 auf dem Methodennamen öffnet die Referenz-Übersicht der Methoden.
Hier werden die möglichen Schichten der Methode angezeigt und können darüber geöffnet werden.
ScriptReturn als Methoden-Ergebnis auswerten
Ein besonderer Rückgabewert ist der Datentyp ScriptReturn
. Dieser erlaubt es mehrere Wert aus einer Methode zurück zu liefern und zusätzlich auch den Status der erfolgreichen Ausführung oder im Fehlerfall die Fehlermeldungen mitzuführen:
Zugriffsmethoden der Klasse ScriptReturn
// Erzeugung
Map<String, Double> map = new HashMap<>();
map.put("var1", new Double(1));
map.put("var2", new Double(2));
ScriptReturn scriptReturn = new ScriptReturn(true, "Kein Fehler", map);
// Auswertung
boolean isSuccessful = scriptReturn.isSuccessful();
String errorCode = scriptReturn.getErrorCode();
Map<String, Double> variables = scriptReturn.getVariables()
Sprachvariablen einbinden
Eigene sprachspezifische Beschriftungen oder Beschreibungen können in den Sprachvariablen hinzugefügt werden.
Jede Sprachvariable kann über ihren eindeutigen Schlüssel im Skript ausgelesen werden. Auf die verfügbaren Schlüssel kann über die Gruppierung "I18nConstants
" via Autovervollständigung zugegriffen werden.
// I18nConstants.MyVariable -> "MyVariable"
String i18nValue = I18nUtils.i18nCustom("MyVariable", SessionConstants.LOCALE);
Wird die Skriptklasse innerhalb eines Moduls eingebunden, sollte die Beschriftungen nicht in den allgemeinen Sprachvariablen hinterlegt werden, sondern in den Sprachvariablen des Moduls (siehe Administrations-Konsole).
// ModuleId.Kundenservice -> "TICKET"
String i18nValue = I18nUtils.i18nModule("TICKET", "MyVariable", SessionConstants.LOCALE);
Andere Skript-Klassen einbinden
Wurde der Modul-Code auf verschiedene Klassen verteilt oder wurden Hilfsmethoden in eine Hilfsklasse ausgelagert, so will man diese Methoden in der eigene Skript-Klasse einbinden und aufrufen.
Die Customizing-Bibliothek erlaubt es ganze Skript-Klassen oder nur ausgewählte Methoden der eigenen Klasse hinzuzufügen. Hierbei entsteht ggf. eine Abhängigkeit zwischen zwei Customizing-Paketen oder Modulen.
Die Skript-Klasse steht mit den ausgewählten, öffentlichen und nicht veralteten Methoden in der Code-Vervollständigung zur Verfügung. Dabei wird die Definition der Verwendungsstellen (per Annotation) der aufrufenden und der aufgerufenen Skript-Methode gegeneinander geprüft. Der Zugriff erfolgt über den Namen der Skript-Klasse und der Methode.
SC0EntityLogic.beforeCopy(...);
Abhängiges Customizing einbinden
Suchen, Entitäten und Globale Variablen können auch zu einer Skript-Klasse hinzugefügt werden. Hierbei entsteht ggf. eine Abhängigkeit zwischen zwei Customizing-Paketen oder Modulen. Eine Erweiterung der Abhängigkeiten ist auch für Skript-Klassen aus tieferen Schichten möglich. Hierbei ist aber die Auswahl auf die aktuelle Schicht beschränkt.
Entitäten hinzufügen
Die Entität steht via ihres übersetzten Bezeichners direkt in der Code-Vervollständigung zur Verfügung. Unterhalb dieser Gruppierung kann auf alle Felder dieser Entität zugegriffen werden.GROOVY// Aktivität -> Activity // Activity.Beginnt am -> StartDate.Activity FieldUtils.getValue("StartDate.Activity");
Suchen hinzufügen
Suchen werden mit dem Bezeichner (plainkey) unterhalb der Gruppierung "Searches
" angeboten.GROOVY// Searches->Standard_Opportunity -> "Standard_Opportunity" WorkSpaceScriptUtils.searchForRead("Standard_Opportunity");
Globale Variablen hinzufügen
Globale Variablen stehen aus der aktuellen Schicht zur Verfügung und können über die Gruppierung "VariablesGlobal
" ausgewählt werden.GROOVY// VariablesGlobal.MyVariable -> "MyVariable" VariableUtils.getGlobalVariable("MyVariable");
Skript-Klasse einbinden und aufrufen
Eine erstelle Skript-Klasse bzw. Skript-Methode kann an verschiedenen Stellen im CRM eingebunden werden.
Masken-Skript
Im Masken-Skript kann die Skript-Klasse in der Customizing-Bibliothek hinzugefügt werden. Alle gewählten Methoden mit der Annotation @MaskScript stehen unter dem Klassennamen in der Code-Vervollständigung zur Verfügung.Prozess
Die Skript-Klasse kann mit einer Auswahl ihrer Methoden einem Prozess über seine Prozess-Bibliothek hinzugefügt werden. Dabei gelten folgende Verfügbarkeitsregeln:Startbedingung: Es stehen alle Methoden mit der Annotation
@EventRuleScript
zur Verfügung.Skript-Aktion: Es stehen alle Methoden mit der Annotation
@BpmSkript
zur Verfügung.Benutzer-Aktion: Es stehen alle Methoden mit der Annotation
@BpmMaskScript
zur Verfügung.Zwischenereignis Client-Skript: Es stehen alle Methoden mit der Annotation
@ClientScript
zur Verfügung.
Zeit-Aktion
Eine Skript-Methode kann als zeitliche (Timer-basierte) Aktion angesprochen und zyklisch ausgeführt werden. Dazu wird sie als entsprechende Aktion in der Administrationskonsole hinterlegt. Als Name der Aktion vom TypSCRIPTCLASS
wird "<classname>.<methodname>
" eingetragen. Die Signatur der Skriptklasse ist auf wenige Arten beschränkt. Der String-Parameter der Aktion kann für die Konfiguration der Skript-Methode verwendet werden. Der Rückgabewert wird hierbei ignoriert.method()
method(String parameter)
method(Map<String, String> map) // map.get("parameter")
Rest-API und Kacheln
Eine Skript-Methode kann über die Rest-API und aus Kacheln heraus angesprochen werden. Die Methode muss für den externen Zugriff (oder den Zugriff über Kacheln) mit der Annotation@Remote
gekennzeichnet sein. Die Dokumentation der Schnittstelle ist online verfügbar (aktuell ist die Schnittstellte aber noch privat, da es noch Änderungen daran geben kann. Eine Kundenfreigabe erfolgt erst mit der öffentlichen Schnittstelle):CODEhttps://<host>:<port>/rest/private/doc/v1/services/scriptlibrary
Zum Aufruf einer Skript-Klasse in Kacheln des Infoboards steht der
ScriptLibraryController
undScriptLibraryParamsBuilder
bereit.
Skript-Methode über Rest-Service ausführen
Die Rest-API bietet die Möglichkeit eine @Remote
und @BpmSkript
gekennzeichnete Methode aufzurufen. Die Dokumentation der Rest-API kann mit folgender URL aufgerufen werden:
https://server:port/rest/doc/v1/services/scriptlibrary
Die Services benötigen eine gültige Autorisierung mit Benutzername und Passwort oder eines Session-Tokens. Die Sprache des Aufrufs kann durch dien Angabe "Accepted-Language: de-DE" gesteuert werden oder fällt auf die Standard-Sprache des Applikationsservers bzw. Server-Systems zurück.
Die Rest-API bietet zwei Services über dieselbe URL an, um die Dokumentation der Methode auslesen zu können und diese aufzurufen.
GET: /rest/api/scriptlibrary/v2/classes/{classname}/{methodname}
-- HTTP-Status --
200: OK
-- Response-Body --
/**
* Remote call method.
*
* @param parameter The method parameter.
*
* @return The method result.
*
* @example
String result = SCRemoteCall.remoteCall("string");
*/
@BpmScript @Remote
String remoteCall(String parameter)
Kann eine Skript-Klasse bzw. Methode nicht gefunden werden oder erfüllt die Methode die Anforderungen für die Rest-API nicht, so wird mit dem HTTP-Status-Code "604: Wrong Parameter
" quittiert.
Der Aufruf dieser Skript-Methode verwendet dieselbe URL erwartet aber die Parameter als JSON-Payload als HTTP-POST Aufruf.
POST: /rest/api/scriptlibrary/v2/classes/{classname}/{methodname}
-- Skript-Methode --
@BpmScript @Remote
String remoteCall(String parameter)
{
return "Result: " + parameter
}
-- Request-Body --
{
"parameter" : "Parameter-Content"
}
-- HTTP-Status --
200: OK
-- Response-Body --
{
"result" : "Result: Parameter-Content"
}
Falls die Methode nicht gefunden werden kann oder die Voraussetzungen für den Aufruf über Rest-API nicht erfüllt, Parameter fehlen oder einen fehlerhaften Datentyp verwenden, erfolgt eine Ausgabe als Fehler.
{
"error": {
"reason": "Wrong parameter.",
"exceptionMessage": "ParameterException: Could not find ScriptLibrary class 'SCRemoteCall'!",
"exceptionHash": -1099067344,
"statusCode": 604
}
}
Der JSON-Payload ermöglicht nicht alle Datentypen der Parameter oder Rückgabewerte direkt anzugeben. Daher findet eine Konvertierung der Parameter statt. Die folgenden Beispiele zeigen verschiedene Methode-Signaturen mit den entsprechenden Request- und Response-JSON-Payload.
Datumswerte werden im ISO-8600-Format angegeben
Nachschlagewerte werden über den Primärschlüssel identifiziert
Bei Maps, Listen und Arrays vom Typ
Object
oderSerializable
findet keine Konvertierung z.B. von Strings im Format ISO-8600 nach Date statt
Multi-Parameter
@BpmScript @Remote
Date multiParameterMethod(String s, Integer i, Long l, Double d, BigDecimal bd, Boolean b, Date dt, ILookup lo)
{
return dt;
}
-- Request-Body --
{
"s" : "abc",
"i" : 123,
"l" : 123456789012345,
"d" : 12.34,
"bd": 12345678901234.12345678901234,
"dt" : "2022-04-11T12:00:00.000Z",
"b" : true
"lo" : "LookupPk"
}
-- Response-Body --
{
"result" : "2022-04-11T12:00:00+00:00"
}
Array-Parameter
@BpmScript @Remote
BigDecimal[] arrayParameterMethod(BigDecimal[] bd, Double[] d)
{
BigDecimal[] result = new BigDecimal[bd.length + d.length + 1];
for(int i = 0; i < bd.length; i++) {
result[i] = bd[i];
}
for(int i = 0; i < d.length; i++) {
result[bd.length + i] = new BigDecimal(d[i]);
}
result[result.length - 1] = new BigDecimal(5.5)
return result;
}
-- Request-Body --
{
"bd" : [11111111.11111111,222222222.22222222],
"d" : [33.33,44.44]
}
-- Response-Body --
{
"result" : [11111111.11111111,222222222.22222222,33.33,44.44,5.5]
}
List-Parameter
@BpmScript @Remote
List<String> listParameterMethod(List<String> ls, List<Integer> li)
{
for(Integer i : li)
{
ls.add("" + i)
}
return ls;
}
-- Request-Body --
{
"ls" : ["a","b","c"],
"i" : [1,2,3]
}
-- Response-Body --
{
"result" : ["a","b","c","1","2","3"]
}
Map-Parameter
@BpmScript @Remote
Map<String,Object> mapParameterMethod(Map<String,Object> map)
{
map.put("sOut", "xyz");
map.put("iOut", 456);
map.put("dtOut", new Date());
return map;
}
-- Request-Body --
{
"map" : {
"s" : "abc",
"i" : 123,
"l" : 123456789012345,
"d" : 12.34,
"bd": 12345678901234.12345678901234,
"dt" : "2023-01-01T12:00:00.000Z",
"b" : true
}
}
-- Response-Body --
{
"result" : {
"s" : "abc",
"sOut" : "xyz",
"i" : 123,
"iOut" : 456,
"l" : 123456789012345,
"d" : 12.34,
"bd": 12345678901234.12345678901234,
"dt" : "2023-01-01T12:00:00.000Z",
"dtOut" : "2023-10-31T12:00:00+00:00",
"b" : true
}
}
Testfall zu einer Methode hinterlegen
Es kann (und sollte) zu jeder Methode eine Test-Skript hinterlegt werden. Mit einer umfangreichen Test-Abdeckung von Skript-Klassen kann die Stabilität des Customizings und von Schnittstellen deutlich verbessert werden.
Zum Test-Skript kann über den rechten Navigationsbereich gewechselt werden. Bei einer übersteuerten Skript-Klasse steht das ursprüngliche Test-Skript schreibgeschützt zur Verfügung.
Über die Code-Vervollständigung kann dem Test-Skript eine Testmethode hinzugefügt werden. Die Test-Methode trägt denselben Namen wie die ausführbare Methode, enthält aber keine Parameter und keinen Rückgabewert.
// normales Skript
private String methodName(Object a, Map<String,ISearch> b)
// Test-Skript
void methodName()
In Test-Methoden kann auf alle Methoden der Skript-Klasse zugegriffen werden, welche dieselben Verwendungsstellen definiert haben. Test-Methoden können sich aber nicht gegenseitig aufrufen, da sie unabhängig voneinander ausgeführt werden. Für das Test-Skript steht die besondere Hilfsklasse Assert bereit, um Ergebnisse miteinander zu vergleichen. Die assert
-Anweisung prüft die angegebene Bool'sche Anweisung. Ist diese false,
schlägt der Test fehl.
Beispiel für einen Test-Skript
// Methode in SCUtils
String method(String[] sArray, Double... dArray)
{
String result = "";
for (s in sArray)
{
result += s;
}
for (d in dArray)
{
result += d;
}
return result;
}
// Test-Skript in SCUtils
String method(String[] sArray, Double... dArray)
{
String[] sArray = new String[2]; sArray[0] = "a"; sArray[1] = "b";
Double[] dArray = new Double[2]; dArray[0] = 1.0d; dArray[1] = 2.0d;
Assertions.assertEquals("ab", SCUtils.method(sArray), "string array only");
Assertions.assertEquals("ab1.02.0", SCUtils.method(sArray, dArray), "string and double array");
Assertions.assertEquals("ab1.0", SCUtils.method(sArray, new Double(1.0)), "string and new double array");
Assertions.assertEquals("ab2.03.04.0", SCUtils.method(sArray, 2.0d, 3.0d, 4.0d), "string array and double varargs");
Assertions.assertEquals("ab", SCUtils.method(sArray, new Double[0]), "string array and emtpy double array");
assert "ab10.1234" == SCUtils.method(sArray, 10.1234d)
}
Wurden Testdaten im Testfall angelegt, müssen diese nach dem Test wieder gelöscht werden. Hierzu sollte jeder neue Datensatz über die Methode ScriptTestUtils.addTestEntry
zur nachträglichen automatischen Löschung markiert werden.
Um in Tests echte REST oder Webservice Aufrufe zu vermeiden, stehen die Hilfsklassen RestMockUtils
und WebServiceMockUtils
zur Verfügung. Diese Hilfsklassen erlauben das Mocking von REST und Webservice Aufrufen, sodass anstelle der echten Schnittstellen vordefinierte Antworten zurückgegeben werden.
Das Vorgehen wird im Folgenden an einem Beispiel eines REST Aufrufs mittels der RestUtils
verdeutlicht.
Um in Tests echte REST oder Webservice Aufrufe zu vermeiden, stehen die Hilfsklassen RestMockUtils
und WebServiceMockUtils
zur Verfügung. Diese Hilfsklassen erlauben das Mocking von REST und Webservice Aufrufen, sodass anstelle der echten Schnittstellen vordefinierte Antworten zurückgegeben werden.
Das Vorgehen wird im Folgenden an einem Beispiel eines REST Aufrufs mittels der RestUtils
verdeutlicht.
Eine Methode mit einem REST Aufruf mithilfe RestUtils
// Methode in SCRestCalls
@BpmScript
Map<String, Object> makeRestCall()
{
WebTarget target = RestUtils.createTarget(WebServiceUtils.getServerBaseURL());
WebTarget resourceTarget = RestUtils.setPath(target, "rest/api/user/v1/me");
Builder request = RestUtils.createRequest(resourceTarget);
request = RestUtils.setBasicAuthentication(request, "<user>", "<pw>");
Response response = RestUtils.get(request);
return RestUtils.readResponse(response, Map.class);
}
Test zur Methode SCRestCalls.makeRestCall
// Test in SCRestCalls
private void mockRestCall()
{
ResponseBuilder response = RestMockUtils.createResponse(200);
response = RestMockUtils.setPayload(response, RestUtils.createJsonPayload(Map.of("shortCut", "MockUser")));
RestMockUtils.mockRequestOnce(RestUtils.GET, WebServiceUtils.getServerBaseURL() + "/rest/api/user/v1/me", response);
}
@Test
void makeRestCall()
{
RestMockUtils.setAllowOnlyMockedRequests(true);
mockRestCall();
assert SCRestCalls.makeRestCall().get("shortCut") == "MockUser";
}
Um sicherzustellen, dass der echte REST Service nicht im Test aufgerufen wird, wird RestMockUtils.setAllowOnlyMockedRequests(true)
aufgerufen. Sollte in einem Test nun ein REST Aufruf erfolgen, zu welchem kein Mock mittels RestMockUtils.mockRequestOnce
definiert worden ist, schlägt der Test fehl. Als nächstes wird die Antwort für den Mock mittels RestMockUtils.createResponse
und RestMockUtils.setPayload
erzeugt. Anschließend wird der Mock für den nächsten Aufruf des Services definiert, sodass beim nächsten Aufruf dieses Services in einem Test, die zuvor erzeugte Antwort zurückgegeben wird. Nachfolgende Aufrufe des gleichen Services würden wieder die echte Schnittstelle aufrufen, sofern nicht weitere Mocks mittels RestMockUtils.mockRequestOnce
hinterlegt worden sind. Sollten in einem Test Mocks definiert worden sein, die keine Anwendung finden, da keine entsprechenden REST Aufrufe getätigt werden, schlägt der Test fehl.
Ein ähnliches Vorgehen ist für Webservice Aufrufe mittels WebServiceUtils.callWebService
möglich:
Eine Methode mit einem Webservice Aufruf mithilfe WebServiceUtils
// SCWebServiceCalls
@BpmScript
Map<String, Object> makeWebServiceCall()
{
return WebServiceUtils.callWebService("MyWebService");
}
Test zur Methode SCWebServiceCalls.makeWebServiceCall
// Test in SCWebServiceCalls
private void mockWebServiceCall()
{
Map<String, Object> result = Map.of("key", "value");
WebServiceMockUtils.mockWebServiceOnce("MyWebService", result);
}
@Test
void makeWebServiceCall()
{
WebServiceMockUtils.allowOnlyMockedCalls(true);
mockWebServiceCall();
assert SCWebServiceCalls.makeWebServiceCall().get("key") == "value";
}
Um sicherzustellen, dass der echte Webservice nicht im Test aufgerufen wird, wird WebServiceMockUtils.setAllowOnlyMockedCalls(true)
aufgerufen. Sollte in einem Test nun ein Webservice Aufruf erfolgen, zu welchem kein Mock mittels WebServiceMockUtils.mockWebServiceOnce
definiert worden ist, schlägt der Test fehl. Als nächstes wird das Ergebnis für den Mock erzeugt. Anschließend wird der Mock für den nächsten Aufruf des Services definiert, sodass beim nächsten Aufruf dieses Services in einem Test, das zuvor erzeugte Ergebnis zurückgegeben wird. Nachfolgende Aufrufe des gleichen Services würden wieder die echte Schnittstelle aufrufen, sofern nicht weitere Mocks mittels WebServiceUtils.mockWebServiceOnce
hinterlegt worden sind. Sollten in einem Test Mocks definiert worden sein, die keine Anwendung finden, da der jeweilige Webservice nicht aufgerufen worden ist, schlägt der Test fehl.
Die Ausführung des Tests für die aktuelle Methode kann in der Methodenliste dediziert gestartet werden.
Wenn beim Ausführen des Tests eine Exception auftritt, dann können Sie ausführlichere Informationen zu dem Fehler im Stacktraces-Log finden. Der Stacktrace der Test-Ausführung ist im Standard deaktiviert und muss per Eingabeaufforderung (jboss-logging.bat → scriptTest true) aktiviert werden.
Methoden-Referenz aus der Testmethode aufrufen
Mit der Tastenkombination Strg+F12 auf dem Cursor auf dem Methodennamen kann man sich die Methoden-Definitionen und -Referenzen in der Skript-Klasse und im Testbereich anzeigen lassen.
Mit der Auswahl der Definition mit einem Mausklick springt man an die entsprechende Stelle im Code-Editor innerhalb derselben Skript-Klasse.
Testklasse hinterlegen
Soll ein Modul in der Gesamtheit getestet werden und umfasst dieser Test mehrere Skript-Klassen, ist es sinnvoll die Tests in eine Test-Klasse zu verlagern. Eine Test-Klasse unterschiedet sich von der Skript-Klasse besonders in der Definition der Methoden. In der Test-Klasse können keine öffentlichen Methoden angelegt werden, sondern nur private Hilfsmethoden.
Zudem besitzt die Test-Klasse vier spezielle Methoden, die die Vorbereitung und Nachbereitung von umfangreichen Tests deutlich erleichtern:
beforeClassMethod
:
Vor dem gesamten Test einer Klasse, aber auch beim Einzeltest einer Methode wird dieses Skript ausgeführt. Hier können Testdaten einmalig vorbereitet werden, die in vielen Testfällen ihre Anwendung finden. Diese Daten sollte mitScriptTestUtils.addTestEntry
markiert werden, damit sie am Schluss aller Tests wieder sauber aufgeräumt werden.afterClassMethod
:
nach dem gesamten Test einer Klasse, aber auch beim Einzeltest einer Methode wird dieses Skript ausgeführt. Hier können vorbereitete Aktion wieder entfernt und zurückgerollt werden, die durch den automatischen Löschmechanismus nicht erfasst wurden.beforeMethod
:
Dieses Skript wird direkt vor jeder Testmethode ausgeführt. Daten und Variablen, die hier erstellt wurden sind in den Testmethoden direkt verfügbar.afterMethod
:
Dieses Skript wird direkt nach jeder Testmethode ausgeführt. Daten und Einstellungen aus der Vorbereitung können hier wieder zurückgesetzt werden.
Skript-Klasse oder Methode testen
Der Test aller Test-Methoden einer Skript-Klasse kann aus der Toolbar heraus via Klasse testen-Button
gestartet werden. Die Tests jeder Methode werden einzeln durchlaufen und das Ergebnis mit Durchlaufzeit angezeigt.
Globale Testausführung starten
Zur Prüfung der Stabilität und Integrität aller Skript-Klassen kann der globale Test aus der Haupt-Toolbar der Skript-Bibliothek oder als Zeit-Aktion vom Typ "SCRIPT_TEST
" ausgeführt werden. Die Testausführung läuft im Hintergrund und legt die Ergebnisse in der Entität "Testläufe" - aufrufbar im Administrations-Menü - ab.
Testlauf:
Zeigt die Zeit der gesamten Ausführung, sowie die Anzahl der Testmethoden.Testfall:
Jede zu testende Skript-Klasse wird als Testfall zum Testlauf aufgeführt.Testschritt:
Jede fehlgeschlagene Test-Methode wird mit der Fehlermeldung unterhalb des Testfalls aufgeführt. Über die Anwendungsvariable "/de/cursor/jevi/common/scriptlibrary/testing/AbstractScriptTestRunner$!!$logSuccessfulTestCase
" kann die Protokollierung der positiven Testmethoden aktiviert werden. Dies erhöht aber die Datenbankeinträge deutlich.
Für Zeit-Aktionen vom Typ SCRIPT_TEST können in den Parametern die im System aktiven Module ausgewählt werden. Es ist somit möglich, die Skript-Tests für unterschiedliche Module durch verschiedene User (Feld "Ausführen als") ausführen zu lassen. Die Skripttests werden dabei mit dem Hauptmandanten des eingetragenen Users ausgeführt. Im Standard sind alle Module ausgewählt. Eine Auswahl eines Moduls, mehrerer oder aller Module ist möglich.
Skript-Klasse sichern und einspielen
Im C1-Modulbau ist es von Zeit zu Zeit notwendig, eine Skript-Klasse aus dem Sandbox-System in das Entwicklungssystem zu übertragen. Hierfür kann die Skript-Klasse inklusive der Dokumentation und der Abhängigkeiten aus dem Dashboard der Skript-Bibliothek exportiert werden. Dies kann auch genutzt werden, um einen Zwischenstand z.B. vor einem Update zu sichern.
Die Skript-Klasse kann ebenfalls über das Dashboard wieder importiert werden. Der Import prüft dabei die Konsistenz der Export-Datei. Das überschreiben einer bestehenden Klasse muss bestätigt werden und ist nur möglich, wenn die Klasse nicht zum Bearbeiten geöffnet ist.
Skript-Klasse veröffentlichen
Die Freigabe und Versionierung der Skript-Klassen verhält sich je nach vorliegendem System unterschiedlich.
Versionierung im Kundensystem ohne Customizing-Transport
Eine Skript-Klasse erhält mit jedem Speichern eine neue Version und ein aktuelles Veröffentlichungsdatum. Die Methoden-Signaturen der Skript-Klassen sind jederzeit änderbar.Freigabe im Kundensystem mit Customizing-Transport
Eine Skript-Klasse muss beim Speichern einem Customizing-Paket zugewiesen werden. Solange das Customizing-Paket noch offen ist und nicht exportiert wurde, können die Skript-Klasse und die Methoden-Signaturen im Entwicklungssystem angepasst werden. Mit dem Speichern wird die Version und das Release-Datum nicht verändert. Erst mit dem Export des Customizing-Pakets wird die Skript-Klasse veröffentlicht und die Versionsnummer und das Veröffentlichungsdatum gesetzt.
Der Customizing-Export zeigt alle Skript-Klassen als Warnung auf der Validierungsseite des Export-Wizards an. Nach dem Export der Skript-Klasse werden alle Methoden veröffentlicht. Dadurch können diese Methode weder gelöscht noch in der Signatur geändert werden.Veröffentlichung im Partnersystem
In einem C1-Modulsystem können neue Skript-Klassen den C1-Modulen zugewiesen werden. Mit jeder Veröffentlichung einer neuen Version des Customizing-Moduls wird auch die Versionsnummer der Skript-Klasse und das Veröffentlichungsdatum aktualisiert. Alle Skript-Methoden werden veröffentlicht. Dadurch können diese Methode weder gelöscht noch in der Signatur geändert werden.