Module:Hetman4
From Jpaliowiki EN
Introduction
Hetman is a jPALIO module that supports creation and implementation of all types of sales processes, document flow (the so called workflow).
In order to create a new process using Hetman, follow these steps:
- Define a process - a jPalio object with process definition in the xml format
- Create the process manager - Groovy or JAVA class that inherits after the palio.modules.hetman.ProcessManager class
- Create the process page - the whole process is handled by a single page that functions as a dispatcher
- Create the process view object - the jPalio object
- Create the error handling object - the jPalio object
- Create the form, condition and action objects for each of the defined process states - jPalio objects
The Hetman mechanism was designed with CoC (Convention Over Configuration) principle in mind.
Description
Create and define a process
To create a new process, you will have to create a new jPalio object. The object code is also the process name at the same time. The object's content must contain a properly formatted xml document that describes the process definition.
An example of the process definition:
<?xml version="1.0" encoding="UTF-8"?> <process name="hetman.proc" prefix="hetman" manager="hetman.SampleManager" stateView="hetman.view" errorView="hetman.error" breakView="hetman.break"> <connectors> <connector name="connector1"/> <connector name="connector2"/> <connector ..... </connectors> <states> <!-- full definition of state --> <state id="STATE_A" view="hetman.proc.STATE_A.view > <params> <param name="name1" value="1111" type="Long"/> <param name="name2" value="1980-08-28" type="Date" format="yyyy-MM-dd"/> <param name="name3" value="value3"/> </params> <transitions> <!-- full definition of transition --> <transition destination="STATE_B" label="State B" breakAfterTransition="true" redirectAfterTransitionURL="http://..." action="hetman.proc.STATE_B.action" condition="hetman.proc.STATE_B.condition" form="hetman.proc.STATE_B.form" > <params> <param name="name1" value="value1"/> <param name="name2" value="value2"/> <param ..... </params> <privs> <priv id="8" /> <priv name="ADMIN"/> <priv ..... </privs> </transition> <group name="GROUP_1" form="hetman.proc.groups.GROUP_1.form"> <transition destination="STATE_C" label="State C"/> <transition destination="STATE_D" label="State D"/> </group> </transitions> </state> <state id="STATE_B"> <transitions> <transition destination="STATE_E"/> </transitions> </state> <state id="STATE_C"/> <state id="STATE_D"/> <!-- subprocess definition --> <subprocess name="SUBPROCESS_1"> <state id="STATE_E" first="true"> <transitions> <transition destination="STATE_F" /> <transitions/> </state> <state id="STATE_F"> <transitions> <transition destination="STATE_G" /> <transition destination="STATE_H" /> <transitions/> </state> <state id="STATE_G" last="true" success="true"/> <state id="STATE_H" last="true" success="false"/> </subprocess> </states> </process>
Description of the process definition format:
The /process element - main configuration parameters of the process
- process/@name *
- Process name. The name must be compatible with the jPalio object's code that contains the process definition.
- process/@prefix *
- Prefix of all codes of default jPalio objects used in the process implementation.
- process/@manager *
- Full name of the class that functions as the process manager .
- process/@stateView
- Code of the object responsible for displaying the view of a particular state.
- Default value:
<prefix>.view
- process/@errorView
- Code of the object responsible for error handling.
- Default value:
<prefix>.error
- process/@breakView
- Code of the object responsible for displaying a break in the process.
- Default value:
<prefix>.break
The /process/states/state element - the definition of state
- process/states/state/@id *
- State indicator in the Hetman module.
- process/states/state/@view *
- Object code that supplements the standard state presentation.
- Default value:
<prefix>.<state identifier>.view
The /process/states/state/params/param element - the definition of state parameter
- process/states/state/params/param/@name *
- Parameter name.
- process/states/state/params/param/@value *
- Parameter value.
- process/states/state/params/param/@type *
- Parameter type. Supported types: Long, Date, String.
- process/states/state/params/param/@format *
- Used when the parameter of the Date type.
The /process/states/state/transitions/transition element - the definition of transition
- process/states/state/transitions/transition/@destination *
- Target state identifier. There must be a state with this identifier in the process definition.
- process/states/state/transitions/transition/@label
- A label displayed e.g. on buttons that perform the transition.
- process/states/state/transitions/transition/@breakAfterTransition
- A tag that determines whether there should be a break (display of the break object) in the process after the transition is completed. Available values are either "true" or "false".
- process/states/state/transitions/transition/@redirectAfterTransitionURL
- A tag that determines whether there should be a redirection to the supplied URL after the transition is completed. URL be either static (e.g.: http://www.onet.pl) or dynamic. The value of this attribute is executed as the jPalio code. This enables usage of methods from the page module to create the URL to jPalio pages. An example is given below:
redirectAfterTransitionURL="$page.url("hetman.proc.page", $_RowID, $+("&HSubprocess=MAIN&param1=", $param1))"/>
- process/states/state/transitions/transition/@action
- Code of the object responsible for business logic related to execution of a specific transition.
- Default value:
<prefix>.<target state name>.action
- process/states/state/transitions/transition/@condition
- Code of the object that checks for the ability to execute a transition.
- Default value:
<prefix>.<target state name>.condition
- process/states/state/transitions/transition/@form
- Code of the object responsible for displaying a form, where the data necessary to perform a transition (execute an action) can be entered.
- Default value:
<prefix>.<target state name>.form
The /process/states/state/transitions/transition/privs/priv element - the definition of rights
- /process/states/state/transitions/transition/privs/priv/@id
- jPalio privilege identifier that a logged in user must have in order to perform a specific transition. To define privileges we can use identifiers or privilege names. If we place both an identifier and a name in the definition, only the identifier will be taken into consideration.
- /process/states/state/transitions/transition/privs/priv/@name
- jPalio privilege name that a logged in user must have in order to perform a specific transition.
* - obligatory attributes
Implement the process manager
To create the process manager, you have to create a Groovy or JAVA class that inherits after the palio.modules.hetman.ProcessManager abstract class. The manager must ensure its own implementation of two methods:
/** * Collects the current state of request/document * @param instanceId identifier of the request/document * @throws PalioException */ public abstract State getInstanceState(Object instanceId) throws PalioException; /** * Changes the request/document's state. This method is invoked after a successful execution of the transition/action object. * @param instanceId identifier of the request/document * @param state new state * @throws PalioException */ public abstract void changeInstanceState(Object instanceId, State state) throws PalioException;
An example of the implementation:
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())
}
}
Common condition for all transitions
To implement a common condition for all transitions, you have to overwrite the below method in the process manager. This method is always invoked before executing the condition object.
/** * The method is executed always before checking the conditions implemented in the condition object. * By overwriting this method, in own implementation of the process manager, we can implement common conditions for all transitions. * @param instanceId identifier of the request/document * @throws PalioException */ public Result checkCommonCondition(Object instanceId) throws PalioException
Create the process page
Each process must have its own process page. This page functions as a dispatcher. With this page the whole process is executed (both the business logic and the presentation layer). All process forms should send data to the URL of this page. The main object of the process page (the object that forms the contents of the page) should contain a call to the method executeProcess from the Hetman module.
An example of the contents of the main object of the process page:
$try({
$hetman.executeProcess("hetman.proc", $toLong((String)$_RowID))
}, {
$error.getMessage()
})
or in Groovy (more complicated but we have full control over the catched exception)
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())
}
Create the view object
The view object is responsible for presentation of the request's state and for displaying forms related to transitions that are possible to execute. To display the forms, invoke the displayTransitions method from the Hetman module.
The view object is invoked with the following arguments:
- 0. Object instanceId
- Identifier of a request/document
- 1. String infoMessage
- Information set by the Hetman module's mechanism
- 2. String errorMessage
- Error information set by the Hetman module's mechanism e.g. when the exception palio.modules.hetman.exceptions.BussinessException is returned during the transition.
The default code of the view object is:
<prefix>.view.
If you want to use a non-standard code of the view object you should provide the object code in the process definition (the stateView attribute).
An example of the view object's contents (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>
information on the request (header)<br>
$ifNotNull($@infoMessage, {
<div class="HMessage">$@infoMessage</div>
})
$ifNotNull($@errorMessage, {
<div class="HErrorMessage">$@errorMessage</div>
})
$hetman.displayProcess()
request form<br>
request history<br>
</body>
</html>
Create the error handling object
The error object is responsible for handling errors that can occur during the process execution.
The default code of the error object is:
<prefix>.error
If you want to use a non-standard code of the error object you should provide the object code in the process definition (the errorView attribute).
The error object is invoked with the following arguments:
- 0. Object instanceId
- Identifier of a request/document
- 1. Throwable exception
- Exception
An example of the error handling object's contents (hetman.error):
$#params(Long instanceId, java.lang.Throwable ex)
<html>
<head>
$html.contentMeta()
</head>
<body>
information on the request (header)<br>
$if($palio.instanceOf($@ex, "palio.modules.hetman.exceptions.ProcessException"), {
Process error: $@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())
Source state: $@srcState.getId()<br>
Target state: $@destState.getId()<br>
})
$if($palio.instanceOf($@ex, "palio.modules.hetman.exceptions.InsufficientPrivilegesException"), {
Insufficient rights
})
}, {
$@ex.getMessage()
})
</body>
</html>
The Groovy language is recommended for handling exceptions. An example is given below:
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("Process error occurred<br>")
switch (ex) {
case palio.modules.hetman.exceptions.NoSuchTransitionException:
buff.append("No defined transition from the state ${ex.getSource().getId()} ")
buff.append("to state ${ex.getDestination().getId()}")
break
case palio.modules.hetman.exceptions.InsufficientPrivilegesException:
buff.append("Insufficient rights<br>")
Transition transition = ex.getTransition()
if (ex.getUserId()) {
String username = Groovy.module("user").userName(ex.getUserId())
buff.append("User: ${username}\n")
}
if (transition != null) {
buff.append("Transition from state ${transition.getSource().getId()} ")
buff.append("to state ${transition.getDestination().getId()}")
}
break
}
break
default:
buff.append(ex.getMessage())
}
Groovy.object("hetman.displayError", [instanceId, buff.toString()].toArray())
Create the break object
This object is invoked when a transition is completed that has the "breakAfterTransition" attribute set to "true" in its definition. The main task of this object is to present information on a completed transition and on an interruption of the process.
The default code of the break object is:
<prefix>.break.
If you want to use a non-standard code of the break object you should provide the object code in the process definition (the breakView attribute).
The break object is invoked with the following arguments:
- 0. Object instanceId
- Identifier of a request/document
- 1. String message
- A message that needs to be presented. The message is set by the action object.
An example of the break object's contents (hetman.break):
$#params(Long instanceId, String message) <html> <head> $html.contentMeta() </head> <body> information on the request (header)<br> <h1>break in the process: $@message</h1> </body> </html>
Implement the process
Each state defined within a process can have its own form, condition and action objects.
Create the form object (.form)
This object is responsible for displaying a form, where one can enter data that will be used during the performance of a transition to a particular state. The form must be sent to the process page. The form's content should include a call to one of the Hetman module's methods:
public void displayTransitionButton(String destinationStateId) throws PalioException, HetmanException, ProcessException public void displayTransitionHiddenField(String destinationStateId) throws PalioException, HetmanException
Those methods generate the html code with additional form elements that allow the Hetman module to detect the start of a transition into a particular state.
The form object is invoked with the following arguments:
- 0. Object instanceId
- Identifier of a request/document
The default code of the transition form object is:
<prefix>.<identifier of the target state>.form
If you want to use a non-standard code of the form object you should provide the object code in the definition of a particular transition (the form attribute).
An example of the form object (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")
}, {})
For the form object it is possible to create the form initialization object. Such an object is useful for e.g. reading dictionary data displayed on the form. Code of the initialization object for a particular form:
<form object code>$init
Create the condition object (.condition)
The task of the condition object (of a particular state) is to check whether a particular request/document can proceed from its current state to the state, to which a particular condition object belongs to. Checking the condition (invoking the condition object) is done on an attempt to display a form and on execution of a transition/action. The condition object must return the instance of the palio.modules.hetman.Result class. When checking of the condition fails the transition form will not be displayed (information on the reason of non-fulfilment of the condition will be displayed instead). Also, the action bound to the transition will not be executed.
The condition object is invoked with the following arguments:
- 0. Object instanceId
- Identifier of a request/document
The default code of the transition execution condition object is:
<prefix>.<target state identifier>.condition
If you want to use a non-standard code of the condition object you should provide the object code in the definition of a particular transition (the condition attribute).
An example of the condition object (hetman.VERIFICATION_OK.condition):
import palio.modules.hetman.* // checking conditions return new Result(true, "All OK");
Create the action object (.action)
The action/transition object is invoked when the transition form is sent (if the transition conditions have been met). This object is also executed when the executeTransition method is called from the Hetman module. This object is responsible for implementation of the business logic related to the transition from the current state to the state, to which a particular transition object belongs. The condition object must return the instance of the palio.modules.hetman.Result class. If execution of the action object fails, the state of the request does not change. If in the transition object an exception of the type palio.modules.hetman.exceptions.BussinessException is returned the transition will not be executed, and the error object will not be displayed. Instead, the state presentation object will be displayed with the parameter concerning information on the business error (errorMessage) set.
The condition object is invoked with the following arguments:
- 0. Object instanceId
- Identifier of a request/document
The default code of the action object is:
<prefix>.<target state identifier>.action
If you want to use a non-standard code of the action object you should provide the object code in the definition of a particular transition (the action attribute).
An example of the action object (hetman.VERIFICATION_OK.action):
import palio.modules.hetman.*
import palio.modules.hetman.exceptions.*
// business logic of action
// if a business error occurs, BussinessException should be returned
throw new BussinessException("Incorrect value ...")
return new Result(true);
Access to predefined state and transition parameters
For each state and transition you can define any number of parameters. Examples below show how to make calls to predefined parameters.
$hetman.getStateParam("hetman.proc", "NEW_ORDER", "test")
$hetman.getTransitionParam("hetman.proc", "NEW_ORDER", "VERIFICATION_OK", "test")
ProcessExecutionContext context = Groovy.module("hetman").getProcessExecutionContext()
context.getCurrentState().getParam("test")
context.getCurrentTransition().getParam("test")
Transaction handling
Hetman has a built-in transaction handling. All operations related to execution of a transition (execution of the action object, change of state) are covered by the transaction. The transaction is created on connectors placed in the process definition (the "connectors" tag). There is a possibility to dynamically add a connector to the existing transaction by a call, e.g. in the method's action object:
$sql.transactionAdd("connector") $// add a single connector to the existing transaction
$sql.transactionAdd(["connector1", "connector2"]) $// add a group of connectors
If during an attempt to execute the transition any exception occurs or the action object returns new Result(false), there will be a rollback. Otherwise, commit is executed.
Grouping of transitions
Hetman allows to group transitions under transitions that are possible in a particular state. Grouping can be useful when we want those transitions to be handled from a single, common form. For a group of transitions you can define the form object code (the form attribute). Each group must have a name (the name attribute).
The Hetman module's methods that support grouping of transitions:
List getTransitionsForDropList(String groupName, Boolean checkPossibility)
Create subprocesses
Defining subprocesses allows parallel processing of the instance being processed.
Define a subprocess
To enable handling of subprocesses for a particular process you should define at least one subprocess. States of a subprocess should be included in the subprocess tag. If a particular process has the handling of subprocesses enabled, states that are not part of any subprocess automatically create the main subprocess named MAIN. Przykładowa definicja podprocesu:
<states> ... ... <!-- subprocess definition --> <subprocess name="SUBPROCESS_1"> <state id="STATE_E" first="true"> <transitions> <transition destination="STATE_F" /> <transitions/> </state> <state id="STATE_F"> <transitions> <transition destination="STATE_G" /> <transition destination="STATE_H" /> <transitions/> </state> <state id="STATE_G" last="true" success="true"/> <state id="STATE_H" last="true" success="false"/> </subprocess> </states>
The /process/states/subprocess element - the definition of a subprocess
- process/states/subprocess/@name *
- Subprocess name. This name is the main identifier of a subprocess in the Hetman module.
States of a subprocess can have additional attributes:
- process/states/subprocess/state/@first
- Defines if a particular state is the first state in the subprocess. Available values: true/false.
- process/states/subprocess/state/@last
- Defines if a particular state is the last state in the subprocess. Available values: true/false.
- process/states/subprocess/state/@success
- This attribute can be assigned only to the last state in the subprocess. It determines whether the subprocess completed successfully. Available values: true/false.
Implementation of the process manager that handles subprocesses
For Hetman to correctly handle subprocesses, the following method must be overwritten in the process manager:
/** * Collects the current state of request/document in the subprocess. * Due to backward compatibility the method is not abstract. * To be able to use the subprocesses you must overwrite this method in the process manager. * @param instanceId identifier of the request/document * @param subprocess subprocess * @throws PalioException, ProcessException * @return if for a particular request/document the subprocess was not started (the method should return null) */ public State getInstanceState(Object instanceId, Subprocess subprocess) throws PalioException
Execute/display a subprocess
Value of the global HSubprocess parameter decides which subprocess will be executed/displayed. By default, Hetman operates on the main subprocess (MAIN). When a transition is under way, the subprocess to which the target state belongs is checked. If the subprocess has already started, the last state in the subprocess is retrieved and the transition is executed from this state (there must be a transition to a specified target state defined in it). If the subprocess has not started yet, the last state of the main subprocess is retrieved. In this state there should be the definition of a transition to the target state (it should be the first state in the subprocess).
Changes
2.5.0
- The ability to define subprocesses
- The ability to define an URL to which the client is to be redirected after completing a transition
2.5.1
- The identifier of the processed instance was changed from Long to Object
- The signature of the getInstanceState method in ProcessManager was changed (Note! This change is not backward compatible)
- The signature of the changeInstanceState method in ProcessManager was changed (Note! This change is not backward compatible)
2.5.2
- New methods in the Hetman module: getStatesAfter, getStatesBefore
2.5.3
- The ability to implement a common condition for all transitions in the process manager
3.0.0
- A change of name of the state attribute: from name to id
- Automatic creation of a process implementation template based on the process definition
- WWW interface to manage processes
Summary
Advantages of the Hetman module:
- Introduction of the process implementation convention - order is preserved; easy code maintenance
- Performance - a process definition is stored in the memory; the necessity to call a database has been reduced to minimum
- Transactions - all operations are included in a transaction by definition
- Considerable flexibility - no requirements as to the database structures of the implemented process
- Easy implementation - modification of a process definition now involves only updating an object with the definition; it does not require the server's restart
- Full integration with jPalio
- Defining subprocesses that allows parallel processing of the instance under processing.
- Automatically generated code template based on the process definition.
