Every Task in a BPMN Process is of a particular type: The task type determines the Task's logic. Therefore, if you need a task that will perform actions specific to your business or interact with another system, consider implementing a custom task type.
You will need to create the following:
To declare the task type with its implementation and make it accessible for modeling in PDS, do the following:
Under Task Type Details, enter the task-type name and in the Classname field enter the fully qualified name of the class.
If you have already implemented the task type, you can copy the qualified name of the task class in its Outline view: right-click the class name and select Copy Qualified Name.
In the Parameters list box, define the task parameters if applicable.
Check Dynamic to wrap the parameter value in a non-parametric closure: Such a parameter is processed as { -> <parameter_value> }
. The task-type implementation has to process the parameter as a closure.
In the Task Source Code Generation dialog box:
The generator does the following:
The implementation of your task type must implement the ExecutableTask
interface or its subtype.
Note: To implement an asynchronous task that waits for an event, communicates with a third-party system, or performs demanding operations, refer to Implementing an Asynchronous Task Type.
To implement a custom task type, do the following:
processInput()
will be called when the task instance receives new input.Result.FINISHED
. Then the task instance becomes accomplished and the Process execution continues (it releases the token).Important: The methods start() and processInput() must be implemented in such a way that the thread is not blocked.
Example task implementation
public class RecordReturn implements ExecutableTask {
@Override
public Result processInput(TaskContext arg0, Object arg1) throws ErrorException {
return null;
}
@Override
public Result start(TaskContext context) throws ErrorException {
//get the message parameter of the Task which is a Record:
RecordHolder message = (RecordHolder) context.getParameter("message");
//save the value of the "text" property of the message Record instance:
String messageText = (String) message.getProperty("text");
//set the "myProcessVariable" of the process to the messageText:
context.setVariableValue("myProcessVariable", messageText);
//finish and release the token:
return Result.FINISHED;
}
@Override
public void terminate(TaskContext context, TerminationReason reason) throws ErrorException {
}
}
If your class is an EJB, register it with the ComponentServiceBean class.
Asynchronous tasks represent the border of a model transaction: they hold the token but do not block further process execution (other tokens can continue). The task still waits for the result of its implementation and only then finishes.
An asynchronous task type must implement ExecutableTask
and extend the AbstractAsynchronousExecutionTask
which is an EJB.
Note that implementations of asynchronous task types must run in a single transaction: if your task-type implementation requires multiple transactions, split it into multiple task types. For such tasks, if the server is restarted during the task execution, the execution is repeated.
Important: If you still decide to use multiple transactions in the implementation of your asynchronous tasks, make sure that the execution is always consistent: consider handling the scenario when the server is restarted while the task is running: since the asynchronous execution of the task is not governed by the task, on server restart, the execution seizes to exist while the task itself becomes running again and waits for the result from the execution. To solve this problem, you can
- define the timeout on the task in your models to handle the case when the task receives no results after server restart: add an interrupting timer event on the border of your task with a timeout duration. Mind that such handling can cause premature interruption of the task as a side effect.
- persist data about the execution phase in the database and check its status regularly from the running task (heartbeat check).
To implement an asynchronous task type, do the following:
AbstractAsynchronousExecutionTask
and implements ExecutableTask
.AbstractAsynchronousExecutionTask
is implemented as an EJB).executeAsynchronously()
: checks prior to task execution whether the task should be executed asynchronously; this is useful for tasks that can run both as asynchronous or synchronous;collectDataForExecution()
: provides the data from the task context required for the Java implementation, typically parameters of the task typegetImplementationClass()
: returns the class that implements the task type (typically this class)processDataAsynchronously()
: processing logic that returns the result of the actionsprocessExecutionResult()
: processes the result after the asynchronous action @Stateless
public class GoogleSearchResultStats extends AbstractAsynchronousExecutionTask implements ExecutableTask {
@Override
public Serializable collectDataForExecution(TaskContext context) throws ErrorException {
String collectedData = (String) context.getParameter("queryString");
return collectedData;
}
@Override
public boolean executeAsynchronously(TaskContext context) throws ErrorException {
//the task is always asynchronous:
return true;
}
@Override
public Class<? extends AbstractAsynchronousExecutionTask> getImplementationClass() {
return GoogleSearchResultStats.class;
}
@Override
public Serializable processDataAsynchronously(Serializable data) {
String result;
try {
String keyword = (String) data;
result = readFromUrl(keyword);
} catch (IOException e) {
result = "not found";
}
return result;
}
@Override
public void processExecutionResult(TaskContext context, Serializable result) throws ErrorException {
System.out.println("Number of results: " + result);
}
/**
*
* @param keyword
* @return number of results
* @throws IOException
*/
public String readFromUrl(String keyword) throws IOException {
String url = "https://www.google.sk/search?q=" + keyword;
Document document = Jsoup.connect(url).userAgent("Mozilla").timeout(10000).get();
String question = document.select("#resultStats").text();
String resultCount = question.split(": ")[1];
return resultCount;
}
}
LSPSRuntimeException
. @EJB(beanName = "GoogleSearchResultStats")
private ExecutableTask searchResultTask;
@Override
protected void registerCustomComponents() {
// parameter 1 is the ejb, parameter 2 is the implementation class as referenced in the task type definition:
register(searchResultTask, GoogleSearchResultStats.class);
To have a task type implementation that is an EJB, you need to register it with the server via the register()
method of the ComponentServiceBean
class.
ComponentServiceBean
class as the ExecutableTask: @EJB(beanName = "DecimalAddition")
private ExecutableTask decimalAddition;
ComponentServiceBean
class in the registerCustomComponents()
method. register(decimalAddition, DecimalAddition.class);
Implementation:
@Stateless
public class DecimalAddition implements ExecutableTask {
@Override
public Result processInput(TaskContext context, Object input) throws ErrorException {
return null;
}
@Override
public Result start(TaskContext context) throws ErrorException {
Decimal a = (Decimal) context.getParameter("a");
Decimal b = (Decimal) context.getParameter("b");
System.out.println("################## result" + a.add(b));
return Result.FINISHED;
}
@Override
public void terminate(TaskContext context, TerminationReason reason) throws ErrorException {
}
}
Registration:
//Modifications in the ComponentServiceBean class:
@EJB(beanName = "DecimalAddition")
private ExecutableTask decimalAddition;
protected void registerCustomComponents() {
register(decimalAddition, DecimalAddition.class);
}