Implementacja procesu


Implementacja menadżera procesu

W celu utworzenia menadżera procesu, należy stworzyć klasę Groovy lub JAVA dziedziczącą po abstrakcyjnej klasie palio.modules.hetman.ProcessManager. Menadżer musi zapewnić własną implementację dwóch metod:

/**
 * Pobiera aktualny stan zgłoszenia/dokumentu
 * @param instanceId identyfikator zgłoszenia/dokumentu
 * @throws PalioException
 */
public abstract State getInstanceState(Object instanceId) throws PalioException;
 
/**
 * Zmienia stan zgłoszenia/dokumentu. Metoda ta jest wywoływana po pomyślnym wykonaniu obiektu przejścia/akcji.
 * @param instanceId identyfikator zgłoszenia/dokumentu
 * @param state nowy stan
 * @throws PalioException
 */
public abstract void changeInstanceState(Object instanceId, State state) throws PalioException;

 

Przykładowa implementacja może wyglądać w sposób następujący:

package hetman;
 
import palio.*;
import palio.modules.*;
import palio.modules.hetman.*;
 
public class SampleManager extends ProcessManager {
 
	private static final String GET_INSTANCE_STATE_SQL = "select NAME from H_STATES where ID=(select STATE_ID from H_ORDERS where ID=?)"
	private static final String CHANGE_INSTANCE_STATE_SQL = "update H_ORDERS set STATE_ID=(select ID from H_STATES where NAME=?) where ID=?"
 
	private static Sql sql = Groovy.module("sql")
 
	public SampleManager(Process process) {
		super(process)
	}
 
	public State getInstanceState(Object instanceId) throws PalioException {
		Groovy.module("log").debug(process.getId(), "getInstanceState: ${instanceId}")
		Object[] rslt = sql.readLine(GET_INSTANCE_STATE_SQL, [instanceId].toArray())
		return process.getState(rslt[0])
	}
 
	public void changeInstanceState(Object instanceId, State state) throws PalioException {
		Groovy.module("log").debug(process.getName(), "changeInstanceState: ${instanceId} -> ${state.getName()}")
		sql.write(CHANGE_INSTANCE_STATE_SQL, [state.getName(), instanceId].toArray())
	}
 
}

 

W ramach implementacji menadżera procesu istnieje możliwość zaimplementowania wspólnego warunku dla wszystkich przejść w procesie. Aby zaimplementować wspólny warunek należy w menadżerze procesu nadpisać poniższą metodę. Metoda ta jest wywoływana zawsze przed wykonaniem obiektu warunku.

/**
 * Metoda wykonywana jest zawsze przed sprawdzeniem warunków zaimplementowanych w obiekcie warunku.
 * Nadpisując tą metodę, we własnej implementacji menadżera procesu, możemy zaimplementować wspólne warunki dla wszystkich przejść. 
 * @param instanceId identyfikator zgłoszenia/dokumentu
 * @throws PalioException
 */
public Result checkCommonCondition(Object instanceId) throws PalioException

Tworzenie strony procesu

Każdy proces musi mieć własną stronę procesu. Strona ta pełni funkcję dispatchera. Za pomocą tej strony realizowany jest cały proces (zarówno logika biznesowa, jak i warstwa prezentacji). Wszystkie formularze procesu powinny wysyłać dane pod url tej strony. Główny obiekt strony procesu (obiekt stanowiący treść strony) powinien zawierać w sobie wywołanie metody executeProcess z modułu Hetman.

Przykładowa treść głównego obiektu strony procesu:

$try({
$hetman.executeProcess("hetman.proc", $toLong((String)$_RowID))
}, {
$error.getMessage()
})

lub w Groovy (bardziej skomplikowanie, ale mamy pełną kontrolę nad przechwyconym wyjątkiem)

import palio.*
import palio.modules.*

