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.
It contains a simple start form to start process instances and a form to approve the order.
<!DOCTYPE HTML>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://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.TaskFormto interact with the process instance. It's used to access the process instance and complete the task.
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:
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.
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:
Example task list
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;
}