Building Vaadin applications on top of Activiti
by Petter Holmström
Introduction
In this article, we are going to look at how the Activiti BPM engine can be used together with Vaadin. We are going to do this in the form of a case study of a demo application that is available on GitHub. The code is licensed under Apache License 2.0 and can freely be used as a foundation for your own applications.
The Example Process
The following process is used in the demo application:
Compared to the capabilities of Activiti and BPMN 2.0, the above process is almost ridiculously simple. However, it allows us to test the following things:
Process start forms, i.e. forms that need to be filled in before a process instance is created.
User task forms, i.e. forms that need to be filled in before a task can be marked as completed.
Parallell tasks
Different candidate groups (i.e. groups whose users are potential assignees of a certain task)
Here is a short walk-through of the process:
Before a new process instance is created, the reporter has to fill in a Submit bug report form.
Once the instance has been created, two tasks are created:
Update bug report: a manager assigns priority and target version to the report. Potential assignees are members of the managers group.
Accept bug report: a developer accepts the bug report. Potential assignees are members of the developers group.
Both of these tasks require the assignee to fill in a form before they can be completed: the Update bug report form and Accept bug report form, respectively.
Once the tasks have been completed, a new task is created, namely Resolve bug report. Potential assignees are members of the developers group. Ideally, this task should automatically be assigned to whoever claimed the Accept bug report task, but currently this is not implemented.
Before the task can be completed, the assignee has to fill in the Resolve bug report form.
All tasks have been completed and the process instance ends.
Prerequisites
In order to get the most out of this article, you should already be familiar with both Vaadin and Activiti. If not, there is enough free material available on both products’ web sites to get you started.
The demo application is a standard Java EE 6 web application and can be deployed to any JEE 6 web container, such as Tomcat 7. It uses an embedded in-memory H2 database for storing data, which means that all your data will be lost when the server is restarted.
Eclipse 3.6 and the Vaadin plugin was used to create the application. Both the project files and the third-party libraries are included in the source code repository. At this point, I recommend you to download the source code before continuing.
Once you have Eclipse, Tomcat and Git properly installed and configured, you can follow the following instructions to get the demo application up and running:
Open a command line and clone the Git repository:
git clone git://github.com/peholmst/VaadinActivitiDemo.git
Start up Eclipse.
From the File menu, select Import.
Select Existing Projects into Workspace and click Next.
In the Select root directory field, click the Browse button and locate the cloned Git repository directory.
In the list of projects, check VaadinActivitiDemo and click Finish.
In the Project Explorer, right-click on VaadinActivitiDemo, point to Run As and select Run on Server.
Select the Tomcat 7 server and click Finish.
Open a web browser and point it to http://localhost:8080/VaadinActivitiDemo.
Scope
As Activiti has a huge amount of features, we are only going to look at a small subset of them in order to keep the scope of this article under control. More specifically, we are going to look at the following two questions:
How easy (or hard) is it to create custom-built forms using Vaadin and plug these into Activiti?
How easy (or hard) is it to combine process data from Activiti with other domain data from e.g. JPA?
Application Architecture
In this section, we are going to briefly discuss the architecture of the demo application on a general level and show how it has been implemented on more technical level. A simplified version of the architecture is illustrated here:
The H2 Database
The H2 database is used in in-memory mode and will start when the process engine is initialized and stop when the engine is destroyed. All you have to do is specify some connection parameters when you configure Activiti and the rest will be handled automatically.
The Activiti Engine and Process Definitions
The Activiti engine is initialized and destroyed by a servlet context listener, like so:
Java
@WebListener
public class ProcessEngineServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
ProcessEngines.init();
deployProcesses();
}
@Override
public void contextDestroyed(ServletContextEvent event) {
ProcessEngines.destroy();
}
private void deployProcesses() {
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("path/to/bpmn-document.bpmn20.xml")
.deploy();
}
}
Once the process engine has been initialized, the context listener deploys the BPMN 2.0 process definitions to it. In other words, the Activiti process engine becomes available as soon as the web application starts and remains up and running until the application is stopped. All the Vaadin application instances use the same Activiti engine.
The Vaadin Application
The Vaadin application is designed according to the Model-View-Presenter (MVP) pattern and is implemented using MVP4Vaadin. This gives us the following benefits:
Clear separation between logic and UI (makes unit testing easier).
View navigation becomes easier (e.g. the breadcrumb bar shown in the demo screencast is a built-in part of MVP4Vaadin).
The following diagram illustrates the different views and potential navigation paths between them:
When the application is first started, the Login View is displayed in the main window. Once the user has logged on, the main window is replaced with the Main View:
Java
public class DemoApplication extends Application implements ViewListener {
// Field declarations omitted
@Override
public void init() {
createAndShowLoginWindow();
}
private void createAndShowLoginWindow() {
// Implementation omitted
}
private void createAndShowMainWindow() {
// Implementation omitted
}
@Override
public void handleViewEvent(ViewEvent event) {
if (event instanceof UserLoggedInEvent) {
// Some code omitted
createAndShowMainWindow();
} // Other event handlers omitted
}
// Additional methods omitted.
}
The main view acts as a controller and container for a number of embedded views:
The Home View is the main menu. From here, you can navigate to the Process Browser View and the Identity Management View.
The Process Browser View contains a list of all the available process definitions. From this view, you can start new process instances. If a process has a start form, you can also navigate to the User Form View.
The Identity Management View allows you to manage users and user groups.
The Unassigned Tasks View contains a list of all unassigned tasks. You can navigate to this view from any other view. From this view, you can assign tasks to yourself.
The My Tasks View contains a list of all tasks currently assigned to you. You can navigate to this view from any other view. From this view, you can complete tasks. If a task has a form, you can also navigate to the User Form View.
The User Form View is responsible for displaying the User Task Forms, e.g. before a new process instance is created or before a task is completed. The information about which form to show (if any) is specified in the BPMN process definition. Please note that when we are talking about forms in this article, we are referring to the Acticiti form concept. Do not confuse this with Vaadin forms.
These views (or technically speaking their corresponding presenters) communicate directly with the Activiti engine. For example, the following snippet is taken from the ProcessPresenter
class:
Java
@Override
public void init() {
getView().setProcessDefinitions(getAllProcessDefinitions());
}
public void startNewInstance(ProcessDefinition processDefinition) {
try {
if (processDefinitionHasForm(processDefinition)) {
openFormForProcessDefinition(processDefinition);
} else {
getRuntimeService().startProcessInstanceById(processDefinition.getId());
getView().showProcessStartSuccess(processDefinition);
}
} catch (RuntimeException e) {
getView().showProcessStartFailure(processDefinition);
}
}
private List<ProcessDefinition> getAllProcessDefinitions() {
ProcessDefinitionQuery query = getRepositoryService().createProcessDefinitionQuery();
return query.orderByProcessDefinitionName().asc().list();
}
private RepositoryService getRepositoryService() {
return ProcessEngines.getDefaultProcessEngine().getRepositoryService();
}
private RuntimeService getRuntimeService() {
return ProcessEngines.getDefaultProcessEngine().getRuntimeService();
}
The Main View also regularly checks if there are new tasks available and notifies the user if that is the case. The Refresher add-on is used to handle the polling.
Some Notes on MVP4Vaadin
Thanks to MVP4Vaadin, navigation between views is very simple. For example, the following code snippet is taken from the WindowHeader
component, a part of the Main View implementation:
Java
@SuppressWarnings("serial")
private Button createMyTasksButton() {
Button button = new Button();
button.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
mainPresenter.showMyTasks();
}
});
button.addStyleName(Reindeer.BUTTON_SMALL);
return button;
}
@SuppressWarnings("serial")
private Button createUnassignedTasksButton() {
Button button = new Button();
button.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
mainPresenter.showUnassignedTasks();
}
});
button.addStyleName(Reindeer.BUTTON_SMALL);
return button;
}
The corresponding snippets from the MainPresenter
class are as follows:
Java
public void showUnassignedTasks() {
getViewController().goToView(UnassignedTasksView.VIEW_ID);
}
public void showMyTasks() {
getViewController().goToView(MyTasksView.VIEW_ID);
}
Custom Forms
As you may already know, it is possible to use automatic form generation with Activiti, but the generated forms are not Vaadin based. In this article, we are going to use custom-built Vaadin forms instead. Even though this forces us to write Java code for each form we want to use, it gives us some advantages:
It is possible to have more complex forms with differnt kinds of components.
It is possible to tailor the appearance and look and feel of the forms to the user’s needs.
It is easy to plug in other infrastructure services such as EJBs and JPA entities.
The following approach is used to implement custom forms in the demo application:
Here is a short walk-through of the most important classes:
The
UserTaskForm
interface is implemented by all custom forms. This interface defines several methods, the most interesting of which are the following:populateForm(…)
: This method populates the form with initial data retrieved from the Activiti form service.getFormProperties()
: This method creates a map of the form data that will be sent to the Activiti form service when the form is submitted.
The
UserTaskFormContainer
is a class that contains user task forms. Each form can be accessed by a unique form key, which in turn is used in BPMN-documents to refer to forms. The main Vaadin application class is responsible for creating and populating this container. Please note, that this container class has nothing to do with Vaadin Data Containers.The
UserFormViewImpl
class (and its corresponding presenter) is responsible for looking up the correct form (by its form key), populating it, displaying it to the user and finally submitting it.
Some Code Examples
We are now going to look at some snippets from the demo application source code.
First up is a method from the MyTasksPresenter
class that is invoked when the user wants to open the form for a specific task:
Java
public void openFormForTask(Task task) {
String formKey = getFormKey(task);
if (formKey != null) {
HashMap<String, Object> params = new HashMap<String, Object>();
params.put(UserFormView.KEY_FORM_KEY, formKey);
params.put(UserFormView.KEY_TASK_ID, task.getId());
getViewController().goToView(UserFormView.VIEW_ID, params);
}
}
The method checks if the task has a form and asks the view controller (a part of MVP4Vaadin) to navigate to the User Form View if that is the case. The task ID and form key is passed to the view as a map of parameters.
The next code example is a method of the UserFormPresenter
class that is invoked when the view controller has navigated to the User Form View:
Java
@Override
protected void viewShown(ViewController viewController,
Map<String, Object> userData, ControllableView oldView,
Direction direction) {
if (userData != null) {
String formKey = (String) userData.get(UserFormView.KEY_FORM_KEY);
if (userData.containsKey(UserFormView.KEY_TASK_ID)) {
String taskId = (String) userData.get(UserFormView.KEY_TASK_ID);
showTaskForm(formKey, taskId);
}
// The rest of the implementation is omitted
}
}
private void showTaskForm(String formKey, String taskId) {
UserTaskForm form = userTaskFormContainer.getForm(formKey);
TaskFormData formData = getFormService().getTaskFormData(taskId);
form.populateForm(formData, taskId);
getView().setForm(form);
}
The method first extracts the task ID and form key from the parameter map. It then invokes a helper method that looks up the corresponding form data and form from the Activiti form service and the UserTaskFormContainer
, respectively. Finally, the form is populated and shown to the user.
The final example is a method (also from UserFormPresenter
) that is invoked when the user submits the form:
Java
public void submitForm(UserTaskForm form) {
if (form.getFormType().equals(UserTaskForm.Type.START_FORM)) {
getFormService().submitStartFormData(form.getProcessDefinitionId(), form.getFormProperties());
} else if (form.getFormType().equals(UserTaskForm.Type.TASK_FORM)) {
getFormService().submitTaskFormData(form.getTaskId(), form.getFormProperties());
}
getViewController().goBack();
}
As there are two different kinds of forms (process start forms and user task forms, respectively), the method has to start by checking which kind it is currently processing. Then, the information is submitted to the Activiti form service. Finally, the view controller is asked to navigate back to what ever page it was on before the User Form View became visible.
Complex Domain Objects
The demo application does not use any domain objects as all the information can be represented as Activiti process variables. However, in most real-world applications you probably want to use a dedicated domain model.
We are now going to look at a potential design for combining Activiti with a complex domain model. Please note that the design has not been tested in practice - feel free to test it if you feel like it (and remember to tell me the results)!
Here is a sketch of a process that involves a more complicated domain model than just a few strings:
The idea is that although many different entities need to be created and stored throughout the process, only some small parts of the information is actually required to drive the process forward. For example, the Send invoice task does not necessarily need the entire invoice object; only the invoice number, order number and due date should be sufficient. Likewise, the Receive payment task needs only the invoice number to be able to check that the invoice has been paid, the timer needs the due date to be able to send out a new invoice, etc.
Implementation Ideas
The actual forms that the users fill in could be implemented in Vaadin, as described previously in this article. When the form is submitted, the entities are saved to some data store (e.g. a relational database). After this, the necessary form properties are submitted to the Activiti form service, completing the task in question. In other words, Activiti is used to drive the process forward (i.e. define the business logic), whereas JPA or any other object persistence solution is used to store data.
There are a few things to keep in mind, though:
How are transactions handled?
How is data validation performed?
How is security enforced?
Is versioning of the domain data required? How should it be implemented if so? (Activiti already maintains a history log of the process operations.)
In smaller applications, the following design could be sufficient:
Here, the Presenter (in the MVP-pattern) is responsible for extracting the needed form properties from the domain data, saving the entity and submitting the form. This moves some of the logic to the UI layer, but for small applications this is not a big problem as the presenter is itself decoupled from the actual UI code.
For larger applications, the following design could be a better approach:
Here, both the repository and the form service engine is hidden behind a facade. A Data Transfer Object (DTO) is used to convey the data from the Presenter to the facade. This approach requires more code, but decouples the business layer from the UI layer even more. Security enforcement and transaction handling also become easier.
Summary
In this article, we have looked at how the Activiti BPM engine and Vaadin fit together. We have covered how the engine is initialized and accessed by Vaadin application instances. We have also covered how custom-made Vaadin forms can be used instead of Activiti’s own form generation. Finally, we have discussed a way of combining Activiti processes with a more complex domain model.
The Activiti API is clear and does not force adopters to use a specific GUI technology. Therefore, it plays really well with Vaadin and should be concidered a serious alternative for process centric enterprise applications.
Likewise, Vaadin should be considered a serious alternative as a front end technology for applications based on Activiti.
If you have any comments or questions, for example if something in the article is unclear or confusing, feel free to either post them below or send them to me directly by e-mail.