try {
Groovy.module("hetman").executeProcess("hetman.proc", Long.valueOf(Palio.getParam("_RowID")))
} catch (Exception ex) {
Groovy.object("hetman.error", [null, ex].toArray())
}

Tworzenie obiektu widoku

Obiekt widoku odpowiedzialny jest za prezentację stanu zgłoszenia, oraz za wyświetlanie formularzy związanych z możliwymi do wykonania przejściami. W celu wyświetlenie formularzy należy wywołać metodę displayTransitions z modułu Hetman.

Obiekt widoku wywoływany jest z następującymi argumentami:

Domyślnym kodem obiektu widoku jest <prefix>.view. Jeżeli chcemy użyć niestandardowego kodu obiektu widoku należy podać kod obiektu w definicji procesu (atrybut stateView).

Przykładowa treść obiektu widoku (hetman.view):

$#params(Long instanceId, String infoMessage, String errorMessage)
 
<html>
<head>
$html.contentMeta()
 
<style type="text/css">
	.HMessage {
		width: 400px;
		border:1px solid blue;
		color: blue;
		font-weight: bold;
		text-align: center;
		margin: 5px;
	}
 
	.HErrorMessage {
		width: 400px;
		border:1px solid red;
		color: red;
		font-weight: bold;
		text-align: center;
		margin: 5px;
	}
</style>
 
</head>
<body>
 
informacje o zgłoszeniu (nagłówek)<br>
 
$ifNotNull($@infoMessage, {
	<div class="HMessage">$@infoMessage</div>
})
 
$ifNotNull($@errorMessage, {
	<div class="HErrorMessage">$@errorMessage</div>
})
 
$hetman.displayProcess()
 
formularz zgloszenia<br>
 
historia zgłoszenia<br>
 
</body>
</html>

Tworzenie obiektu obsługi błędu

Obiekt błędu odpowiedzialny jest za obsługę błędów jakie mogą wystąpić podczas wykonywania procesu. 
Domyślnym kodem obiektu błędu jest <prefix>.error. Jeżeli chcemy użyć niestandardowego kodu obiektu błędu należy podać kod obiektu w definicji procesu (atrybut errorView).

Obiekt błędu wywoływany jest z następującymi argumentami:

Przykładowa treść obiektu obsługi błędu (hetman.error):

$#params(Long instanceId, java.lang.Throwable ex)
 
<html>
<head>
$html.contentMeta()
</head>
<body>
 
informacje o zgłoszeniu (nagłówek)<br>
 
$if($palio.instanceOf($@ex, "palio.modules.hetman.exceptions.ProcessException"), {
 
	Błąd procesu: $@ex.getMessage()<br>
 
	$if($palio.instanceOf($@ex, "palio.modules.hetman.exceptions.NoSuchTransitionException"), {
 
		$=(@noSuchTransEx, (palio.modules.hetman.exceptions.NoSuchTransitionException) $@ex)
		$=(@srcState, $@noSuchTransEx.getSource())
		$=(@destState, $@noSuchTransEx.getDestination())
 
		Stan źródłowy: $@srcState.getId()<br>
		Stan docelowy: $@destState.getId()<br>
	})
 
	$if($palio.instanceOf($@ex, "palio.modules.hetman.exceptions.InsufficientPrivilegesException"), {
		Niewystarczające uprawnienia
	})
 
}, {
	$@ex.getMessage()
})
 
</body>
</html>


Do obsługi wyjątków zalecane jest stosowanie języka Groovy. Przykład poniżej:

import palio.*
import palio.modules.hetman.*
 
StringBuilder buff = new StringBuilder()
 
Long instanceId = args[0]
Throwable ex  =  args[1]
 
