Build your own Camunda task explorer with CDI and JSF

By
  • Blog
  • >
  • Build your own Camunda task explorer with CDI and JSF
TOPICS

30 Day Free Trial

Bring together legacy systems, RPA bots, microservices and more with Camunda

Sign Up for Camunda Content

Get the latest on Camunda features, events, top trends, and more.

TRENDING CONTENT
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.

Try All Features of Camunda

Related Content

We're streamlining Camunda product APIs, working towards a single REST API for many components, simplifying the learning curve and making installation easier.
Learn about our approach to migration from Camunda 7 to Camunda 8, and how we can help you achieve it as quickly and effectively as possible.
We've been working hard to reduce the job activation latency in Zeebe. Read on to take a peek under the hood at how we went about it and then verified success.