cancel
Showing results for 
Search instead for 
Did you mean: 
gravitonian
Star Collaborator
Star Collaborator

Introduction

Activiti 7 is an evolution of the battle-tested Activiti workflow engine from Alfresco that is fully adopted to run in a cloud environment. It is built according to the Cloud Native application concepts and differs a bit from the previous Activiti versions in how it is architected. There is also a new Activiti Modeler that we had a look at in a previous article.

In this article we will use the new Activiti 7 Process Runtime and Task Runtime Java APIs to try out the Activiti 7 process engine. We will do this from a Spring Boot 2 application. All the Activiti 7 Java artifacts that we need are available in Alfresco’s Maven Repository (Nexus).  

The Spring Boot application will also include the Web component (i.e. Spring MVC) so we can create a little ReST API to use for starting processes and interacting with processes and tasks. Activiti 7 provides a ReST API but we are not going to use it in this section when we just play around with the core libraries. Here we just create our own simple ReST API that will use the Activiti 7 Java libs (i.e. Process Runtime and Task Runtime).

The new APIs have been designed to provide a clear path to the Cloud Native approach. They also include security and identity management as first class citizens. Some common use-cases are also simplified with the new APIs.

In this article we will actually build a simple Business Process Management (BPM) application/solution using the Activiti 7 Core libraries. This is not normally something you would do, but it is a good exercise to be able to understand the APIs provided by Activiti 7.

Activiti 7 Deep Dive Article Series 

This article is part of series of articles covering Activiti 7 in detail, they should be read in the order listed:

 

  1. Deploying and Running a Business Process
  2. Using the Modeler to Design a Business Process
  3. Building, Deploying, and Running a Custom Business Process
  4. Using the Core Libraries - this article

Prerequisites

  • You have read and worked through the "Activiti 7 - Using the Modeler to Design Business Processes" article
  • JDK installed
  • Maven installed

Source Code

You can find the source code related to this article here:

https://github.com/gravitonian/activiti7-api-basic-process  

Generating a Spring Boot 2 App

It’s really easy to get going with a Spring Boot application. Just head over to https://start.spring.io/ and fill in the data for the app as follows:

Make sure to use Spring Boot version 2.0.x with Activiti 7 Beta 1 - 3, Beta 4 should be aligned with version 2.1.x.

You don’t have to use the same Group (org.activiti.training) and Artifact (activiti7-api-basic-process-usertask-servicetask-events) names as me, just use whatever you like. However, if you copy code from this article it might be easier if you use the same package names (i.e. the same group). Search for the H2 and Web dependencies so they are included in the Maven POM. Then click the Generate Project button. The finished Spring Boot 2 Maven project will automatically download as a ZIP. Unpack it somewhere.

Test the standard Spring Boot App

Let’s make sure that the Spring Boot application works before we continue with the Activiti stuff. This involves two steps. First build the app JAR and then run the app JAR.

Building the application JAR:

$ cd activiti7-api-basic-process-usertask-servicetask-events/

activiti7-api-basic-process-usertask-servicetask-events mbergljung$ mvn clean package

[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ activiti7-api-basic-process-usertask-servicetask-events---

[INFO] Building jar: /Users/mbergljung/IDEAProjects/activiti7-api-basic-process-usertask-servicetask-events/target/activiti7-api-basic-process-usertask-servicetask-events-0.0.1-SNAPSHOT.jar

Running the application JAR:

activiti7-api-basic-process-usertask-servicetask-events mbergljung$ java -jar target/activiti7-api-basic-process-usertask-servicetask-events-0.0.1-SNAPSHOT.jar

 .  ____          _ __ _ _

/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

\\/  ___)| |_)| | | | | || (_| |  ) ) ) )

 ' |____| .__|_| |_|_| |_\__, | / / / /

=========|_|==============|___/=/_/_/_/

:: Spring Boot ::        (v2.0.4.RELEASE)

2018-08-22 15:43:59.992  INFO 8959 --- [ main] cessUsertaskServicetaskEventsApplication : Starting Activiti7ApiBasicProcessUsertaskServicetaskEventsApplication v0.0.1-SNAPSHOT on MBP512-MBERGLJUNG-0917.local with PID 8959 (/Users/mbergljung/IDEAProjects/activiti7-api-basic-process-usertask-servicetask-events/target/activiti7-api-basic-process-usertask-servicetask-events-0.0.1-SNAPSHOT.jar started by mbergljung in /Users/mbergljung/IDEAProjects/activiti7-api-basic-process-usertask-servicetask-events)

2018-08-22 15:43:59.995  INFO 8959 --- [ main] cessUsertaskServicetaskEventsApplication : No active profile set, falling back to default profiles: default

2018-08-22 15:44:00.045  INFO 8959 --- [ main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@64b8f8f4: startup date [Wed Aug 22 15:44:00 BST 2018]; root of context hierarchy

2018-08-22 15:44:00.985  INFO 8959 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)

2018-08-22 15:44:01.011  INFO 8959 --- [ main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]

2018-08-22 15:44:01.011  INFO 8959 --- [ main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.32

2018-08-22 15:44:01.023  INFO 8959 --- [ost-startStop-1] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/mbergljung/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]