switch (ex) {
case palio.modules.hetman.exceptions.ProcessException:
	buff.append("Wystąpił błąd procesu<br>")
 
	switch (ex) {
	case palio.modules.hetman.exceptions.NoSuchTransitionException:
		buff.append("Brak zdefiniowanego przejścia ze stanu ${ex.getSource().getId()} ")
		buff.append("do stanu ${ex.getDestination().getId()}")	
		break
	case palio.modules.hetman.exceptions.InsufficientPrivilegesException:
		buff.append("Niewystarczające uprawnienia<br>")
		Transition transition = ex.getTransition()
		if (ex.getUserId()) {
			String username = Groovy.module("user").userName(ex.getUserId())
			buff.append("Użytkownik: ${username}\n")	
		}
		if (transition != null) {
			buff.append("Przejście ze stanu ${transition.getSource().getId()} ")
			buff.append("do stanu ${transition.getDestination().getId()}")
		}
		break
	}
 
	break	
default:
	buff.append(ex.getMessage())	
}
 
Groovy.object("hetman.displayError", [instanceId, buff.toString()].toArray())

 

Tworzenie obiektu przerwy

Obiekt ten wywoływany jest w momencie gdy zostanie wykonane przejście, które w definicji ma ustawiony atrybut "breakAfterTransition" na wartość "true". Głównym zadaniem tego obiektu jest prezentacja informacji o wykonanym przejściu oraz o przerwaniu procesu. Domyślnym kodem obiektu przerwy jest <prefix>.break. Jeżeli chcemy użyć niestandardowego kodu obiektu przerwy należy podać kod obiektu w definicji procesu (atrybut breakView).

Obiekt przerwy wywoływany jest z następującymi argumentami:

Przykładowa treść obiektu przerwy (hetman.break):

 

$#params(Long instanceId, String message)
 
<html>
<head>
$html.contentMeta()
</head>
<body>
 
informacje o zgłoszeniu (nagłówek)<br>
 
<h1>przerwa w procesie: $@message</h1>
 
</body>
</html>

Tworzenie obiektu formularza

Obiekt ten odpowiedzialny jest jest za wyświetlenie formularza, do którego można wpisać dane jakie zostaną wykorzystane podczas wykonywania przejścia do danego stanu. Formularz musi być wysyłany do strony procesu. W treści formularza powinno znaleźć się wywołanie jednej z metod modułu Hetman:

public void displayTransitionButton(String destinationStateId) throws PalioException, HetmanException, ProcessException
public void displayTransitionHiddenField(String destinationStateId) throws PalioException, HetmanException

Metody te generują kod html z dodatkowymi elementami formularza, dzięki którym moduł Hetman jest w stanie wykonać detekcję rozpoczęcia wykonywania się przejścia do określonego stanu.

Domyślnym kodem obiektu formularza przejścia jest <prefix>.<destination form id>.form. Jeżeli chcemy użyć niestandardowego kodu obiektu formularza należy podać kod obiektu w definicji danego przejścia (atrybut form).

Obiekt formularza wywoływany jest z następującymi argumentami:

Przykładowy obiekt formularza (hetman.VERIFICATION_OK.form):

$#params(Long instanceId)

$html.createForm("VERIFICATION_OK", "VERIFICATION_OK", $pageURL($currentPage()), "form-field", "form-button", {
	<input type="hidden" name="_RowID" value="$@instanceId">
	
	$html.textField("field_name1", (String)$field_name1)
	$html.textField("field_name2", (String)$field_name2)
	$html.textField("field_name3", (String)$field_name3)

	$hetman.displayTransitionButton("VERIFICATION_OK")
}, {})

Dla obiektu formularza możliwe jest utworzenie obiektu inicjalizacji formularza. Obiekt taki przydatny jest np. do wczytania danych słownikowych wyświetlanych w formularzu. Kod obiektu inicjalizacji danego formularza:

<form object code>$init

Każdy z formularzy może mieć swój obiekt akcji "lokalnej". Akcje lokalne są to operacje, które nie mają nic wspólnego ze zmianą stanu. Wykonują się zawsze, o ile są zdefiniowane obiekty takich akcji, przed inicjalizacją oraz wyświetleniem formularza. Domyślnym kodem obiektu akcji "lokalnej" formularza jest:

<form object code>$action

Obiekt akcji "lokalnej" wywoływany jest z następującymi argumentami:

Tworzenie obiektu warunku

Zadaniem obiektu warunku (danego stanu) jest sprawdzenie czy dane zgłoszenie/dokument może ze stanu, w którym obecnie się znajduje, przejść do stanu do którego przynależy dany obiekt warunku. Sprawdzenie warunku (wywołanie obiektu warunku) wykonywane jest w momencie próby wyświetlenia formularza, oraz w momencie wykonywania przejścia/akcji). Obiekt warunku musi zwracać instancję klasy palio.modules.hetman.Result. W przypadku gdy sprawdzenie warunku nie zakończy się sukcesem nie zostanie wyświetlony formularz przejścia (zamiast niego pojawi się informacja o przyczynie niespełnienia warunku). Nie zostanie wykonana także akcja związana przejściem.

Domyślnym kodem obiektu warunku wykonania przejścia jest <prefix>.<destination state id>.condition. Jeżeli chcemy użyć niestandardowego kodu obiektu warunku należy podać kod obiektu w definicji danego przejścia (atrybut condition).

Obiekt warunku wywoływany jest z następującymi argumentami:

Przykładowy obiekt warunku (hetman.VERIFICATION_OK.condition):

import palio.modules.hetman.*
 
// sprawdzenie warunków
 
return new Result(true, "Wszystko OK");

Tworzenie obiektu akcji

Obiekt akcji/przejścia wywoływany jest w momencie wysłania formularza przejścia (jeżeli zostały spełnione warunki przejścia). Obiekt ten wykonywany jest także w momencie wywołania metody executeTransition z modułu Hetman. Obiekt ten odpowiedzialny jest za implementację logiki biznesowej związanej z przejściem z obecnego stanu do stanu do którego przynależy dany obiekt przejścia. Obiekt warunku musi zwracać instancję klasy palio.modules.hetman.Result. W przypadku gdy wykonanie obiektu akcji zakończy się niepowodzeniem, stan zgłoszenia nie ulega zmianie. W przypadku gdy w obiekcie przejścia zostanie wyrzucony wyjątek typu palio.modules.hetman.exceptions.BussinessException przejście nie zostanie wykonane, nie zostanie wyświetlony obiekt błędu. Zamiast niego zostanie wyświetlony obiekt prezentacji stanu z ustawionym parametrem dot. informacji o błędzie biznesowym (errorMessage).

Domyślnym kodem obiektu akcji jest <prefix>.<destination state id>.action. Jeżeli chcemy użyć niestandardowego kodu obiektu akcji należy podać kod obiektu w definicji danego przejścia (atrybut action).

Obiekt akcji wywoływany jest z następującymi argumentami:

Przykładowy obiekt akcji (hetman.VERIFICATION_OK.action):

import palio.modules.hetman.*
import palio.modules.hetman.exceptions.*
 
// logika biznesowa akcji
 
// w przypadku gdy wystąpi błąd biznesowy, należy wyrzucić wyjątek BussinessException
throw new BussinessException("Nieprawidłowa wartość ...")
 
return new Result(true);

Dla każdego ze stanów źródłowych istnieje możliwość stworzenia dodatkowych obiektów akcji. Obiekty te są wykonywane po sprawdzeniu warunku, ale przed wykonaniem głównego obiektu akcji. Aby dodatkowe obiekty przejścia zostały automatycznie wykonane przez silnik Hetmana, należy nadawać im kody wg poniższej konwencji:

<prefix>.<destination state id>.<source state id>.action

Dodatkowy obiekt akcji wywoływany jest z następującymi argumentami:

Dostęp do predefiniowanych parametrów stanów i przejść

