LSPS documentation logo
LSPS Documentation
Creating a Task Type

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:

Declaring a Task Type

To declare the task type with its implementation and make it accessible for modeling in PDS, do the following:

  1. Switch to the Modeling perspective.
  2. In your module, create a task type definition: right-click the module, then New -> Task Type Definition.
  3. In the Task Type Editor, click Add under Task Types.
  4. 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.

  5. Select the relevant flags:
    • Public: select to allow access from importing modules
    • Create activity reflection type: select to create a Record that represents the action taken by the task type. The Record is a subtype of the Activity record and Therefore can be set as the activity parameter of the Execute task.
    • Deprecated: select to display a validation marker for deprecated elements when a task of this task type is used.
  6. 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.

    customtaskparams.png
    Task type declaration
  7. If you have not created the implementation yet, generate the class for the task type:
    1. In the GO-BPMN Explorer view, right-click the GO-BPMN module.
    2. Go to Generate -> Task Java Sources.
    3. In the Task Source Code Generation dialog box:

      1. Select the check boxes of the relevant tasks.
      2. In the Destination folder text box, specify the destination path, possibly to a package of the <YOUR_APP>-ejb project.
      3. Click Finish.

      The generator does the following:

      • places the task implementation in the correct directory structure.
      • generates a class declaration as an implementation of the com.whitestein.lsps.engine.ExecutableTask interface;
      • creates a variable for each parameter in the task type and the related setters used by the Execution Engine to access the variables;
      • generates the initial structure for the task implementation and documentation.
    4. Implement the task-type class.
      tasksourcecodegeneration.png
      Task source code generation
      Consider distributing the module as a library.

Implementing a Task Type

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:

  1. Declare the task type in your Module. Consider distributing it as a part of a library.
  2. In a workspace with your LSPS Application, switch to the Java perspective.
  3. If you have not generated the Task Java Sources, create the implementing task-type class:
    1. Right-click your package, go to New -> Class.
    2. In the dialog, set ExecutableTask in the Interface field.
      customtaskclass.png
      Creating class for implementation of the custom task type
  4. Implement the methods in the created class:
    • The start() runs when the task instance becomes Alive The method must return a Result value, that is, Result.FINISHED or Result.WAITING_FOR_INPUT:
      • If the method returns FINISHED, the task becomes Accomplished and the Process execution continues (the token leaves the Task).
      • If the method returns WAITING_FOR_INPUT, the token stops: the task becomes a transaction boundary and the method processInput() will be called when the task instance receives new input.
    • The processInput() method must also return a Result value: the return value is the same as in start(), but while the start() method is called only when the task becomes alive, the processInput() method is called whenever the task instance receives input until it returns Result.FINISHED. Then the task instance becomes accomplished and the Process execution continues (it releases the token).
    • The terminate() method is called when the task instance is terminated abnormally, for example, by a timeout intermediate event attached to the task

      Important: The methods start() and processInput() must be implemented in such a way that the thread is not blocked.

  5. To get the parameters of the task as defined in the Task Type definition, use the getParameter() method on the TaskContext. The TaskContext is passed as a parameter to the start() method.
  6. To access other resources of the model instance, such as, variables, signal queue, etc. use the respective methods of the TaskContext, such as, getVariableValue(), getProcessModel(), getProcessInstance(), addSignal(), etc.

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.

Implementing an Asynchronous Task Type

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:

  1. In the <YOUR_APP>-ejb project, create the class for the task type implementation that extends AbstractAsynchronousExecutionTask and implements ExecutableTask.
  2. Make the class an EJB (AbstractAsynchronousExecutionTask is implemented as an EJB).
  3. Implement the methods:
    • 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 type
    • getImplementationClass(): returns the class that implements the task type (typically this class)
    • processDataAsynchronously(): processing logic that returns the result of the actions
    • processExecutionResult(): 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;
        }
      }
      
  4. If you want to repeat the execution under some circumstances, throw a runtime exception from the respective method, for example, LSPSRuntimeException.
  5. Inject and register your class in the ComponentServiceBean.
      @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);
    
  6. If your class is an EBJ, register it with the ComponentServiceBean class.
  7. Build and deploy your application
  8. Declare the task type in a module.

Creating an EJB Task Type

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.

  1. Inject the task EJB in the ComponentServiceBean class as the ExecutableTask:
      @EJB(beanName = "DecimalAddition")
      private ExecutableTask decimalAddition;
    
  2. Register it with the ComponentServiceBean class in the registerCustomComponents() method.
    register(decimalAddition, DecimalAddition.class);
    

Example EJB Task Type

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);
  }