====== Lab 5: Business Rule task w modelu BPMN ====== ==== Wdrażanie procesu w Activiti Explorerze ==== Na poprzednich zajęciach przygotowaliśmy i przetestowaliśmy następujący model: {{ :pl:dydaktyka:bim:lab5:eclipse3-model3-with-error.png |}} Będziemy chcieli wdrożyć go w Activiti Explorerze, a następnie uruchomić. \\ W tym celu potrzebujemy stworzyć odpowiednie pliki **jar** oraz **bar** przy użyciu ''ant''a. {{ :pl:dydaktyka:bim:lab5:eclipse2-designer-filemenu.png|}} Dla struktury katalogów, jak na rysunku obok, plik ''build.xml'' może wyglądać następująco: Po przygotowaniu odpowiednich klas Javy do obsługi niskopoziomowej logiki procesu, należy stworzyć odpowiednio pliki **jar** oraz **bar** (proszę pamiętać o unikalnych nazwach klas i plików!), a następnie: - Proszę skopiować plik jar do katalogu z bibliotekami dla Activiti Explorera: \\ ''/opt/activiti-5.10/apps/apache-tomcat-6.0.32/webapps/activiti-explorer/WEB-INF/lib''. - Po poprawnym przesłaniu pliku należy przeładować Activiti Explorer, aby przeczytał biblioteki. - Jeśli wszystko wykonało się poprawnie, można będzie w Activiti Explorerze wdrażać procesy \\ zawierające odniesienia do zdefiniowanych przez nas klas Javy. ==== Rozbudowa modelu o reguły Droolsa ==== Rozbudujemy teraz model z poprzednich zajęć o zadanie regułowe. Dodamy je jako dodatkowy element analizy merytorycznej oferty (//Check offer//). W zadaniu //Evaluate offer// określaliśmy odgórnie, czy kompetencje zespołu są wystarczające. W rzeczywistości same kompetencje zespołu bez informacji o trudności projektu, nie dają podstaw do rzetelnej ewaluacji. Proszę przed zadaniem //Evaluate offer// dodać zadanie regułowe //Estimate risk//, które oszacuje ryzyko projektu na podstawie dwóch czynników: * kompetencji zespołu -- zmiennej ''teamSkills'', zdefiniowanej na poprzednich zajęciach w zadaniu //Set offer factors//, oraz * spodziewanej trudności projektu -- zmiennej ''projectDifficulty'' (proszę dodać tę zmienną typu ''long'' do zadania //Set offer factors//). Dla oszacowania ryzyka zdefiniujemy prostą klasę modelu ryzyka (najlepiej dopisać inicjały do nazwy klasy, tak aby nie było konfliktów nazw): package pl.org.bpmn; import java.io.Serializable; public class Model implements Serializable { private static final long serialVersionUID = 1L; public Integer Kompetencje; public Integer Trudnosc; public String Ryzyko = "Brak danych"; public String toString() { return Ryzyko; } } oraz zdefiniujemy reguły Droolsa do szacowania ryzyka, oparte o powyższy model: package pl.org.bpmn import pl.org.bpmn.Model; rule "rule1" when m : Model(Kompetencje > 7, Trudnosc < 5) then m.Ryzyko = "Niskie"; end rule "rule2" when m : Model(Kompetencje < 5, Trudnosc > 5) then m.Ryzyko = "Wysokie"; end rule "rule3" when m : Model(Kompetencje > 7, Trudnosc > 5) then m.Ryzyko = "Srednie"; end W tym momencie możemy usunąć warunek dot. kompetencji zespołu z zadania //Evaluate offer// i zamiast niego wprowadzić warunek związany z wysokim ryzykiem projektu (np. ''projectRisk == "Wysokie"''). | Opcjonalnie: Aby przetestować opisane reguły bez Activiti możemy do naszego projektu dołożyć klasę [[pl:dydaktyka:bim:lab5?&#dodatkowe_pliki_do_testowania_modelu_regulowego|RuleRunner]] do uruchomienia reguł, a nasze reguły przetestować przy użyciu klasy [[pl:dydaktyka:bim:lab5?&#dodatkowe_pliki_do_testowania_modelu_regulowego|RuleRunnerTest]]. Do tego celu potrzebujemy w eclipsie dołączyć odpowiednie pliki biblioteczne do ''src/main/resources/lib'' -- {{:pl:dydaktyka:bim:lab5:lib.zip|}}.| Ustawienia dla zadania regułowego: * Input variables: ''${modelRyzyka}'' * Result variable: ''projectRisk'' Do pliku ''pom.xml'' proszę dołożyć odpowiednie dependencje jak na listingu w części [[pl:dydaktyka:bim:lab5?&#dodatkowe_pliki_do_testowania_modelu_regulowego|Dodatkowe pliki...]]. ==== Uruchomienie modelu z regułami w eclipsie ==== Aby móc przetestować nasz proces, musimy jeszcze w projekcie do pliku konfiguracyjnego dla silnika (activiti.cfg-mem-rules.xml) dodać dla **processEngineConfiguration**: Proszę do testu stworzonego na poprzednich zajęciach dopisać testowanie zadania regułowego. Fragment klasy testującej proces z regułami, może wyglądać następująco: public class RuleRunnerProcessTest { @Rule public ActivitiRule activitiRule = new ActivitiRule( "activiti.cfg-mem-rules.xml"); @SuppressWarnings("unchecked") @Test @Deployment(resources = { "diagrams/CheckOfferWithRules.bpmn20.xml", "rules/rules.drl" }) public void testProcessWithRules() { Map variableMap = new HashMap(); Model modelRyzyka = new Model(); modelRyzyka.Kompetencje = 2; modelRyzyka.Trudnosc = 8; variableMap.put("modelRyzyka", modelRyzyka); ProcessInstance processInstance = activitiRule.getRuntimeService() .startProcessInstanceByKey("processandrules", variableMap); assertNotNull(processInstance); Collection ruleOutputList = (Collection) activitiRule .getRuntimeService().getVariable(processInstance.getId(),"projectRisk"); assertNotNull(ruleOutputList); for(Object obj : ruleOutputList){ if(obj instanceof Model) { assertEquals("Wysokie", ((Model) obj).Ryzyko); System.out.println("ruleOutput: " + obj); } } } } Póki co przekazywaliśmy instancję ''Model''u bezpośrednio przez jUnit test. Docelowo chcielibyśmy, by tworzyła się ona w czasie działania procesu. Możemy zatem odpowiedni obiekt stworzyć w Javie, tak jak na poprzednich zajęciach lub przy okazji zadania pobierającego od użytkownika czynniki podlegające analizie merytorycznej -- jako [[http://www.activiti.org/userguide/index.html#taskListeners|task listener]]. Do stworzenia task listenera potrzeba przygotować klasę np. ''pl.org.bpmn.AddModel'', która implementuje interfejs ''TaskListener'' i w funkcji ''notify'' zawrzeć odpowiednią logikę działania tworzącą obiekt modelu ryzyka. public class AddModel implements TaskListener { public void notify(DelegateTask delegateTask) { // Custom logic goes here } } Następnie tak przygotowaną klasę możemy wybrać w zakładce **Listeners** w zadaniu **Set offer factors**: {{:pl:dydaktyka:bim:lab5:eclipse4-tasklistener-addmodel.png?950|}} Proszę zmodyfikować test, tak by nie przekazywał instancji ''modelRyzyka'' do procesu, tylko korzystał z instancji stworzonej w przygotowanym task listenerze. DO SPRAWOZDANIA: * Proszę w sprawozdaniu umieścić model oraz rozbudowany test. * Proszę umieścić screenshot z eclipsa z rezultatem testu, jak również kod klasy ''pl.org.bpmn.AddModel''. ==== Wdrożenie i uruchomienie procesu z regułami w Activiti Explorerze ==== Jeśli silnik actviti został skonfigurowany tak, by mógł uruchamiać zadania regułowe Droolsa, to będziemy mogli uruchomić proces w Activiti Explorerze. Podobnie jak poprzednio, do wdrożenia procesu i reguł potrzebne będą archiwa **jar** oraz **bar** (proszę pamiętać o **unikalnych nazwach klas i plików**!). W archiwum bar dołożymy oprócz diagramu również plik z regułami. Następnie należy: - Przekopiować plik jar do katalogu z bibliotekami dla Activiti Explorera. - Po poprawnym przesłaniu pliku należy przeładować silnik Activiti Explorera, aby przeczytał biblioteki. - Jeśli wszystko wykonało się poprawnie, w Activiti Explorerze powinno się dać wdrożyć procesy zawierające zadania regułowe. ==== CZĘŚĆ NIEOBOWIĄZKOWA ==== Dla zainteresowanych osób, poniżej przedstawiono możliwości integracji modeli stworzonych w trakcie zajęć oraz dalszej rozbudowy procesu. ==== Integracja procesu "Check offer" (z lab. 3 i 4) z procesem rozwijanym w lab. 1 i 2 ==== Jeśli założymy, że podproces **Check offer** jest osobną całością, która mogłaby być użyta w różnych procesach, sensownie będzie go wydzielić jako //Call Activity//. W tym celu zostawimy na diagramie procesu jedynie elementy z wnętrza podprocesu **Check offer**: {{ :activiti:tutorial4:eclipse4-subprocess.png |}} Upewniwszy się, że nasz podproces działa, zapewniamy mu unikalne ''id'', tak byśmy mogli się do niego odwoływać z innego procesu. {{ pl:dydaktyka:bim:lab5:eclipse4-callactivitiinprocess.png |}} Aby odwołać się z **Check offer** w postaci //Call Activity// (jak na powyższym rysunku) do odpowiedniego podprocesu, który zostanie wykonany, ustawiając następujące parametry w ''Main Config'' dla //Call Activity//: * ''Called element'' -- id zdefiniowanego podprocesu, * ''Input parameters'' -- ew. zmienne, które chcielibyśmy przekazać do podprocesu, * ''Output parameters'' -- zmienne, które chcielibyśmy przekazać z podprocesu do procesu macierzystego (w ''source'': nazwa zmiennej wyniku analizy merytorycznej w podprocesie; w ''target'': nazwa zmiennej wyniku analizy merytorycznej w procesie macierzystym). W przypadku, gdy wynik analizy merytorycznej jest negatywny, proces powinien zostać przerwany, gdyż dalsze przetwarzanie oferty jest zbyteczne. Na poprzednich zajęciach w tym celu sygnalizowaliśmy błąd przy użyciu zdarzenia końcowego wyrzucającego błąd w podprocesie. W naszym procesie macierzystym wypada zatem ten błąd przechwycić. Przy użyciu zdarzenia na krawędzi aktywności wychwytującego błąd, będziemy mogli odpowiednio obsłużyć przerwanie procesu. Aby zdarzenia błędów poprawnie się komunikowały istone jest, by nadać im jednakowy ''errorCode'' w konfiguracji (ważne: ''errorCode'' nie może być samą liczbą!). Proszę dodać do modelu odpowiednią obsługę błędu, po czym przetestować działanie modelu dla różnych scenariuszy. ==== Dodanie wyceny oferty ==== Przed akceptacją zarządu potrzebujemy dokonać jeszcze wyceny oferty. Możemy to zrobić na podstawie reguł stworzonych w narzędziu Drools do ustalania modyfikatora oszacowania wyceny oferty. Proszę dołożyć w tym celu odpowiednie zadania: ** 1)** Zadanie **Add factors** typu "user task" pobierające od użytkownika odpowiedzialnego za przetwarzanie oferty brakujące dane -- do reguł potrzebne nam będą wartości następujących zmiennych: * ''priceValuationClientPotential'' -- potencjał klienta (wymaga zapytania użytkownika), * ''priceValuationCooperationChance'' -- perspektywa współpracy z klientem (wymaga zapytania użytkownika), * ''priceValuationVisualAtractiveness'' -- atrakcyjność wizualna (wymaga zapytania użytkownika),\\ a także obliczające następujące zmienne: * ''priceValuationRealAttractiveness'' -- atrakcyjność merytoryczna, ma wartość zmiennej ''projectAttractiveness'' z podprocesu //Check Offer//, * ''priceValuationTeamAvailability'' -- dostępność zespołu (do wyznaczenia na podstawie ''requiredHours'' oraz ''availableHours'' z podprocesu //Check Offer//,), * ''priceValuationOfferValue'' -- wartość oferty (do obliczenia na podstawie wymaganej liczby osobogodzin, np. ''100*requiredHours''). Do obliczenia wybranych wartości przy zakończeniu zadania typu "user task" można użyć [[http://www.activiti.org/userguide/index.html#taskListeners|task listenera]] dla eventu ''complete'' albo dodatkowego zadania skryptowego. W przypadku implementacji poprzez task listener, należy stworzyć odpowiednią klasę np. ''pl.org.bpmn.PriceConditions'', która implementuje interfejs ''TaskListener'' i w funkcji ''notify'' obliczyć, po czym wyeksportować odpowiednie zmienne do procesu. {{ :pl:dydaktyka:bim:lab5:eclipse4-tasklistener.png?435|}} public class PriceConditions implements TaskListener { public void notify(DelegateTask delegateTask) { // Custom logic goes here } } ** 2)** Zadanie regułowe **Price offer**, które uruchomi odpowiednie reguły i ustali wielkość ''priceValuationPriceModification'', czyli wartość modyfikatora oszacowania wyceny oferty (ma ona zostać wyznaczona na podstawie zdefiniowanych reguł w pliku //drl// lub odpowiedniej tabeli decyzyjnej). Tak przygotowany proces wyglądałby wtedy następująco: {{:pl:dydaktyka:bim:lab5:eclipse4-full-process.png?900|}} ==== Inne możliwości dalszej rozbudowy procesu... ==== * Możliwość ręcznej poprawy wyceny oferty (w przypadku braku akceptacji zarządu). * Zmiana zadania **Check formal condition** na zadanie regułowe i podpięcie do niego odpowiednich reguł. * Dodanie zadań przygotowujących dokumenty dla klienta na potrzeby oferty. * Dodanie zadania informującego o przyczynie przerwania przetwarzania oferty \\ (np. nie spełniamy warunków formalnych, negatywna analiza merytoryczna, brak możliwości wyceny itp.). ==== Dodatkowe pliki do testowania modelu regułowego ==== package pl.org.bpmn; import java.io.File; import pl.org.bpmn.Model; import org.drools.KnowledgeBase; import org.drools.KnowledgeBaseFactory; import org.drools.builder.KnowledgeBuilder; import org.drools.builder.KnowledgeBuilderError; import org.drools.builder.KnowledgeBuilderErrors; import org.drools.builder.KnowledgeBuilderFactory; import org.drools.builder.ResourceType; import org.drools.io.ResourceFactory; import org.drools.runtime.StatelessKnowledgeSession; public class RuleRunner { public static String runRules(Model modelRyzyka) throws Exception { KnowledgeBase kbase = readKnowledgeBase(); StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); ksession.execute(modelRyzyka); System.out.println("Done firing.. --> result = " + modelRyzyka.Ryzyko); return modelRyzyka.Ryzyko; } private static KnowledgeBase readKnowledgeBase() throws Exception { KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource("rules" + File.separator + "rules.drl"), ResourceType.DRL); KnowledgeBuilderErrors errors = kbuilder.getErrors(); if (errors.size() > 0) { for (KnowledgeBuilderError error : errors) { System.err.println(error); } throw new IllegalArgumentException("Could not parse knowledge."); } KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages(kbuilder.getKnowledgePackages()); return kbase; } } package pl.org.bpmn; import static org.junit.Assert.assertEquals; import pl.org.bpmn.RuleRunner; import pl.org.bpmn.Model; import org.junit.Test; public class RuleRunnerTest { @Test public void testNiskiegoRyzyka(){ Model modelRyzyka = new Model(); modelRyzyka.Kompetencje = 8; modelRyzyka.Trudnosc = 3; String ryzyko = null; try { ryzyko = RuleRunner.runRules(modelRyzyka); } catch (Exception e) { e.printStackTrace(); } assertEquals("Niskie", ryzyko); } @Test public void testWysokiegoRyzyka(){ Model modelRyzyka = new Model(); modelRyzyka.Kompetencje = 2; modelRyzyka.Trudnosc = 7; String ryzyko = null; try { ryzyko = RuleRunner.runRules(modelRyzyka); } catch (Exception e) { e.printStackTrace(); } assertEquals("Wysokie", ryzyko); } @Test public void testSredniegoRyzyka(){ Model modelRyzyka = new Model(); modelRyzyka.Kompetencje = 8; modelRyzyka.Trudnosc = 8; String ryzyko = null; try { ryzyko = RuleRunner.runRules(modelRyzyka); } catch (Exception e) { e.printStackTrace(); } assertEquals("Srednie", ryzyko); } @Test public void testBrakuRegulOkreslajacychRyzyko(){ Model modelRyzyka = new Model(); modelRyzyka.Kompetencje = 5; modelRyzyka.Trudnosc = 5; String ryzyko = null; try { ryzyko = RuleRunner.runRules(modelRyzyka); } catch (Exception e) { e.printStackTrace(); } assertEquals("Brak danych", ryzyko); } } W pliku ''pom.xml'' powinny zostać dołożone następujące elementy: 5.3.0.Final org.drools drools-core ${drools-version} org.drools drools-compiler ${drools-version} org.drools drools-decisiontables ${drools-version} org.drools drools-transformer-xstream 5.0.1 jboss-public-repository-group JBoss Public Maven Repository Group http://repository.jboss.org/nexus/content/groups/public-jboss/