2018-08-22 15:44:01.112  INFO 8959 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext

2018-08-22 15:44:01.112  INFO 8959 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1070 ms

2018-08-22 15:44:01.204  INFO 8959 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Servlet dispatcherServlet mapped to [/]

2018-08-22 15:44:01.208  INFO 8959 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]

2018-08-22 15:44:01.209  INFO 8959 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]

2018-08-22 15:44:01.209  INFO 8959 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]

2018-08-22 15:44:01.209  INFO 8959 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]

2018-08-22 15:44:01.348  INFO 8959 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

2018-08-22 15:44:01.500  INFO 8959 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@64b8f8f4: startup date [Wed Aug 22 15:44:00 BST 2018]; root of context hierarchy

2018-08-22 15:44:01.563  INFO 8959 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)

2018-08-22 15:44:01.564  INFO 8959 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)

2018-08-22 15:44:01.589  INFO 8959 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

2018-08-22 15:44:01.590  INFO 8959 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

2018-08-22 15:44:01.726  INFO 8959 --- [ main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup

2018-08-22 15:44:01.775  INFO 8959 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''

2018-08-22 15:44:01.780  INFO 8959 --- [ main] cessUsertaskServicetaskEventsApplication : Started Activiti7ApiBasicProcessUsertaskServicetaskEventsApplication in 2.288 seconds (JVM running for 2.718)

Ctrl-C out of the application to continue with the configuration below.

Adding Activiti 7 Dependencies to the App

The Spring Boot app has most of the dependencies that we need, except for the Activiti 7 dependencies. So let’s add them. We can use a BOM (Bill-of-Materials) dependency that will bring in all the needed Activiti 7 dependency management configurations, including the correct versions of all dependencies.

Add the following to the pom.xml:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.activiti.cloud.dependencies</groupId>
<artifactId>activiti-cloud-dependencies</artifactId>
<version>7.0.0.Beta3</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

This will import all the dependency management configurations for Activiti 7. Now we just need to add an Activiti 7 dependency that supports running the Activiti process engine, service task implementations (i.e. Cloud Connectors), and event handler implementations (i.e. replacement for process and task listeners). Add the following dependency to the pom.xml:

<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
</dependency>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

This will bring in all the Activiti and Spring dependencies need to run the Activiti 7 process engine embedded in a Spring Boot application. I will also make it possible to compile our Service Task implementations and our process engine event handlers.

We cannot yet run the application with these new dependencies as it will look for process definitions in the resources/processes directory. And if this directory does not exist, then an exception is thrown and the app halts.

Adding the Process Definition to the App

We will now add the process definition XML file we designed in one of the previous articles to the project. Create a new directory called processes under the src/main/resources directory. Then copy the .bpmn20.xml file into this directory. You should see a directory structure like this now:

├── src

│   ├── main

│   │ ├── java

│   │ │   └── org

│   │ │       └── activiti

│   │ │           └── training

│   │ │               └── activiti7apibasicprocessusertaskservicetaskevents

│   │ │                   └── Activiti7ApiBasicProcessUsertaskServicetaskEventsApplication.java

│   │ └── resources

│   │    ├── application.properties

│   │    ├── processes

│   │    │ └── sample-process.bpmn20.xml

│   │    ├── static

│   │    └── templates

This is all we need to do, we can now test to start the app.

Test the Spring Boot App containing Activiti libs and process definition

We can now package and run the app to see that all the Activiti libs are loaded properly and that the process definition is read correctly without errors.

activiti7-api-basic-process-usertask-servicetask-events mbergljung$ mvn clean package

activiti7-api-basic-process-usertask-servicetask-events mbergljung$ java -jar target/activiti7-api-basic-process-usertask-servicetask-events-0.0.1-SNAPSHOT.jar

 .  ____          _ __ _ _

/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

\\/  ___)| |_)| | | | | || (_| |  ) ) ) )

 ' |____| .__|_| |_|_| |_\__, | / / / /

=========|_|==============|___/=/_/_/_/

:: Spring Boot ::        (v2.0.4.RELEASE)

...

2018-08-22 16:08:44.688  INFO 9056 --- [ost-startStop-1] aultActiviti5CompatibilityHandlerFactory : Activiti 5 compatibility handler implementation not found or error during instantiation : org.activiti.compatibility.DefaultActiviti5CompatibilityHandler. Activiti 5 backwards compatibility disabled.

2018-08-22 16:08:44.751  INFO 9056 --- [ost-startStop-1] o.activiti.engine.impl.db.DbSqlSession   : performing create on engine with resource org/activiti/db/create/activiti.h2.create.engine.sql

2018-08-22 16:08:44.864  INFO 9056 --- [ost-startStop-1] o.a.engine.impl.ProcessEngineImpl        : ProcessEngine default created

...

2018-08-22 16:08:47.478  INFO 9056 --- [ main] cessUsertaskServicetaskEventsApplication : Started Activiti7ApiBasicProcessUsertaskServicetaskEventsApplication in 7.213 seconds (JVM running for 7.685)

Adding ReST calls to interact with the process engine

We now got the application running with the Activiti 7 process engine runtime libraries available, so we can create some standard Spring MVC rest calls to interact with the process engine and the available process definition.