Dla każdego stanu i przejścia możliwe jest zdefiniowane dowolnej liczby parametrów. Poniższe przykłady obrazują sposób w jakim można odwołać się do predefiniowanych parametrów.

Przykład w jPALIO. Pobranie wartości parametrów dla wskazanego stanu oraz dla wskazanego przejścia.

$hetman.getStateParam("hetman.proc", "NEW_ORDER", "test")
$hetman.getTransitionParam("hetman.proc", "NEW_ORDER", "VERIFICATION_OK", "test")


Przykład w Groovy/JAVA. Pobranie wartości parametrów dla bieżacego stanu oraz dla bieżacego przejścia.

ProcessExecutionContext context = Groovy.module("hetman").getProcessExecutionContext()
context.getCurrentState().getParam("test")
context.getCurrentTransition().getParam("test")

Wsparcie transakcyjności

Hetman ma wbudowaną obsługę transakcyjności. Wszystkie operacje związane wykonaniem przejścia (wykonanie obiektu akcji, zmiana stanu) są objęte transakcją. Transakcja tworzona jest na konektorach umieszczonych w definicji procesu (tag "connectors"). Istnieje możliwość dynamicznego dodania konektora do istniejącej transakcji poprzez wywołanie, np. w obiekcie akcji, metody:

$sql.transactionAdd("connector") $// dodanie jednego konektora do istniejącej transakcji
$sql.transactionAdd(["connector1", "connector2"]) $// dodanie grupy konektorów

W przypadku gdy przy próbie wykonania przejścia wystąpi jakikolwiek wyjątek lub obiekt akcji zwróci new Result(false), następuje rollback. W przeciwnym wypadku wykonywany jest commit.

Grupowanie przejść

Hetman umożliwia grupowanie przejść w ramach przejść jakie są możliwe w danym stanie. Grupowanie może być przydatne wówczas gdy chcemy obsługę tych przejść umieścić w jednym, wspólnym formularzu. Dla grupy przejść można zdefiniować kod obiektu formularza (atrybut form). Każda grupa musi mieć nazwę (atrybut name).

Metody modułu Hetman wspierające grupowanie przejść:

List getTransitionsForDropList(String groupName, Boolean checkPossibility)

Implementacja podprocesu

Implementacja menadżera procesu obsługującego podproces

Aby Hetman prawidłowo obsługiwał podprocesy, należy nadpisać w menadżerze procesu poniższą metodę:

/**
* Pobiera aktualny stan zgłoszenia/dokumentu w podprocesie.
* Ze względu na kompatybilność wsteczną metoda nie jest abstrakcyjna.
* Aby korzystać z podprocesów należy w menadżerze procesu nadpisać tę metodę.
* @param instanceId identyfikator zgłoszenia/dokumentu
* @param subprocess podproces
* @throws PalioException, ProcessException
* @return jeżeli dla danego zgłoszenia/dokumentu nie rozpoczęto podprocesu (metoda powinna zwrócić null)
*/
public State getInstanceState(Object instanceId, Subprocess subprocess) throws PalioException

 

Wykonywanie/wyświetlanie podprocesu

Wartość globalnego parametru HSubprocess decyduje o tym jaki podproces ma być wykonywany/wyświetlany. Hetman domyślnie pracuje na głównym podprocesie (MAIN). W przypadku gdy jest wykonywane przejście sprawdzany jest podproces do jakiego należy stan docelowy. Jeżeli podproces został już rozpoczęty pobierany jest ostatni stan w podprocesie i przejście wykonywane jest z tego stanu (musi być w nim zdefiniowane przejście do podanego stanu docelowego). W przypadku gdy podproces nie został rozpoczęty pobierany jest ostatni stan podprocesu głównego. W stanie tym powinno być zdefiniowane przejście do stanu docelowego (powinien on być pierwszym stanem w podprocesie).

Zobacz także:


[Definicja procesu] Definicja podprocesu