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 <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
@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
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;
}
<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.