Add some users and groups and enable web security

To be able to interact with the Process Runtime API we need to be authenticated with a user that has the role ROLE_ACTIVITI_USER. If we were just calling the Process Runtime API directly from Java code, such as from the class with the main method, then we could set the user context directly before we make the API call. See here for an example. Note. do this only for playing around with the API, not in real production implementations...

We are going to create our own little ReST API so we would like the authentication to work via the Web Browser using Basic Auth. Activiti makes use of Spring Security so we can quite easily do this.

Create a Spring configuration class called  Activiti7ApplicationConfiguration in the src/main/java/org/activiti/training/ activiti7apibasicprocessusertaskservicetaskevents package:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
* Set up some users and groups that we can use when interacting with the process engine API.
* We use the testuser in the process definition so we need to include this user.
*
* We also enable Web security so we can build a simple ReST API that uses the Process Engine Java API. We need
* to be authenticated with a user that has the role ROLE_ACTIVITI_USER to be able to use the API.
*/

@Configuration
@EnableWebSecurity
public class Activiti7ApplicationConfiguration extends WebSecurityConfigurerAdapter {

private Logger logger = LoggerFactory.getLogger(Activiti7ApplicationConfiguration.class);

@Override
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService());
}

@Bean
public UserDetailsService myUserDetailsService() {

InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();

String[][] usersGroupsAndRoles = {
{"mbergljung", "1234", "ROLE_ACTIVITI_USER", "GROUP_activitiTraining"},
{"testuser", "1234", "ROLE_ACTIVITI_USER", "GROUP_activitiTraining"},
{"system", "1234", "ROLE_ACTIVITI_USER"},
{"admin", "1234", "ROLE_ACTIVITI_ADMIN"},
};

for (String[] user : usersGroupsAndRoles) {
List<String> authoritiesStrings = Arrays.asList(Arrays.copyOfRange(user, 2, user.length));
logger.info("> Registering new user: " + user[0] + " with the following Authorities[" + authoritiesStrings + "]");
inMemoryUserDetailsManager.createUser(new User(user[0], passwordEncoder().encode(user[1]),
authoritiesStrings.stream().map(s -> new SimpleGrantedAuthority(s)).collect(Collectors.toList())));
}


return inMemoryUserDetailsManager;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Set up some users and groups that we can use when interacting with the process engine API. We use the testuser in the process definition (assigned to the User Task 1) so we need to include this user. We also enable Web security so we can build a simple ReST API that uses the Process Engine Java API.

Adding a ReST call to list Process Definitions

First, let’s add a ReST call that lists the deployed process definitions. Create a new sub-package called rest in the org/activiti/training/activiti7apibasicprocessusertaskservicetaskevents package. Then add a new Spring MVC controller called ProcessDefinitionsController in this new package as follows:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest;

import org.activiti.api.process.model.ProcessDefinition;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* ReST controller to interact with deployed process definitions
*
*/

@RestController
public class ProcessDefinitionsController {
private Logger logger = LoggerFactory.getLogger(ProcessDefinitionsController.class);

@Autowired
private ProcessRuntime processRuntime;

@GetMapping("/process-definitions")
public List<ProcessDefinition> getProcessDefinitions() {
Page<ProcessDefinition> processDefinitionPage = processRuntime.processDefinitions(Pageable.of(0, 10));
logger.info("> Available Process definitions: " + processDefinitionPage.getTotalItems());

for (ProcessDefinition pd : processDefinitionPage.getContent()) {
logger.info("\t > Process definition: " + pd);
}

return processDefinitionPage.getContent();
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Start by injecting the ProcessRuntime Spring bean so we can use the Process Runtime API, which has the following methods:

public interface ProcessRuntime {
ProcessRuntimeConfiguration configuration();
ProcessDefinition processDefinition(String var1);
Page<ProcessDefinition> processDefinitions(Pageable var1);
Page<ProcessDefinition> processDefinitions(Pageable var1, GetProcessDefinitionsPayload var2);
ProcessInstance start(StartProcessPayload var1);
Page<ProcessInstance> processInstances(Pageable var1);
Page<ProcessInstance> processInstances(Pageable var1, GetProcessInstancesPayload var2);
ProcessInstance processInstance(String var1);
ProcessInstance suspend(SuspendProcessPayload var1);
ProcessInstance resume(ResumeProcessPayload var1);
ProcessInstance delete(DeleteProcessPayload var1);
void signal(SignalPayload var1);
ProcessDefinitionMeta processDefinitionMeta(String var1);
ProcessInstanceMeta processInstanceMeta(String var1);
List<VariableInstance> variables(GetVariablesPayload var1);
void removeVariables(RemoveProcessVariablesPayload var1);
void setVariables(SetProcessVariablesPayload var1);
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In this case we just need the processDefinitions() method. The /process-definitions URL path is used for this ReST GET call.

Now, package and run the application as described before. When starting the application you should see logs that indicate that everything has been implemented correctly:

...

2018-08-28 15:12:49.742  INFO 21982 --- [ost-startStop-1] .a.t.a.Activiti7ApplicationConfiguration : > Registering new user: mbergljung with the following Authorities[[ROLE_ACTIVITI_USER, GROUP_activitiTraining]]

2018-08-28 15:12:49.869  INFO 21982 --- [ost-startStop-1] .a.t.a.Activiti7ApplicationConfiguration : > Registering new user: testuser with the following Authorities[[ROLE_ACTIVITI_USER, GROUP_activitiTraining]]

2018-08-28 15:12:49.994  INFO 21982 --- [ost-startStop-1] .a.t.a.Activiti7ApplicationConfiguration : > Registering new user: system with the following Authorities[[ROLE_ACTIVITI_USER]]

2018-08-28 15:12:50.113  INFO 21982 --- [ost-startStop-1] .a.t.a.Activiti7ApplicationConfiguration : > Registering new user: admin with the following Authorities[[ROLE_ACTIVITI_ADMIN]]

org.springframework.security.web.authentication.www.BasicAuthenticationFilter@6fdbe764,

...

2018-08-28 15:12:53.119  INFO 21982 --- [  main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/process-definitions],methods=[GET]}" onto public java.util.List<org.activiti.api.process.model.ProcessDefinition> org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest.ProcessDefinitionsController.getProcessDefinitions()

...

Go to a browser and hit http://localhost:8080/process-definitions, you should be presented with a login dialog. Type in a username and password for a user that has the role ROLE_ACTIVITI_USER, such as testuser/1234. You should then get a response looking something like this:

[
{
id: "c68315b2-fa2a-11e8-9c34-acde48001122",
name: "Sample Process",
version: 1,
key: "sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d"
}
]‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In the logs you should see the following:

2018-08-28 15:13:09.678  INFO 21982 --- [nio-8080-exec-1] o.a.t.a.r.ProcessDefinitionsController   : > Available Process definitions: 1

2018-08-28 15:13:09.678  INFO 21982 --- [nio-8080-exec-1] o.a.t.a.r.ProcessDefinitionsController   : > Process definition: ProcessDefinition{id='c68315b2-fa2a-11e8-9c34-acde48001122', name='Sample Process', key='sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d', description='null', formKey='null', version=1}

Adding a ReST call to start a Process Instance

We now know we got one process definition that we can use to start a process instance. Let’s create a ReST call that can be used to do this. It will take one parameter with the process definition key.

In the org/activiti/training/activiti7apibasicprocessusertaskservicetaskevents/rest package add a new Spring MVC controller called ProcessStartController as follows:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest;

import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.builders.ProcessPayloadBuilder;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class ProcessStartController {
private Logger logger = LoggerFactory.getLogger(ProcessStartController.class);

@Autowired
private ProcessRuntime processRuntime;

@RequestMapping("/start-process")
public ProcessInstance startProcess(
@RequestParam(value="processDefinitionKey", defaultValue="SampleProcess") String processDefinitionKey) {
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder
.start()
.withProcessDefinitionKey(processDefinitionKey)
.withProcessInstanceName("Sample Process: " + new Date())
.withVariable("someProcessVar", "someProcVarValue")
.build());
logger.info(">>> Created Process Instance: " + processInstance);

return processInstance;
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Start by injecting the ProcessRuntime Spring bean so we can use the Process Runtime API, which has the start() method that we need. The /start-process?processDefinitionKey={processDefinitionKey} URL is used for this ReST call.

Now, package and run the application as described before. When starting the application you should see logs that indicate that everything has been implemented correctly:

...

2018-08-29 10:34:34.312  INFO 27844 --- [  main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/start-process]}" onto public org.activiti.runtime.api.model.ProcessInstance org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest.ProcessStartController.startProcess(java.lang.String)

...

Go to a browser and type in the following URL (use the processDefinitionKey that matches your deployed process definition, you can see what key by calling /process-definitions as done previously): http://localhost:8080/start-process?processDefinitionKey=sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c53..., you should be presented with a login dialog (or NOT if credentials are cached). Type in a username and password for a user that has the role ROLE_ACTIVITI_USER. You should then get a response looking something like this:

{
id: "b0a28a43-fa2b-11e8-9c34-acde48001122",
name: "Sample Process: Fri Dec 07 14:23:38 GMT 2018",
processDefinitionId: "c68315b2-fa2a-11e8-9c34-acde48001122",
processDefinitionKey: "sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d",
startDate: "2018-12-07T14:23:38.878+0000",
status: "RUNNING"
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In the logs you should see the following:

2018-08-29 10:35:18.379  INFO 27844 --- [nio-8080-exec-4] o.a.t.a.rest.ProcessStartController      : >>> Created Process Instance: ProcessInstance{id='b0a28a43-fa2b-11e8-9c34-acde48001122', name='Sample Process: Fri Dec 07 14:23:38 GMT 2018', description='null', processDefinitionId='c68315b2-fa2a-11e8-9c34-acde48001122', processDefinitionKey='sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d', initiator='null', startDate=Fri Dec 07 14:23:38 GMT 2018, businessKey='null', status=RUNNING}

Adding a ReST call to list Process Instances

It is useful to be able to list active process instances. And also be able to get more metadata about a process instance, such as where in the execution flow it is. Let’s create a couple of ReST calls for this that can come in handy.

In the org/activiti/training/activiti7apibasicprocessusertaskservicetaskevents/rest package add a new Spring MVC controller called ProcessInstanceController as follows:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest;

import org.activiti.api.process.model.ProcessInstance;
import org.activiti.api.process.model.ProcessInstanceMeta;
import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.runtime.shared.query.Pageable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
* ReST controller to get some info about a process instance
*
*/

@RestController
public class ProcessInstanceController {
private Logger logger = LoggerFactory.getLogger(ProcessInstanceController.class);

@Autowired
private ProcessRuntime processRuntime;

@GetMapping("/process-instances")
public List<ProcessInstance> getProcessInstances() {
List<ProcessInstance> processInstances =
processRuntime.processInstances(Pageable.of(0, 10)).getContent();

return processInstances;
}

@GetMapping("/process-instance-meta")
public ProcessInstanceMeta getProcessInstanceMeta(@RequestParam(value="processInstanceId") String processInstanceId) {
ProcessInstanceMeta processInstanceMeta = processRuntime.processInstanceMeta(processInstanceId);

return processInstanceMeta;
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Start by injecting the ProcessRuntime Spring bean so we can use the Process Runtime API, which has the processInstances() and processInstanceMeta() methods that we need. The /process-instances URL is used for the first ReST call that just returns the first 10 active process instances. The second URL /process-instance-meta?processInstanceId={processInstanceId} gives information about the process instance, such at what activiti(es) it is currently waiting on.

Now, package and run the application as described before. When starting the application you should see logs that indicate that everything has been implemented correctly:

...

2018-08-30 10:35:46.192  INFO 36251 --- [  main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/process-instance-meta],methods=[GET]}" onto public org.activiti.runtime.api.model.ProcessInstanceMeta org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest.ProcessInstanceController.getProcessInstanceMeta(java.lang.String)

2018-08-30 10:35:46.193  INFO 36251 --- [  main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/process-instances],methods=[GET]}" onto public java.util.List<org.activiti.runtime.api.model.ProcessInstance> org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest.ProcessInstanceController.getProcessInstances()

...

As we are running an in-memory database the process instance we previously started will be gone after a restart, create a new one with http://localhost:8080/start-process?processDefinitionKey=sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c53....

Now, to list process instances hit the http://localhost:8080/process-instances URL. You should get a response looking something like this:

[
{
id: "b0a28a43-fa2b-11e8-9c34-acde48001122",
name: "Sample Process: Fri Dec 07 14:23:38 GMT 2018",
processDefinitionId: "c68315b2-fa2a-11e8-9c34-acde48001122",
processDefinitionKey: "sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c533a6d",
startDate: "2018-12-07T14:23:38.878+0000",
status: "RUNNING"
}
]‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

We can then query this process instance for more information with the other ReST call. Type in the following URL (use the processInstanceId that was returned in the call above): http://localhost:8080/process-instance-meta?processInstanceId=b0a28a43-fa2b-11e8-9c34-acde48001122. You should then get a response looking something like this:

{
processInstanceId: "b0a28a43-fa2b-11e8-9c34-acde48001122",
activeActivitiesIds:
[
"UserTask_0b6cp1l"
]
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The first activity in our process definition is the User Task 1, so that’s where in the process definition the execution is. The process engine is waiting for the following User Task to be completed:

<bpmn2:userTask id="UserTask_0b6cp1l" name="User Task 1" activiti:assignee="testuser">
<bpmn2:incoming>SequenceFlow_0qdq7ff</bpmn2:incoming>
<bpmn2:outgoing>SequenceFlow_1sc9dgy</bpmn2:outgoing>
</bpmn2:userTask>‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Being able to list and inspect process instances is useful when you get stuck and don’t know exactly where a process instance is waiting in the process definition.

Adding a ReST call to list available User Tasks

With the process running we should be able to list available tasks and then see the User task that is the first activiti in the process definition.

In the org/activiti/training/activiti7apibasicprocessusertaskservicetaskevents/rest package add a new Spring MVC controller called TaskManagementController as follows:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest;

import org.activiti.api.runtime.shared.query.Page;
import org.activiti.api.runtime.shared.query.Pageable;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.model.builders.TaskPayloadBuilder;
import org.activiti.api.task.runtime.TaskAdminRuntime;
import org.activiti.api.task.runtime.TaskRuntime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class TaskManagementController {
private Logger logger = LoggerFactory.getLogger(TaskManagementController.class);

@Autowired
private TaskRuntime taskRuntime;

@GetMapping("/my-tasks")
public List<Task> getMyTasks() {
Page<Task> tasks = taskRuntime.tasks(Pageable.of(0, 10));
logger.info("> My Available Tasks: " + tasks.getTotalItems());

for (Task task : tasks.getContent()) {
logger.info("\t> My User Task: " + task);
}

return tasks.getContent();
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Start by injecting the TaskRuntime Spring bean so we can use the Task Runtime API. This API will work with tasks related to the current user. So when we call the tasks() method it will return the tasks assigned to the currently logged in user. The API has the following methods:

public interface TaskRuntime {
TaskRuntimeConfiguration configuration();
Task task(String var1);
Page<Task> tasks(Pageable var1);
Page<Task> tasks(Pageable var1, GetTasksPayload var2);
Task create(CreateTaskPayload var1);
Task claim(ClaimTaskPayload var1);
Task release(ReleaseTaskPayload var1);
Task complete(CompleteTaskPayload var1);
Task update(UpdateTaskPayload var1);
Task delete(DeleteTaskPayload var1);
List<VariableInstance> variables(GetTaskVariablesPayload var1);
void setVariables(SetTaskVariablesPayload var1);
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In this case we just need the tasks() method. The /my-tasks URL path is used for this ReST GET call.

We can also add a ReST call to use when we want to see all tasks assigned in all active process instances. Can be useful for support and management purposes, such as when you want to reassign a task or complete a task on behalf of somebody. This call will require admin credentials (i.e. we need to be logged in as a user with ROLE_ACTIVITI_ADMIN). We also need to use a different runtime API called TaskAdminRuntime. Here is the method:

...
@Autowired
private TaskAdminRuntime taskAdminRuntime;
...
@GetMapping("/all-tasks")
public List<Task> getAllTasks() {
Page<Task> tasks = taskAdminRuntime.tasks(Pageable.of(0, 10));
logger.info("> All Available Tasks: " + tasks.getTotalItems());

for (Task task : tasks.getContent()) {
logger.info("\t> User Task: " + task);
}

return tasks.getContent();
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Now, package and run the application as described before. When starting the application you should see logs that indicate that everything has been implemented correctly:

...

2018-08-30 09:09:47.757  INFO 36063 --- [  main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/my-tasks],methods=[GET]}" onto public java.util.List<org.activiti.runtime.api.model.Task> org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest.TaskManagementController.getMyTasks()

2018-08-30 09:09:47.757  INFO 36063 --- [  main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/all-tasks],methods=[GET]}" onto public java.util.List<org.activiti.runtime.api.model.Task> org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest.TaskManagementController.getAllTasks()

...

As we are running an in-memory database the process instance we previously started will be gone after a restart, create a new one with http://localhost:8080/start-process?processDefinitionKey=sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c53....

I started the process instance logged in as user mbergljung. Because the user task is assigned to user testuser it will not show up when we call taskRuntime.tasks(). We will have to logout and login again as testuser before continuing with the below ReST call (easiest way is to clear browser cache before next ReST call).

Then type in the following URL: http://localhost:8080/my-tasks, you should then get a response looking something like this:

[
{
id: "b0a60cb6-fa2b-11e8-9c34-acde48001122",
name: "User Task 1",
status: "ASSIGNED",
assignee: "testuser",
createdDate: "2018-12-07T14:23:38.896+0000",
priority: 50,
processDefinitionId: "c68315b2-fa2a-11e8-9c34-acde48001122",
processInstanceId: "b0a28a43-fa2b-11e8-9c34-acde48001122"
}
]‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

In the logs you should see the following:

2018-08-30 09:50:09.753  INFO 36063 --- [nio-8080-exec-9] o.a.t.a.rest.TaskManagementController    : > My Available Tasks: 1

2018-08-30 09:50:09.754  INFO 36063 --- [nio-8080-exec-9] o.a.t.a.rest.TaskManagementController    : > My User Task: TaskImpl{id='b0a60cb6-fa2b-11e8-9c34-acde48001122', owner='null', assignee='testuser', name='User Task 1', description='null', createdDate=Fri Dec 07 14:23:38 GMT 2018, claimedDate=null, dueDate=null, priority=50, processDefinitionId='c68315b2-fa2a-11e8-9c34-acde48001122', processInstanceId='b0a28a43-fa2b-11e8-9c34-acde48001122', parentTaskId='null', formKey='null', status=ASSIGNED}

Now, logout by clearing the browser cache and then hit the http://localhost:8080/all-tasks URL. When asked to login use the admin/1234 credentials. As a response you should see the same user task.

Adding a ReST call to complete a User Task

We are now at a stage when we should be able to implement a ReST call that can be used to complete the user task that is assigned to testuser and that we just listed.

In the same controller we just used, called TaskManagementController, implement the following ReST call:  

...
@RequestMapping("/complete-task")
public String completeTask(@RequestParam(value="taskId") String taskId) {
taskRuntime.complete(TaskPayloadBuilder.complete()
.withTaskId(taskId).build());
logger.info(">>> Completed Task: " + taskId);

return "Completed Task: " + taskId;
}
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Here we use the complete() method of the TaskRuntime API. We need to be logged in with the user that is assigned the task (so testuser) to complete it. The /complete-task?taskId={taskId} URL path is used for this ReST GET call.

Now, package and run the application as described before. When starting the application you should see logs that indicate that everything has been implemented correctly:

...

2018-08-30 10:07:36.214  INFO 36167 --- [  main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/complete-task]}" onto public java.lang.String org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.rest.TaskManagementController.completeTask(java.lang.String)

...

As we are running an in-memory database the process instance we previously started will be gone after a restart, create a new one with http://localhost:8080/start-process?processDefinitionKey=sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c53....

I started the process instance logged in as user mbergljung. Because the user task is assigned to user testuser it will not show up when we call taskRuntime.tasks() and I will not be able to complete it.

We will have to logout and login again as testuser before continuing with the below ReST call (easiest way is to clear browser cache before next ReST calls).

Then type in the following URL: http://localhost:8080/my-tasks, you should then get a response looking something like this:

[
{
id: "b0a60cb6-fa2b-11e8-9c34-acde48001122",
name: "User Task 1",
status: "ASSIGNED",
assignee: "testuser",
createdDate: "2018-12-07T14:23:38.896+0000",
priority: 50,
processDefinitionId: "c68315b2-fa2a-11e8-9c34-acde48001122",
processInstanceId: "b0a28a43-fa2b-11e8-9c34-acde48001122"
}
]
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Make a note of the Task ID and then use it in the http://localhost:8080/complete-task?taskId=b0a60cb6-fa2b-11e8-9c34-acde48001122 call to complete the task. This will have the process instance transition into the next activiti, which in our case is a service task.

As we have not yet implemented the service task we will see the following exception in the logs and in the browser:

org.activiti.engine.ActivitiException: No bean named 'serviceTask1Impl' available

So let’s fix the service task implementation.

Implementing Service Tasks and Listeners

Service tasks and listeners are implemented differently in Activiti 7 than in previous versions.

Implementing the Service Task Spring Bean

The service task is the last activiti in our process definition. Let’s implement it so we can complete the process instance.

What we need to do is create a Spring Bean with the name serviceTask1Impl that will represent the implementation of the Service Task. The Spring bean need to be of the type interface  org.activiti.runtime.api.connector.Connector. This new Connector interface is the natural evolution of Java Delegates, and Activiti 7 Core will try to reuse your Java Delegates by wrapping them up inside a Connector implementation.

Create a new sub-package called connectors in the org/activiti/training/activiti7apibasicprocessusertaskservicetaskevents package. Then add a new Spring bean named ServiceTask1Connector in this new package as follows:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.connectors;

import org.activiti.api.process.model.IntegrationContext;
import org.activiti.api.process.runtime.connector.Connector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service(value = "serviceTask1Impl")
public class ServiceTask1Connector implements Connector {
private Logger logger = LoggerFactory.getLogger(ServiceTask1Connector.class);

public IntegrationContext execute(IntegrationContext integrationContext) {
logger.info("Some service task logic... [processInstanceId=" + integrationContext.getProcessInstanceId() + "]");

return integrationContext;
}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The connector is wired up automatically to the ProcessRuntime using the Bean name, in this example “serviceTask1Impl”. This bean name is picked up from the implementation property of the serviceTask element inside our process definition:

<bpmn2:serviceTask id="ServiceTask_1wg38me" name="Service Task 1" implementation="serviceTask1Impl">‍‍‍‍‍‍‍‍‍

Connectors receive an IntegrationContext with process instance information and the process variables and return a modified IntegrationContext with the results that needs to be mapped back to process variables.

Now, package and run the application as described before. As we are running an in-memory database the process instance we previously started will be gone after a restart, create a new one with http://localhost:8080/start-process?processDefinitionKey=sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c53.... Login as user testuser so we can keep the same user throughout.

Then type in the following URL: http://localhost:8080/my-tasks, you should then get a response looking something like this:

[
{
id: "79ce7445-fc4b-11e8-95e6-acde48001122",
name: "User Task 1",
status: "ASSIGNED",
assignee: "testuser",
createdDate: "2018-12-10T07:16:13.109+0000",
priority: 50,
processDefinitionId: "723ee071-fc4b-11e8-95e6-acde48001122",
processInstanceId: "79cb3ff2-fc4b-11e8-95e6-acde48001122"
}
]‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Make a note of the Task ID and then use it in the http://localhost:8080/complete-task?taskId=79ce7445-fc4b-11e8-95e6-acde48001122 call to complete the task. This will have the process instance transition into the next activiti, which in our case is a service task. You should see the following in the logs:

2018-12-10 07:17:05.090  INFO 39199 --- [nio-8080-exec-7] o.a.t.a.c.ServiceTask1Connector          : Some service task logic... [processInstanceId=79cb3ff2-fc4b-11e8-95e6-acde48001122]

It would be good now to check if the process instance has completed. We can do that with the http://localhost:8080/process-instances call we developed earlier on. It should return an empty list.

Implementing Process Listeners and Task Listeners

Process Listeners and Task Listeners were traditionally in Activiti implemented with proprietary extensions. These extensions also meant that the code would run synchronously with the process execution. Which isn’t good in a Cloud deployment. In Activiti 7 the process engine emits events that we can listen to and subscribe to in an asynchronous way.

Process Listeners are created by implementing the interface  org.activiti.api.process.runtime.events.listener.ProcessRuntimeEventListener.

Create a new sub-package called listeners in the org/activiti/training/activiti7apibasicprocessusertaskservicetaskevents package. Then add a new Spring bean named MyProcessEventListener in this new package as follows:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.listeners;

import org.activiti.api.model.shared.event.RuntimeEvent;
import org.activiti.api.model.shared.event.VariableCreatedEvent;
import org.activiti.api.process.model.events.SequenceFlowTakenEvent;
import org.activiti.api.process.runtime.events.*;
import org.activiti.api.process.runtime.events.listener.ProcessRuntimeEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class MyProcessEventListener implements ProcessRuntimeEventListener {
private Logger logger = LoggerFactory.getLogger(MyProcessEventListener.class);

@Override
public void onEvent(RuntimeEvent runtimeEvent) {

if (runtimeEvent instanceof ProcessStartedEvent)
logger.info("Do something, process is started: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCompletedEvent)
logger.info("Do something, process is completed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCancelledEvent)
logger.info("Do something, process is cancelled: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessSuspendedEvent)
logger.info("Do something, process is suspended: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessResumedEvent)
logger.info("Do something, process is resumed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof ProcessCreatedEvent)
logger.info("Do something, process is created: " + runtimeEvent.toString());
else if (runtimeEvent instanceof SequenceFlowTakenEvent)
logger.info("Do something, sequence flow is taken: " + runtimeEvent.toString());
else if (runtimeEvent instanceof VariableCreatedEvent)
logger.info("Do something, variable was created: " + runtimeEvent.toString());
else
logger.info("Unknown event: " + runtimeEvent.toString());

}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

The process listener is wired up automatically to the ProcessRuntime by implementing the ProcessRuntimeEventListener interface. Listeners receive a RuntimeEvent with all the information about the event. And we can look at subclasses to figure out what the event is about, such as ProcessCompletedEvent.

In the same way we can create task listeners by implementing the org.activiti.api.task.runtime.events.listener.TaskRuntimeEventListener interface.

Add a new Spring bean named MyTaskEventListener in this new package as follows:

package org.activiti.training.activiti7apibasicprocessusertaskservicetaskevents.listeners;

import org.activiti.api.model.shared.event.RuntimeEvent;
import org.activiti.api.task.model.Task;
import org.activiti.api.task.runtime.events.*;
import org.activiti.api.task.runtime.events.listener.TaskRuntimeEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class MyTaskEventListener implements TaskRuntimeEventListener {
private Logger logger = LoggerFactory.getLogger(MyTaskEventListener.class);

@Override
public void onEvent(RuntimeEvent runtimeEvent) {

if (runtimeEvent instanceof TaskActivatedEvent)
logger.info("Do something, task is activated: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskAssignedEvent) {
TaskAssignedEvent taskEvent = (TaskAssignedEvent)runtimeEvent;
Task task = taskEvent.getEntity();
logger.info("Do something, task is assigned: " + task.toString());
} else if (runtimeEvent instanceof TaskCancelledEvent)
logger.info("Do something, task is cancelled: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskCompletedEvent)
logger.info("Do something, task is completed: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskCreatedEvent)
logger.info("Do something, task is created: " + runtimeEvent.toString());
else if (runtimeEvent instanceof TaskSuspendedEvent)
logger.info("Do something, task is suspended: " + runtimeEvent.toString());
else
logger.info("Unknown event: " + runtimeEvent.toString());

}
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Now, package and run the application as described before. Create a new Process Instance with http://localhost:8080/start-process?processDefinitionKey=sampleproc-e9b76ff9-6f70-42c9-8dee-f6116c53....

Already at this point we should see a lot of event logging:

2018-12-10 07:22:24.319  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.l.MyProcessEventListener         : Do something, process is created: org.activiti.runtime.api.event.impl.ProcessCreatedEventImpl@3b555ed0

2018-12-10 07:22:24.321  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.l.MyProcessEventListener         : Do something, process is started: org.activiti.runtime.api.event.impl.ProcessStartedEventImpl@45c34e86

2018-12-10 07:22:24.322  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.l.MyProcessEventListener         : Unknown event: org.activiti.api.runtime.event.impl.BPMNActivityStartedEventImpl@70e78827

2018-12-10 07:22:24.323  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.l.MyProcessEventListener         : Unknown event: org.activiti.api.runtime.event.impl.BPMNActivityCompletedEventImpl@2ea9f9f7

2018-12-10 07:22:24.325  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.l.MyProcessEventListener         : Do something, sequence flow is taken: org.activiti.api.runtime.event.impl.SequenceFlowTakenImpl@3f1b812c

2018-12-10 07:22:24.325  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.l.MyProcessEventListener         : Unknown event: org.activiti.api.runtime.event.impl.BPMNActivityStartedEventImpl@60734f37

2018-12-10 07:22:24.328  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.listeners.MyTaskEventListener    : Do something, task is created: org.activiti.runtime.api.event.impl.TaskCreatedEventImpl@2c9945ae

2018-12-10 07:22:24.329  INFO 39199 --- [nio-8080-exec-4] o.a.t.a.listeners.MyTaskEventListener    : Do something, task is assigned: TaskImpl{id='5711101b-fc4c-11e8-95e6-acde48001122', owner='null', assignee='testuser', name='User Task 1', description='null', createdDate=Mon Dec 10 07:22:24 GMT 2018, claimedDate=null, dueDate=null, priority=50, processDefinitionId='723ee071-fc4b-11e8-95e6-acde48001122', processInstanceId='570ffea8-fc4c-11e8-95e6-acde48001122', parentTaskId='null', formKey='null', status=ASSIGNED}

2 Comments