To jest stara wersja strony!
Lab 4: Service task w modelu BPMN
Na początek w eclipsie proszę stworzyć nowy fragment modelu, tak by łatwo można było go testować.
Fragment ten będzie mógł zostać dołączony potem do naszego modelu z poprzednich zajęć.
Nasz model będzie się składał z podprocesu, którego zadaniem jest dokonać ewaluacji merytorycznej oferty.
Podprocesy są użyteczne, gdyż:
pozwalają na hierarchiczne modelowanie (w wielu narzędziach można wyświetlać podprocesy w postaci zwiniętej, nie ukazującej ich wewnętrznej logiki, dzięki czemu widzimy tylko model wysokiego poziomu),
tworzą nową przestrzeń dla zdarzeń, tzn. zdarzenia rzucane przy wykonywaniu podprocesu mogą być przechwytywane przez zdarzenia brzegowe podprocesu (a widoczność jest ograniczona do podprocesu).
Istotne: W Activiti podproces musi się zaczynać pojedynczym zdarzeniem startowym i kończyć co najmniej jednym zdarzeniem końcowym, mimo że specyfikacja BPMN 2.0 pozwala omijać zdarzenia początkowe i końcowe w podprocesach.
Service task "Get available hours" używający funkcji klasy w Javie
Na początek poznamy możliwości uruchomienia określonej funkcji w Javie (z dołączonej klasy).
Ogólnie service task wywołujący określoną funkcję w kodzie XML procesu wygląda następująco:
<serviceTask id="javaService" name="My Java Service Task" activiti:expression="#{object.function()}" />
W naszym przypadku będziemy chcieli zasymulować pobranie liczby dostępnych osobogodzin z zewnętrznego systemu.
W tym celu udostępniona została biblioteka z klasą TeamSchedule
naśladującą takie zachowanie: team-scheduler.jar.
Biblioteka ta zwraca dostępną liczbę osobogodzin (de facto funkcja getAvailableHours()
zwraca losowo liczby: 14, 47, 78, zaś funkcja getAvailableHours(int priority)
zwraca konkretną z liczb w zależności od znaku podanego argumentu).
Dodajemy bibliotekę do naszego projektu w miejscu /src/test/resources/lib/
.
Działanie naszego Team Schedulera można przetestować przy użyciu testu TeamScheduleTest
, który sprawdza 2 dostępne funkcje klasy TeamSchedule
:
- TeamScheduleTest.java
package pl.org.bpmn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import pl.org.bpmn.TeamSchedule;
import org.junit.Test;
public class TeamScheduleTest {
@Test
public void testAvailableHours(){
TeamSchedule schedule = new TeamSchedule();
for(int i=0; i<10; i++) {
int avHrs = schedule.getAvailableHours();
assertTrue(Arrays.asList(14, 47, 78).contains(avHrs));
}
}
@Test
public void testAvailableHoursWithPriority(){
TeamSchedule schedule = new TeamSchedule();
assertEquals(schedule.getAvailableHours(-10), 14);
assertEquals(schedule.getAvailableHours(0), 47);
assertEquals(schedule.getAvailableHours(10), 78);
}
}
Ponieważ chcemy mieć dostęp do obiektu klasy TeamSchedule
, potrzebujemy go stworzyć przy uruchamianiu silnika activiti. W tym celu w pliku konfiguracyjnym w projekcie (np. activiti.cfg-mem.xml
), dodajemy odpowiednią linijkę:
<bean id="schedule" class="pl.org.bpmn.TeamSchedule"/>
Teraz możemy już w eclipsowym Activiti Designerze dodać odpowiednie wywołanie:
Testowanie modelu
Aby uprościć testowanie modelu skorzystamy z klasy Activiti Rule
, która służy do inicjalizacji silnika procesowego dla JUnit testów.
Proszę dodać poniższy plik do projektu:
- CheckOfferTest.java
package pl.org.bpmn;
import java.util.List;
import org.activiti.engine.*;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.ActivitiRule;
import org.activiti.engine.test.Deployment;
import org.junit.*;
public class CheckOfferTest {
@Rule
public ActivitiRule activitiRule = new ActivitiRule("activiti.cfg-mem.xml");
@Test
@Deployment(resources={"diagrams/CheckOffer.bpmn20.xml"})
public void executeJavaExpression() {
RuntimeService runtimeService = activitiRule.getRuntimeService();
TaskService taskService = activitiRule.getTaskService();
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("process-key");
List<Task> taskList;
do {
taskList = taskService.createTaskQuery()
.processInstanceId(processInstance.getId()).list();
if(taskList.size()!=0) {
System.out.println("Completing task: " + taskList.get(0).getName());
taskService.complete(taskList.get(0).getId());
}
} while(taskList.size()!=0);
}
}
W pliku konfiguracyjnym w projekcie (activiti.cfg-mem.xml
) należy jeszcze dodać odpowiednią linijkę do połączenia z serwerem:
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
<property name="jdbcUrl" value="jdbc:h2:tcp://localhost/activiti" />
<property name="jdbcDriver" value="org.h2.Driver" />
<property name="jdbcUsername" value="sa" />
<property name="jdbcPassword" value="" />
</bean>
W powyższym kodzie plik konfiguracyjny uruchamia silnik procesowy w trybie StandaloneInMemProcessEngineConfiguration. Jest to tryb wygodny do testów jednostkowych, gdyż w tym trybie baza danych zostanie utworzona w trakcie uruchamiania silnika i następnie skasowana przy wyłączaniu.
Sprawdzenie zmiennych procesu
Proszę rozbudować powyższy test, tak aby wyświetlał zmienną zwracaną do procesu przez zadanie Get available hours i testował jej wartość, analogicznie jak to miało miejsce przy testowaniu klasy TeamSchedule
.
DO SPRAWOZDANIA:
Rozbudowa modelu o logikę niskopoziomową w Javie
Zanim wprowadzimy nową klasę Javy do ewaluacji merytorycznej (na kolejnych zajęciach częściowo zostanie to zastąpione regułami), potrzebujemy mieć wartości zmiennych, będących potencjalnymi czynniki analizy merytorycznej.
Dla klasy Set offer factors ustawiamy więc 3 zmienne (potencjalne czynniki analizy merytorycznej):
Project Attractiveness – atrakcyjność merytoryczna zamówienia.
Required Hours – przewidywana liczba osobogodzin do wykonania projektu opisanego w ofercie.
Team Skills – określa poziom kompetencji potencjalnego zespołu do wykonania projektu.
Proszę wprowadzić stosowne zmiany do jUnit testu, by wczytać 3 dowolne wartości do powyższych zmiennych.
Następnie będziemy chcieli przeprowadzić analizę merytoryczną oferty na podstawie danych od użytkownika (z zadania Set offer factors) oraz pobranych automatycznie (z zadania Get available hours).
Proszę zamienić zadanie Show result na service task (po najechaniu na element z menu kontekstowego change element type należy wybrać change to service task) i zmienić nazwę na Evaluate offer. Do tego zadania podepniemy klasę w Javie, która przetworzy dane z procesu.
Specyfikacja logiki zadania w Javie możliwa jest poprzez wybranie odpowiedniej klasy w polu activiti:class
w zadaniu typu Service task. Klasa taka musi implementować interfejs JavaDelegate
i nadpisywać funkcję public void execute(DelegateExecution execution)
, która jest wywoływana w momencie uruchamiania tasku.
Proszę stworzyć klasę Evaluation.java:
- Evaluation.java
package pl.org.bpmn;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
public class Evaluation implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
//TODO
}
}
W klasie tej przy pomocy metody execution.getVariable()
proszę odczytać wartości potencjalnych czynników analizy merytorycznej (pobranych wcześniej od użytkownika oraz możliwości produkcyjnych zespołu – dostępnych osobogodzin), a następnie zaimplementować poniżej opisaną logikę, która zwróci (przy pomocy execution.setVariable()
) odpowiedni wynik analizy merytorycznej.
Załóżmy, że wynik analizy merytorycznej jest negatywny, w każdym z następujących przypadków:
Niewystarczające kompetencje zespołu.
Przewidywane, wymagane zasoby przekraczają możliwości produkcyjne zespołu o 50%.
Przewidywane, wymagane zasoby przekraczają możliwości produkcyjne zespołu i oferta jest mało atrakcyjna.
Po zaimplementowaniu odpowiedniej logiki pozostaje dodać klasę do odpowiedniego zadania, które ma ona obsługiwać:
Na koniec należałoby wyświetlić wynik analizy w zadaniu Show checking result,
np. przy użyciu odpowiedniego komunikatu w polu description
.
Proszę uruchomić test i sprawdzić zachowanie stworzonego procesu powiązanego z klasą w Javie.
DO SPRAWOZDANIA:
Proszę w sprawozdaniu umieścić model, rozbudowany test.
Proszę umieścić screenshot z eclipsa z rezultatem testu.
Obsługa błędów/wyjątków
W przypadku, gdy wynik analizy merytorycznej jest negatywny, proces powinien zostać przerwany, gdyż dalsze przetwarzanie oferty jest zbyteczne. W takim wypadku można użyć zdarzenia końcowego, które sygnalizuje błąd, a następnie przechwycić ten błąd innym zdarzeniem np. na krawędzi podprocesu.
Aby zdarzenia te się poprawnie komunikowały istone jest, by nadać im jednakowy errorCode
w konfiguracji.
W ten sposób możemy przerwać wykonywanie podprocesu, po czym zakończyć cały proces (tak jak na rysunku obok).
Proszę dodać do modelu obsługę błędu, po czym przetestować jego działanie.
Uzupełnienie procesu o elementy konieczne do uruchomienia w Activiti Explorerze
Na kolejnych zajęciach przygotujemy paczkę do wdrożenia tak rozbudowanego projektu w Activiti Explorerze.
Jeśli jednak chcemy uruchomić proces w Activiti Explorerze musimy jeszcze uzupełnić zadania typu User task o użytkownika assignee
, tak byśmy mogli zatwierdzać odpowiednie zadania.