Build your own Camunda task explorer with CDI and JSF

There’s a lot of interest in how to write a task list with CDI and JSF, and not a lot of up-to-date examples available. Until now! – In this blog post I’m going to show you how you can build your own task list with Camunda BPM

Build a process application with JSF and CDI

To get tasks into your task list, you need to build a process application that includes at least one process definition, as well as some user tasks. A recipe to build a JSF based process application can be found in our getting started guide.

After completing the tutorial you’ll have a pizza order process like this:
pizza order process model
It contains a simple start form to start process instances and a form to approve the order.

<!DOCTYPE HTML>
<html lang="en" xmlns="https://www.w3.org/1999/xhtml"
  xmlns:ui="https://java.sun.com/jsf/facelets"
  xmlns:h="https://java.sun.com/jsf/html"
  xmlns:f="https://java.sun.com/jsf/core">

<f:view>
  <f:metadata>
    <!-- Start working on a task. Task Id is read internally from
         request parameters and cached in the CDI conversation scope.
    -->
    <f:event type="preRenderView" listener="#{camundaTaskForm.startTaskForm()}" />
  </f:metadata>
  <h:head>
    <title>Approve Order</title>
  </h:head>
  <h:body>
    <h1>Order:</h1>
    <p>Customer: #{approveOrderController.orderEntity.customer}</p>
    <p>Address: #{approveOrderController.orderEntity.address}</p>
    <p>Pizza: #{approveOrderController.orderEntity.pizza}</p>
    <h:form id="submitForm">
      <h:outputLabel>Approve Order?</h:outputLabel>
      <h:selectBooleanCheckbox value="#{approveOrderController.orderEntity.approved}"/><br/>
      <h:commandButton id="submit_button" value="Approve Order" action="#{approveOrderController.submitForm()}" />
    </h:form>
  </h:body>
</f:view>
</html>

The forms use a pre-built bean <a href="https://github.com/camunda/camunda-bpm-platform/blob/master/engine-cdi/src/main/java/org/camunda/bpm/engine/cdi/jsf/TaskForm.java" target="_blank" rel="noopener noreferrer">org.camunda.bpm.engine.cdi.jsf.TaskForm</a> to interact with the process instance. It’s used to access the process instance and complete the task.

The code for the tutorial is also available on GitHub.

You can also use our maven project templates to generate a project with a process and some form snippets and build your own process application. The camunda-archetype-ejb-war will be your friend.

Start a process instance from a list of process definitions

To start a process instances with a JSF-start form you need a list of all process definitions. This list is very simple with injection of the process engine services:

@SessionScoped
@Named("startList")
public class ProcessDefinitionList extends ProcessApplicationBean implements Serializable {

  @Inject
  private RepositoryService repositoryService;

  @Inject
  private FormService formService;

  @Inject
  private RuntimeService runtimeService;

  public List<ProcessDefinition> getList() {
    return repositoryService.createProcessDefinitionQuery().latestVersion().list();
  }

  public String getAbsoluteStartFormKey(ProcessDefinition processDefinition) {
    String startFormKey = "";
    if (processDefinition.hasStartFormKey()) {
      startFormKey = formService.getStartFormKey(processDefinition.getId());
    }

    if (startFormKey.startsWith("app:")) {
      String applicationPath = getApplicationPath(processDefinition.getId());
      return applicationPath + "/" + startFormKey.substring(4); 
    }
    return startFormKey;
  }
}

You will get this start form from it:
demo start form
After hitting the start button, the user will see the start form and after that form is completed, the process instance will start. The user will be able to see it in the task list now.

The starting of the process instance and the navigation to the task list is based on a injected bean of a jsf actionListener:

<h:commandButton class="btn btn-small" 
    rendered="#{!thisProcessDefinition.hasStartFormKey()}" 
    action="taskList.jsf" 
    actionListener="#{businessProcess.startProcessByKey(thisProcessDefinition.key)}" 
    value="start"/>

Handle more than one process application

If you have more than one process application deployed to your shared engine, the start list and the task list have to work with different servlet-context-paths.

This is done in the super-class of the ProcessDefinitionList:


  public String getApplicationPath(String processDefinitionId) {
    String deploymentId = repositoryService
        .getProcessDefinition(processDefinitionId)
        .getDeploymentId();

    // get the name of the process application that made the deployment
    String processApplicationName = managementService
        .getProcessApplicationForDeployment(deploymentId);

    if (processApplicationName == null) {
      // no a process application deployment
      return null;
    } else {
      ProcessApplicationService processApplicationService = BpmPlatform.getProcessApplicationService();
      ProcessApplicationInfo processApplicationInfo = processApplicationService.getProcessApplicationInfo(processApplicationName);
      return processApplicationInfo
          .getProperties()
          .get(ProcessApplicationInfo.PROP_SERVLET_CONTEXT_PATH);
    }
  }

Working with tasks in JSF-tasklist

Again, injecting the process engine services makes a task list very simple:

@SessionScoped
@Named
public class TaskList extends ProcessApplicationBean implements Serializable {

  @Inject
  private TaskService taskService;
  @Inject
  private FormService formService;

  private String assignee = null;

  public void update() {
    // do nothing here, since a refresh trigger a reload of the list anyway
  }

