Eskalace v CzechIdM – jBPM timery

Docent Novák si zažádal o přístup do specializované knihovny, ve které by chtěl najít odpověď na otázku, na kterou během výzkumu narazil. Žádost už ale šest dní leží někde na stole a nevypadá to, že se jí v brzké době vůbec někdo bude zabývat. Pan Pokorný, který má knihovnu na starost, totiž odjel na dlouhou služební cestu a vrátí se až za několik týdnů.

Jak situaci řešit? Eskalací žádosti na jinou zodpovědnou osobu, která dokončí započatý schvalovací proces! V našem Identity Manageru CzechIdM eskalace právě zavádíme. Při implementaci jsme se setkali s timery v jBPM a právě o nich bude tento článek.

Co je jBPM?

Technologie jBPM je open-source nástroj od společnosti JBoss, který je napsán v jazyce Java a slouží k obsluze a návrhu firemních procesů. Každý proces je popsán pomocí stavů a přechodů mezi nimi.

Příklad procesu

 

Procesy lze definovat různě; my používáme jazyk JPDL, s jehož pomocí lze konkrétní proces snadno navrhnout ve struktuře xml. Každý uzel (stav) je definován vlastním xml tagem; pomocí jiných xml tagů lze definovat přechody z jednoho uzlu do druhého. My se v tomto článku zaměříme na speciální druh uzlu task-node.  V našem jednoduchém příkladu bychom uzlem typu task-node modelovali právě schvalovací požadavek. V xml by vypadal takto:

<task-node name="reviewDiscount">
<task name="approving">
<assignement pooled-actors="#{approvers}"/>
...

</task>

<transition name="approved" to="approved"/>
<transition name="denied" to="denied"/>
</task-node>

Pro každý task-node je klíčový především tag task. V něm je definován samotný schvalovací požadavek, který je směrován na uživatele uvedené v atributu pooled-actors. Podle toho, zda byl požadavek schválen či zamítnut, pokračuje proces příslušným přechodem „approved“ nebo „denied“.

Timery uvnitř procesů

Řekněme, že si přejeme, aby na schválení nebo zamítnutí schvalovacího požadavku měl každý schvalovatel sedm dní. Pokud se do sedmi dnů schvalovatel nevyjádří, chceme požadavek automaticky přeposlat na někoho jiného. Od vstupu do uzlu task-node tedy musíme stopovat čas a po uplynutí sedmi dnů spustit akci, která odebere schvalovací požadavek původnímu schvalovateli a přesune jej ke schvalovateli novému. Řešením tohoto problému jsou timery.

Timer si v jBPM můžeme představit jako záznam v databázi, který je pravidelně kontrolován. Uvádí se v něm, kdy (nebo za jak dlouho) má dojít k akci, zda se akce má opakovat a co má ona akce obnášet. V JPDL bychom náš timer definovali takto:

<task-node name="reviewDiscount" end-tasks="true">
<task name="approving">
<timer name="escalation" duedate="7 days" transition="escalate"/>

...
</task>
<transition name="escalate" to="reviewDiscount">
<script>
<expression>
//Escalation code
//New approvers
</expression>
</script>
</transition>
</task-node>

Timer je definován v tagu timer. Atribut name uvádí název timeru, podle kterého bychom mohli spuštěný timer dohledat v databázi, v duedate specifikujeme, kdy má dojít ke spuštění. V našem případě bude timer spuštěn přesně za týden. Bylo by ovšem možné také stanovit pevný časový okamžik nezávislý na tom, kdy proces do uzlu vstoupil. Konečně atribut transition říká, kterým přechodem má proces pokračovat, jakmile je timer spuštěn.

Do každého přechodu mezi dvěma stavy můžeme vložit kód, který se při přechodu vykoná. V našem případě stačí pozměnit seznam schvalovatelů a vrátit se do téhož uzlu, který jsme opustili. Tím se úkol přesune k jiné množině schvalovatelů a spustí se nový timer.

Nečekaný problém v Seam

K tomu, aby naše timery fungovaly tak, jak mají, potřebujeme spustit službu, která za nás kontroluje spuštěné timery a hlídá, kdy se má který aktivovat. V jBPM k tomu slouží Job Executor.

Při implementaci nás ale čekalo nepříjemné zjištění. V našem návrhu eskalací bylo totiž při každém spuštění timeru nutné přistoupit k běžícím Seam komponentám. Timery v jBPM však běží od Seamu odděleně, ke kontextu Seamu tedy nemají přístup. Proto jsme museli implementovat svůj vlastní Job Executor coby komponentu Seamu:

@Name("jbpmJobExecutor")
@Scope(ScopeType.APPLICATION)
@AutoCreate
@Install
@Startup
public class JbpmJobExecutor extends org.jbpm.job.executor.JobExecutor {
...
@Create
public void afterCreate() {
...
//start ordinary JobExecutor
...
}

@Destroy
public void beforeDestroy() {
...
//stop ordinary JobExecutor
...
}

}

Závěr

V článku jsme se podívali na nový rys CzechIdM, eskalace, a věnovali jsme se podrobněji jeho implementaci pomocí jBPM timerů. Kdybyste měli ohledně článku nebo našeho Identity Manageru CzechIdM jakýkoli dotaz, neváhejte se na mě obrátit na adrese vojtech.matocha@bcvsolutions.eu!