In einem unserer Kundenprojekte setzen wir die BPM-Lösung Bonita Open Solution (Version 5.6.1) ein. Die Software-Suite stellt einen BPMN-Editor wie auch eine BPE zur Verfügung, um die Prozesse zu designen und auszuführen. Leider ist die Dokumentation etwas dürftig, wenn es darum geht, einen Sub-Prozess mehrfach parallel auszuführen. Während manche Aufgaben (Activities) mit der kompletten Liste von Elementen arbeiten, muss für andere Tätigkeiten jeweils eine Aufgaben-Instanz mit einem Element der Liste gestartet werden. Man spricht auch von einer (parallelen) Mehrfachausführung.
Beispiele für das Szenario gibt es viele. Es ist z.B. denkbar, dass eine Loop-Activity Bewerbungen für einen Zeitraum entgegennimmt und in einer Liste sammelt. Diese werden dann zusammen in der HRM-Abteilung bearbeitet und später einzeln auf die Fachbereiche verteilt. In unserem rein technischen Beispiel warten wir auf die Eingabe von fünf Namen, die wir in einer einfachen Parallel-Ausführung und dann in einem parallelen Sub-Prozess einzeln anzeigen.
Unser Prozess beinhaltet eine Liste von Namen, die in der ersten Loop-Aufgabe abgefragt und über einen Connector der Namens-Liste hinzugefügt werden. Der zweite Schritt besteht aus einer einfachen Nutzer-Aufgabe, die für jeden Namen aufgerufen wird und diesen dann ausgibt. Hierzu wählt man die Aufgabe aus und selektiert unter „Advanced“ die Einstellung „is Multi-Instantiated“. Es erscheinen zwei Felder, die das Instantiieren und das Zusammenführen von den Aufgaben-Instanzen organisieren. Der „Instantiator“ ermittelt die Anzahl und Parameter für die Aufgaben-Instanzen, während der „Join Checker“ überprüft, ob alle Aufgaben abgeschlossen wurden, die für die Fortführung des Prozesses nötig sind. Demnach läuft der Instantiator auch vor allen Enter-Methoden, und wir erzeugen uns für unser Beispiel einen „Variable number von instances“-Connector. Wichtig ist, dass die lokalen Variablen in der Instantiator-Phase noch nicht zur Verfügung stehen.
Im Wizard-Verlauf müssen nun der Name einer lokalen Aufgaben-Variablen und die Liste der Werte angegeben werden. Die Variable können wir im Drop-Down über „Create data …“ wie gewohnt erzeugen. Wichtig ist, dass unter „Name“ kein GString angegeben wird, wie ihn Bonita fälschlich automatisch dort einfügt. Das heißt, der Name muss ohne Dollar und geschweifte Klammern in dem Feld stehen. Um die Werte nun zuzuweisen, muss man auf den Link „Set value as expression“ klicken, um in eine Ansicht zu kommen, die nur eine Liste erwartet. Hier geben wir unsere Liste als GString an.
Über den Join Checker stellt Bonita fest, ob alle Aufgaben-Instanzen beendet wurden, und fährt dann mit dem Prozess fort. Hier kann ein einfacher „Percentage of completed instances“-Connector ausgewählt werden, den wir mit „1.00“ für 100% versehen. Dies soll schon genügen: Wir teilen über den Instantiator Anzahl und Werte der BPE mit, und diese Werte werden bei der Aufgaben-Instanziierung den lokalen Variablen zugewiesen. Wenn alle Aufgaben abgeschlossen, sprich alle Namen angezeigt wurden, führen wir die Arbeit fort.
Bei einem mehrfach ausgeführten Sub-Prozess kann man das Vorgehen fast analog anwenden. Es muss zusätzlich die lokale Variable der Aufgabe auf eine Variable des Sub-Prozesses gemapped werden, mit der dieser dann arbeiten kann.
Wir hatten nun die Anforderung, dass wir an der Aktivität die eigentliche Liste der Daten erst ermitteln mussten, um den Sub-Prozess mit mehreren Daten aufzurufen. Man könnte dies mit einer Java-Klasse lösen, die die MultiInstantiator-Klasse der Bonita-API erweitert. Um eine schon bestehende Methode nutzen zu können, entschieden wir uns für die Groovy/Java-Lösung. In diesem Fall muss man ein Groovy/Java-Code-Snippet schreiben, das eine Liste von Aufgaben-Variablen zurückgibt. Konkret: List<Map<String, Object>>. Für jeden Listen-Eintrag wird eine Instanz gestartet, und die lokalen Variablen der Aufgabe werden durch Einträge aus der Map gefüllt. Für unser Namens-Beispiel sieht unser Java-Snippet so aus:
List<Map<String, Object>> initParams = new ArrayList<Map<String, Object>>(); // Liste aus Parametern. for (Object processVar : listOfNames) { Map<String, Object> entry = new HashMap<String, Object>(); entry.put("name", processVar); initParams.add(entry); } return initParams;
Für Groovy und Java-Code wählt man den „Groovy“-Connector unter Instantiator. Hier erwartet Bonita ein Code-Snippet, das dann ausgeführt wird. Sehr tückisch ist auch hier, dass Bonita gern GStrings bei der Verwendung des Expression-Editors einfügt. So wird der komplette Code nach dem Verlassen des Expression-Editors, der auch Code-Highlighting und Hilfe beim Import von Klassen bietet, in einen GString gewrapped. Dieser kann dann nicht ausgeführt werden – es müssen manuell alle GStrings entfernt werden. (Dies gilt auch für Variablen im Snippet, da der Inhalt ansonsten später als Groovy-Code ausgewertet wird). Sollte man dies mal übersehen, wird der Prozess an der Stelle abbrechen. Man erhält in der engine.log eine Fehlermeldung wie:
org.ow2.bonita.util.BonitaRuntimeException: Unable to find a method with name: setScript and parameters: [classjava.util.ArrayList=[{a=1, b=1}, {a=2, b=2}, {c=3, c=3}, {d=4, d=4}],] in connector: class org.bonitasoft.connectors.bonita.instantiators.GroovyInstantiator at org.ow2.bonita.definition.activity.AbstractActivity.instantiateMultiInstanceActivity(SourceFile:225) at org.ow2.bonita.definition.activity.AbstractActivity.execute(SourceFile:168)
Das ganze Beispiel kann als Bonita-Prozess herunter geladen werden.