  public List getList() {
    if (assignee != null && assignee.length() > 0) {
      return taskService.createTaskQuery().taskAssignee(assignee).initializeFormKeys().list();
    } else {
      return taskService.createTaskQuery().initializeFormKeys().list();
    }
  }

  public String getAssignee() {
    return assignee;
  }

  public void setAssignee(String assignee) {
    this.assignee = assignee;
  }

Because I’ve concentrated on the technical details, our task list may looks like this:

simple tasklist
Of course, you can change the cryptic IDs against businessKeys and display more information from your business context.

Claiming and unclaiming

If you work in groups, your colleagues should not be able to see the tasks that you are currently working on. Therefore the user has to claim a task. If you are unable to finish the work, you can give the task back to the team by “claiming” it with userID null.
The methods for claim and unclaim looks like this:
  public void unclaim(Task task) {
    taskService.claim(task.getId(), null);
  }

  public void claim(Task task) {
    taskService.claim(task.getId(), currentUser);
  }

  private String currentUser = null;

  public String getFormKey(Task task) {
    TaskFormData taskFormData = formService.getTaskFormData(task.getId());
    if (taskFormData!=null) {
      return taskFormData.getFormKey();
    }
    else {
      // we do not want to fail just because we have tasks in the task list without form data (typically manually created tasks)
      return null;
    }
  }

  public String getAbsoluteFormKey(Task task) {
    String formkey = getFormKey(task);
    if (formkey.startsWith("app:")) {
      String applicationPath = getApplicationPath(task.getProcessDefinitionId());
      return applicationPath + "/" + formkey.substring(4);
    } else {
      return formkey;
    }
  }

  public String getCurrentUser() {
    return currentUser;
  }

  public void setCurrentUser(String currentUser) {
    this.currentUser = currentUser;
  }

This is the table of the taskList.xhtml:
<h:dataTable value="#{taskList.list}" var="thisTask" id="list" cellspacing="0" rowClasses="odd, even" styleClass="table table-striped table-bordered">
  <h:column><f:facet name="header">ID</f:facet>#{thisTask.id}</h:column>
  <h:column><f:facet name="header">name</f:facet><strong>#{thisTask.name}</strong></h:column>
  <h:column><f:facet name="header">creation time</f:facet>#{thisTask.createTime}</h:column>
  <h:column><f:facet name="header">due date</f:facet>#{thisTask.dueDate}</h:column>
  <h:column><f:facet name="header">assignee</f:facet>#{thisTask.assignee}</h:column>
  <h:column><f:facet name="header">actions</f:facet>
    <h:commandLink action="#{taskList.claim(thisTask)}" value="claim" type="submit" class="btn btn-small"/>
    <h:commandLink action="#{taskList.unclaim(thisTask)}" value="release" type="submit" class="btn btn-small"/>
    <h:outputLink value="#{taskList.getAbsoluteFormKey(thisTask)}" class="btn btn-small">
      <f:param name="taskId" value="#{thisTask.id}"></f:param>
      <f:param name="callbackUrl" value="#{request.contextPath}/app/taskList.jsf"></f:param>
      complete
    </h:outputLink>
  </h:column>
</h:dataTable>

Conclusion

The complete GitHub repo of this task list example is in consulting/snippets/jsf-simple-tasklist.

Have fun with JSF-Tasklists.

  • Monitoring Camunda Platform 7 with Prometheus

    Monitoring is an essential facet of running applications in a production system. Through this process, organizations collect and analyze data, and determine if a program is performing as expected within set boundaries. When combined with alerting, monitoring allows for detecting unexpected system behavior to mitigate exceptional situations as fast as possible. Furthermore, tracking the performance of a system enables organizations to improve those aspects that have the biggest impact with higher priority. One essential aspect of monitoring is the list of key metrics you want to observe. There are different categories of statistics that can be of interest here. To observe the defined metrics, there are plenty of application monitoring tools on the market today. They differ in many aspects...

    Read more
  • Securing Camunda 8 self-managed cluster and applications...

    Directory services are an effective way to manage an organization’s users, groups, printers, devices, and more. Most organizations accomplish this using Active Directory, Apache Directory, Oracle Internet Directory, or other similar tools. Recently I worked with a customer who wanted to see how he could secure the Camunda 8 Platform and process applications with such a directory. Their requirements consisted of: Allowing Directory users to access Camunda applications (Tasklist, Operate, Optimize) Accessing secured Tasklist & Operate APIs from our custom project Securing the custom project In this article, I’ll briefly explain the 3 easy steps taken to fulfill their requirements which include: Federate users from the Directory service into Keycloak Declare an application in Identity to access Camunda APIs Configure...

    Read more
  • Accelerate Connectivity with Camunda Platform 8.1

    We’re thrilled to announce Camunda Platform 8.1, the latest release of our process orchestration solution. This new version introduces features that accelerate connectivity to the many different systems and technologies that are required for true digital transformation, including: Create custom Connectors with our Integration Framework to accelerate connectivity New out-of-the-box Connectors for popular services Enhancements to Camunda Modeler that improve productivity Hot backups and official support for Amazon EKS and Red Hat OpenShift Plus, several upgrades requested by Camunda Platform 7 customers Organizations across all industries rely on complex technology stacks to adapt and enhance their operations in response to market dynamics, new disruptive companies, and increasing consumer expectations. Your technology stack likely includes everything from cutting-edge technologies to legacy...

    Read more

Ready to get started?

Still have questions?