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:

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 org.camunda.bpm.engine.cdi.jsf.TaskForm 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 aplication. 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:

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:


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.

  • Orchestrating Cloud Events with Zeebe

    Disclaimer: This blog post is about Cloud-Native software, containers, Cloud Events, and Workflows. It describes a concrete example that you can run yourself using Kubernetes, Helm, and Zeebe.io. You should be familiar with Kubernetes and Helm to follow along and will learn about zeebe.io and cloudevents.io on your way. While working with Kubernetes the chances are quite high that you’ll find services written in different languages and using different technologies stacks. CloudEvents (cloudevents.io / CNCF spec) was born to enable these systems to exchange information by describing their events in a standard way, no matter which transports these services are using (HTTP, Messaging AMPQ/JMS, Protobuf, etc).  In such scenarios, where you have events being produced and consumed by different systems, there are common requirements that start to arise when...

    Read more
  • How we automatically keep our Documentation Screenshots...

    When you open the Camunda User Guide, you’ll see that there are many screenshots explaining the different functions and options the product offers. We hope that you, as a user, find those screenshots helpful. But for us as developers, creating and keeping those screenshots up to date has always been a pain. https://unsplash.com/photos/dDppsuM_UpE At the time of writing, the user guide for Camunda Optimize contained 94 screenshots. And with every release we add more functionality, which means the number of screenshots continuously grows. When we change the look of buttons or add a new section to the header, we need to update every screenshot that has a button or header in it. In practice, this meant we were manually recreating every...

    Read more
  • Git push to deploy to Camunda Cloud

    Using the Zeebe Action for GitHub, you can automate your GitHub repo to deploy BPMN models to Camunda Cloud when you push to a specific branch. In this quick tutorial, I show you how to configure your GitHub repo to deploy all BPMN models in the bpmn directory of your repo on a push to master. If you don’t have a Camunda Cloud account yet, you can join the public beta to get one. Create a client in Camunda Cloud Go into your Zeebe cluster in the Camunda Cloud console, and create a new client. You might want to name it “GitHub-Automation” so you know what it is for. Copy the “Connection Info” block by clicking the copy icon in the lower right-hand...

    Read more