Skip navigation
All Places > Alfresco Process Services & Activiti (BPM) > Blog > 2016 > November > 15

Table of Contents                   

 

Introduction

Service tasks are one of the fundamental building blocks of any process. They allow you to implement complex business logic, make calculations, talk to external systems and services, and more. In Activiti there are a number of ways in which a service task can be implemented:

 

  1. As a POJO, which is called a Java Delegate
  2. As a Spring Bean Java Delegate
  3. As a Spring Bean method (what this article covers)

 

What implementation approach you choose depend on the use-case. If you don’t need to use any Spring beans in your implementation then use a POJO Java Delegate. If your service task implementation needs to use, for example out-of-the-box Spring beans, then use the Spring Bean Java Delegate. These two approaches uses a “one operation per service task” implementation. If you need your implementation to support multiple operations, then go with the Spring bean method implementation.

 

There is also a runtime twist to this, most likely there will be multiple process instances calling the same service task implementation. And the same service task implementation might be called from multiple service tasks in a process. The implementation behaves differently depending on approach:

 

  1. POJO Java Delegate - inside a process instance you have one class instance per service task, between process instances you share class instances per service task
  2. Spring Bean Java Delegate - same Spring bean (i.e. class instance) is used within a process instance and between process instances
  3. Spring Bean method - same Spring bean (i.e. class instance) is used within a process instance and between process instances but there is no field injection and all data is passed in via method params, so thread-safe

 

What this basically means is that if you use a third party class inside a Java Delegate, then it needs to be thread safe as it can be called by multiple concurrent threads. If you use a Spring bean Java Delegate approach then the same thing applies, if you inject beans they need to all be thread safe. With the Spring bean approach you can also change the bean instantiation scope to be PROTOTYPE, which means an instance will be created per service task.

 

Or you can use the Spring Bean Method approach, which this article covers, and which solves a lot of concurrency issues.

Source Code

Source code for the Activiti Developer Series can be found here.

Prerequisites

Before starting with your service task implementation make sure to set up a proper Activiti Extension project.

Implementing a Hello World Spring Bean Method

Let’s start the usual way with a Hello World Spring Bean Method. This differs from the other approaches as we are not implementing one specific execute method for a Java Delegate. Here we are implementing as many methods as we want, serving as many service tasks as we need from the same Spring Bean implementation.

Coding the Spring Bean class

Here is the implementation of the class:

 

package com.activiti.extension.bean.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;


@Service
public class HelloWorldService {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

  public String greeting() {
      return "Hello World from Service!";
  }

  public void customGreeting(String text) {
      logger.info("[Java object=" + this + "]");
      logger.info("Hello World: " + text);
  }
}

 

So every method we provide inside the Spring Bean class implementation is a potential Service Task implementation. In this case we are going to use the customGreeting method as the implementation and pass in different texts depending on what service task that is calling the method.

 

This is completely thread safe as we are not using any class members, just passing in data via a method parameter. However, the logging library needs to be thread safe as this method can be called from multiple concurrent threads, serving different service tasks.

Testing the method

Now to test the Spring Bean method as a service task implementation create a process model looking like this:

 

 

And the Service Task is connected to the Spring Bean method implementation via the Expression property:

 

 

The first service task has the following Expression:

 

${helloWorldService.customGreeting('Service Task 1')}

 

And the second task has the following Expression:

 

${helloWorldService.customGreeting('Service Task 2')}

 

In BPMN 2.0 XML it will look like this:

 

<serviceTask id="serviceTask1"
             name="Service Task 1 (Spring Bean method customGreeting(x))"
             activiti:expression="${helloWorldService.customGreeting('Service Task 1')}">

 

When we run this process it will print similar logs to the following:

 

03:11:10,919 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Java object=com.activiti.extension.bean.service.HelloWorldService@4ed3dbd3]

03:11:10,919 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Service Task 1

03:11:10,920 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - [Java object=com.activiti.extension.bean.service.HelloWorldService@4ed3dbd3]

03:11:10,920 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.service.HelloWorldService  - Hello World: Service Task 2

 

We can see here that all the context for the method is passed in as a parameter. In this case it’s only a String of text, but could be any parameters you like. So using the same Spring bean works fine as the logging library is thread safe.

 

Now, what about process information and process variables? How do I get to them? That’s easy, just add an extra parameter of type DelegateExecution:

 

package com.activiti.extension.bean.service;

import org.activiti.engine.delegate.DelegateExecution;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class HelloWorldService {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldService.class);

  public String greeting() {
      return "Hello World from Service!";
  }

  public void customGreeting(DelegateExecution execution, String text) {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Java Delegate=" + this + "]");

      logger.info("Hello World: " + text);
  }
}

 

And update the Expression as follows:

 

${helloWorldService.customGreeting(execution, 'Service Task 1')}

Using Process Variables in the Spring Bean Method

In a Spring Bean Method we access process variables in the same way as in a POJO Java Delegate, so read its docs.

Returning data from the Spring Bean Method

There are probably many situations where we would want to return some data from the Spring Bean method and use it in the process. We can achieve this by defining return values for the service task. First update the customGreeting method to return a String:

 

public String customGreeting(DelegateExecution execution, String text) {
  logger.info("[Process=" + execution.getProcessInstanceId() + "][Java Delegate=" + this + "]");
  logger.info("Hello World: " + text);

  return "Something back from service!";
}

 

Now, to return this back to the process instance as a process variable we need to do the following property configuration for each service task:

 

 

Basically we need to specify the process variable name as the Result variable name property.

 

In BPMN 2.0 XML it will look like this:

 

<serviceTask id="serviceTask1"
             name="Service Task 1 (Spring Bean method customGreeting(x))"
             activiti:expression="${helloWorldService.customGreeting(execution, 'Service Task 1')}"
             activiti:resultVariableName="greetingFromService">

 

You could now use the REST API to list the variables available for the process instance after the Service Tasks have been executed, we should see the greetingFromService variable:

 

The URL path parameter 122501 in the above example is the process instance ID.

Using Field Injection in the Spring Bean Method

Not available when using Spring Bean Methods.

Injecting Beans into the backing Spring Bean

When we use Spring we most likely want to use other Spring Beans in our Java Delegate implementation. This can be Spring beans that we create and out-of-the-box Spring beans.

 

This works the same way as for Spring Bean Java Delegates, see docs.

Calling Spring Bean Methods Asynchronously

See the POJO Java Delegate docs.

Table of Contents                   

 

Introduction

Service tasks are one of the fundamental building blocks of any process. They allow you to implement complex business logic, make calculations, talk to external systems and services, and more. In Activiti there are a number of ways in which a service task can be implemented:

 

  1. As a POJO, which is called a Java Delegate
  2. As a Spring Bean Java Delegate (what this article covers)
  3. As a Spring Bean method

 

What implementation approach you choose depend on the use-case. If you don’t need to use any Spring beans in your implementation then use a POJO Java Delegate. If your service task implementation needs to use, for example out-of-the-box Spring beans, then use the Spring Bean Java Delegate. These two approaches uses a “one operation per service task” implementation. If you need your implementation to support multiple operations, then go with the Spring bean method implementation.

 

There is also a runtime twist to this, most likely there will be multiple process instances calling the same service task implementation. And the same service task implementation might be called from multiple service tasks in a process. The implementation behaves differently depending on approach:

 

  1. POJO Java Delegate - inside a process instance you have one class instance per service task, between process instances you share class instances per service task
  2. Spring Bean Java Delegate - same Spring bean (i.e. class instance) is used within a process instance and between process instances
  3. Spring Bean method - same Spring bean (i.e. class instance) is used within a process instance and between process instances but there is no field injection and all data is passed in via method params, so thread-safe

 

What this basically means is that if you use a third party class inside a Java Delegate, then it needs to be thread safe as it can be called by multiple concurrent threads. If you use a Spring bean approach then the same thing applies, if you inject beans they need to all be thread safe. With the Spring bean approach you can also change the bean instantiation scope to be PROTOTYPE, which means an instance will be created per service task.

 

This article cover the second approach - Spring Bean Java Delegate.

Source Code

Source code for the Activiti Developer Series can be found here.

Prerequisites

Before starting with your service task implementation make sure to set up a proper Activiti Extension project.

Implementing a Hello World Spring Bean Java Delegate

This is pretty much the same thing as implementing a POJO Java Delegate, you just “Springify” it a bit so it can act as a Spring Bean and wire in other Spring Beans. Let’s start the usual way with a Hello World Spring Bean Java Delegate. However, we are going to take the opportunity to check process IDs and object instance IDs while we are at it, so the log message from the Spring Bean Java Delegate will be a little bit different than the usual “Hello World”.

Coding the Java class

Here is the implementation of the Spring Bean Java Delegate class:

 

package com.activiti.extension.bean;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component("helloWorld")
public class HelloWorldSpringJavaDelegate implements JavaDelegate {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringJavaDelegate.class);

  @Override
  public void execute(DelegateExecution execution) throws Exception {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Spring Java Delegate=" + this + "]");
  }
}

 

There are two things here that differentiate a Spring Bean Java Delegate implementation from a POJO Java Delegate implementation. The first is that it is annotated with org.springframework.stereotype.Component, setting it up to be discovered and  registered as a Bean in the Spring application context. The second thing, which is very important, is that the class needs to be defined in the com.activiti.extension.bean package, or any sub-package, for the Spring component scanning to find it.

 

Note here that the Spring Bean will have the name helloWorld, as specified via the @Component annotation. If we don’t specify a name then the classname will be used with first letter in lower-case.

 

The rest of the implementation is the same as for the POJO Java Delegate, so read through the implementation section for it.

Testing the Spring Bean Java Delegate

Now to test the Java Delegate implementation create a process model looking like this:

 

 

And the Service Task is connected to the Spring Bean Java Delegate implementation via the Delegate expression property:

 

 

In BPMN 2.0 XML it will look like this:

 

<serviceTask id="sid-7C83EB30-8E02-400B-BEEF-CAE34BFB6FFD"
             name="Service Task 1 (Spring Bean Java Delegate)"
             activiti:delegateExpression="${helloWorld}">

 

Note here that instead of using activiti:class we use activiti:delegateExpression to tell Activiti about the Spring Bean name. An activiti:delegateExpression can be used when the expression resolves to a Java object instance that implements the JavaDelegate interface.

 

When we run this process it will print similar logs to the following:

 

02:43:54,236 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102534][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

02:43:54,238 [http-nio-8080-exec-9] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102534][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

 

We can immediately see the difference from when using POJO Java Delegates, there is only one instance of the Spring Bean used by both service tasks. This is the standard way the Spring container works, it creates Singletons. If we let the process instance stay at the User Task and start another process, then we will see logs such as follows for the second process instance:

 

02:46:20,065 [http-nio-8080-exec-2] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102542][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

02:46:20,070 [http-nio-8080-exec-2] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102542][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@3e39eeb0]

 

And we can see that the initial Spring Bean instance is used also by the second process instance. So we need to make sure classes that we use are thread-safe. And that access to class members are serialized.

Spring Bean Java Delegates and Thread Safety

When using the activiti:delegateExpression attribute, the thread-safety of the delegate instance will depend on how the expression is resolved. If the delegate expression is reused in various tasks and/or process definitions, and the expression always returns the same instance, then it is not thread-safe. Let’s look at a few examples to clarify.

 

Suppose the expression is ${helloWorld}, like in our example above, which resolves to a shared single Spring Bean JavaDelegate instance. Then using this expression in different tasks and/or process definitions, and having it always resolve to the same instance, will cause problems unless we are careful. In this case, thread safe libraries must be used, and member variables cannot be used unless access is synchronized.

 

However, if the expression looks something like this ${helloWorldFactory.createDelegate(someVariable)}, where helloWorldFactory is a Spring Bean factory that creates a new instance each time the expression is resolved. Then there is no problem with regards to thread-safety. Same thing if we re-implement the Java Delegate class with Spring bean scope PROTOTYPE, more on this further on in this article.

Using Process Variables in the Spring Bean Java Delegate

In a Spring Bean Java Delegate we access process variables in the same way as in a POJO Java Delegate, so read its docs.

Using Field Injection in the Spring Bean Java Delegate

Now, let’s say you wanted to use the Spring Bean Java Delegate from multiple service tasks in your process but have the implementation behave a little bit different depending on from which service task it is invoked. Basically you want configure the implementation for each service task.

 

This can be done easily using Class fields and field injection. Let’s set a separate greeting for each service task. This can be done as follows, first click on Class fields for the service task:

 

 

Then set them up, for Service Task 1:

 

 

And for Service Task 2:

 

 

The BPMN 2.0 looks like this:

 

<serviceTask id="sid-7C83EB30-8E02-400B-BEEF-CAE34BFB6FFD" 
             name="Service Task 1 (Spring Bean Java Delegate)"
             activiti:delegateExpression="${helloWorld}">

  <extensionElements>
      <activiti:field name="greeting">
          <activiti:string><![CDATA[Hello from Service Task 1]]></activiti:string>
      </activiti:field>
  </extensionElements>
</serviceTask>

 

We can now access this field in the Java Delegate implementation via an org.activiti.engine.delegate.Expression:

 

@Component("helloWorld")
public class HelloWorldSpringJavaDelegate implements JavaDelegate {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringJavaDelegate.class);

  private Expression greeting;

  public void setGreeting(Expression greeting) {
      this.greeting = greeting;
  }

  @Override
  public void execute(DelegateExecution execution) throws Exception {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Spring Java Delegate=" + this + "]");

      String greetingText = (String) greeting.getValue(execution);
      logger.info("The greeting set for this service task is: " + greetingText);
  }
}

 

 

The field value is injected through a public setter method on your Java Delegate class, following the Java Bean naming conventions (e.g. field <activiti:field name="greeting"> has setter setGreeting(…)).

 

The output from this implementation looks like this:

 

09:58:57,566 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=107501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@f787778]

09:58:57,566 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

 

09:58:57,567 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=107501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@f787778]

09:58:57,567 [http-nio-8080-exec-14] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

 

So when we used the POJO Java Delegate we could understand why this would work as there is one class instance per service task in a process definition. How come it works for Spring Bean Java Delegates too, when only a single instance of the bean is used by all service tasks?

 

A class field is stateless and thus can be shared between all service tasks and process instances. The secret is in the greeting.getValue(execution) call. Multiple calls to <Expression>.getValue(<DelegateExecution>) can happen concurrently without problems, Activiti will figure out what value to return depending on current process instance.

Field Injection and Thread Safety

Field Injection is not thread-safe when using singleton Spring Bean Java Delegates. You could have multiple parallel execution paths in a process instance, and multiple process instances running in parallel, calling the same Java Delegate instance. And as there is only one instance of the delegate and X number of threads trying to inject an org.activiti.engine.delegate.Expression, then this will lead to race conditions.

 

To solve this problem we could rewrite the Spring Bean Java delegate to use an expression and passing the needed data to the delegate via method arguments. Or we could change our example to create a bean per service task instance instead, using the PROTOTYPE scope:

 

@Component("helloWorld")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class HelloWorldSpringJavaDelegate implements JavaDelegate {

 

The the logs would then look like this:

 

12:41:18,400 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@fe8507a]

12:41:18,400 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

12:41:18,401 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112501][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@6f6558f4]

12:41:18,401 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

 

So this looks better, one instance per service task. If we start another process instance it will look like this:

 

12:46:20,078 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112509][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@1168625f]

12:46:20,078 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

12:46:20,079 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=112509][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@641c0070]

12:46:20,079 [http-nio-8080-exec-11] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

 

As we can see, we got four new instances created for our delegate, making it all thread safe. This might not be the most efficient solution if the production environment will have thousands of process instances running in parallel. See the Spring Bean Method approach for another solution.

 

Note. As of Activiti version 5.21, the process engine configuration can be configured in a way to disable the use of field injection on delegate expressions, by setting the value of the delegateExpressionFieldInjectionMode property (which takes one of the values in the org.activiti.engine.imp.cfg.DelegateExpressionFieldInjectionMode enum).

 

Following settings are possible:

 

  • DISABLED: fully disables field injection when using delegate expressions. No field injection will be attempted. This is the safest mode, when it comes to thread-safety.
  • COMPATIBILITY: in this mode, the behaviour will be exactly as it was before version 5.21: field injection is possible when using delegate expressions and an exception will be thrown when the fields are not defined on the delegate class. This is of course the least safe mode with regards to thread-safety, but it can be needed for backwards compatibility or can be used safely when the delegate expression is used only on one task in a set of process definitions (and thus no concurrent race conditions can happen).
  • MIXED: Allows injection when using delegateExpressions but will not throw an exception when the fields are not defined on the delegate. This allows for mixed behaviours where some delegates have injection (for example because they are not singletons) and some don’t.

 

The default mode for Activiti version 5.x is COMPATIBILITY.

Injecting Beans into Spring Bean Java Delegates

When we use Spring we most likely want to use other Spring Beans in our Java Delegate implementation. This can be Spring beans that we create and out-of-the-box Spring beans.

 

Let’s create a super simple bean that we can call from our delegate:

 

package com.activiti.extension.bean.service;

import org.springframework.stereotype.Service;

@Service
public class HelloWorldService {
  public String greeting() {
      return "Hello World from Service!";
  }
}

 

Note that it is located in the com.activiti.extension.bean package, this means it will be scanned by Activiti and registered in the Spring application context. Now when this is done we can update our Spring Bean Java Delegate to wire in this bean plus an out-of-the-box bean called UserService:

 

package com.activiti.extension.bean;

import com.activiti.domain.idm.User;
import com.activiti.extension.bean.service.HelloWorldService;
import com.activiti.service.api.UserService;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


@Component("helloWorld")
public class HelloWorldSpringJavaDelegate implements JavaDelegate {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldSpringJavaDelegate.class);

  /**
   * Expression representing the greeting class field
   */

  private Expression greeting;

  @Autowired
  HelloWorldService simpleService;

  @Autowired
  UserService userService;

  public void setGreeting(Expression greeting) {
      this.greeting = greeting;
  }

  @Override
  public void execute(DelegateExecution execution) throws Exception {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Spring Java Delegate=" + this + "]");

      String greetingText = (String) greeting.getValue(execution);
      logger.info("The greeting set for this service task is: " + greetingText);

      logger.info("Injected Spring Bean greeting: " + simpleService.greeting());

      User user = userService.findUser(Long.parseLong((String)execution.getVariable("initiator")));
      String username = user.getFirstName() + " " + user.getLastName();
      logger.info("Initiator is: " + username);
  }
}

 

Running this produces a log similar to this:

 

02:03:21,181 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=115001][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@1251052b]

02:03:21,181 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

02:03:21,182 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Injected Spring Bean greeting: Hello World from Service!

02:03:21,184 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Initiator is: null Administrator

02:03:21,185 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - [Process=115001][Spring Java Delegate=com.activiti.extension.bean.HelloWorldSpringJavaDelegate@1251052b]

02:03:21,185 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

02:03:21,185 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Injected Spring Bean greeting: Hello World from Service!

02:03:21,186 [http-nio-8080-exec-3] INFO  com.activiti.extension.bean.HelloWorldSpringJavaDelegate  - Initiator is: null Administrator

 

Accessing the HelloWorldService works nicely, mostly because it just returns a static String literal. But if you are calling methods that manipulate data you have to make them thread safe. Using the out-of-the-box UserService was also no problem, note that the Admin user does not have a first name and hence null.

 

Note. the order in which beans are read does not matter, the scanner will collect all bean info first and then create them in necessary order, just like it would work with XML based configuration of beans. The important thing is that all beans are in packages that are scanned.

Calling Spring Bean Java Delegates Asynchronously

See the POJO Java Delegate docs.

Table of Contents                   

 

Introduction

Service tasks are one of the fundamental building blocks of any process. They allow you to implement complex business logic, make calculations, talk to external systems and services, and more. In Activiti there are a number of ways in which a service task can be implemented:

 

  1. As a POJO, which is called a Java Delegate (what this article covers)
  2. As a Spring Bean Java Delegate
  3. As a Spring Bean method

 

What implementation approach you choose depend on the use-case. If you don’t need to use any Spring beans in your implementation then use a POJO Java Delegate. If your service task implementation needs to use, for example out-of-the-box Spring beans, then use the Spring Bean Java Delegate. These two approaches uses a “one operation per service task” implementation. If you need your implementation to support multiple operations, then go with the Spring bean method implementation.

 

There is also a runtime twist to this, most likely there will be multiple process instances calling the same service task implementation. And the same service task implementation might be called from multiple service tasks in a process. The implementation behaves differently depending on approach:

 

  1. POJO Java Delegate - inside a process instance you have one class instance per service task, between process instances you share class instances per service task
  2. Spring Bean Java Delegate - same Spring bean (i.e. class instance) is used within a process instance and between process instances
  3. Spring Bean method - same Spring bean (i.e. class instance) is used within a process instance and between process instances but there is no field injection and all data is passed in via method params, so thread-safe

 

What this basically means is that if you use a third party class inside a Java Delegate, then it needs to be thread safe as it can be called by multiple concurrent threads. If you use a Spring bean approach then the same thing applies, if you inject beans they need to all be thread safe. With the Spring bean approach you can also change the bean instantiation scope to be PROTOTYPE, which means an instance will be created per service task.

 

This article cover the first approach - POJO Java Delegate.

Source Code

Source code for the Activiti Developer Series can be found here.

Prerequisites

Before starting with your service task implementation make sure to set up a proper Activiti Extension project.

Implementing a Hello World Java Delegate

Let’s start the usual way with a Hello World Java Delegate. However, we are going to take the opportunity to check process IDs and object instance IDs while we are at it, so the log message from the Java Delegate will be a little bit different than the usual “Hello World”.

Coding the Java class

Here is the implementation of the Java Delegate class:

 

package com.activiti.extension.bean;

import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorldJavaDelegate implements JavaDelegate {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldJavaDelegate.class);

  @Override
  public void execute(DelegateExecution execution) throws Exception {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Java Delegate=" + this + "]");
  }
}

 

The class has been created in the com.activiti.extension.bean package, but it does not have to be located in this package. It is just good practice to put the class in this package if it ever needs to be converted to a Spring Java Delegate in the future. Then this package is scanned automatically by Activiti and any beans are instantiated.

 

A Java Delegate needs to implement the org.activiti.engine.delegate.JavaDelegate interface, which contains the execute method. This methods takes one parameter of type org.activiti.engine.delegate.DelegateExecution, which is how we get access to the process instance that is invoking this Java Delegate. As said in the introduction, a Java Delegate object instance for a Service Task is shared between process instances, so the DelegateExecution parameter is our way of finding out information about the active process instance.

 

Notice how we use an external class here for logging and that the logger.info method could be called concurrently by multiple threads. So it needs to be thread safe, which it is. Same thing applies for other class members that we define and use in the execute method. We can do whatever we want inside the execute method as all threads have independent call stacks.

Testing the Java Delegate

Now to test the Java Delegate implementation create a process model looking like this:

 

Both of these Service Tasks uses the Java Delegate that we implemented:

 

In BPMN 2.0 XML it will look like this:

 

<serviceTask id="sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE"
  name="Service Task 1 (Java Delegate D1)"
  activiti:class="com.activiti.extension.bean.HelloWorldJavaDelegate">

 

When we run this process it will print similar logs to the following:

 

11:17:31,894 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70005][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@7ce61257]

11:17:31,896 [http-nio-8080-exec-7] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70005][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@2394529d]

 

If we let the process instance stay at the User Task and start another process, then we will see logs such as follows for the second process instance:

 

11:20:07,035 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70015][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@7ce61257]

11:20:07,036 [http-nio-8080-exec-5] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=70015][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@2394529d]

 

What we can see here is that when inside one process instance each Service Task will have its unique version of the Java Delegate object instance (i.e. 7ce61257 and 2394529d). However, when starting a second process instance, it will reuse the Java Delegate instances from the first process instance.

Java Delegates and Thread Safety

When using the activiti:class attribute, things are not thread safe even if each service task has its own Java Delegate instance. This is because Java Delegate instances are shared between process instances. And this could potentially lead to library code used in the Java Delegate implementation being called concurrently, and as a result it needs to be thread-safe, as the log library we used in our example. Same thing for any class member variables, access needs to be synchronized.

 

So use thread-safe libraries and avoid class members.

Using Process Variables in the Java Delegate

Most of the time when you implement a Java Delegate you would want to use some process variable values. Process variables are typically set when the process instance is started, through the API, or by different activities in the process. They are stored in the database for each process instance.

 

So how do you pass these process variable values into the Java Delegate? It is not necessary to pass any value, you can access all process instance variables via the DelegateExecution object and the getVariable method.

 

There is one process variable that you can access immediately to try this out, and that is the initiator:

 

String initiator = (String)execution.getVariable("initiator");

logger.info("Initiator of the process has user ID = " + initiator);

 

To print all process variables for the process instance you can do this:

 

logger.info("--- Process variables:");
Map<String, Object> procVars = execution.getVariables();
for (Map.Entry<String, Object> procVar : procVars.entrySet()) {
  logger.info("   [" + procVar.getKey() + " = " + procVar.getValue() + "]");
}

 

It is equally easy to create a new process variable via the setVariable method on the DelegateExecution object:

 

execution.setVariable("greeting", "Hello World!");

 

When you create new variables they are created in a scope, similar to how you create variables in a Java program. For example, if you create a variable inside a Java class method it is scoped to that method and not accessible outside of it. Variables in the Activiti workflow engine are scoped at for example process instance level, sub-process instance level, user task instance level etc. If you are creating a variable that already exist in the scope, then it will be overwritten with the new value.

 

However, there is a difference between Java and Activiti, when we create a variable with the setVariable method it will traverse the scope hierarchy until it reaches the top root scope and set the variable there, unless there is a local variable with the same name in some scope on the way to the root scope, in that case this local variable is overwritten.

 

So when we created the variable greeting it was set on the root process instance scope. This is because service tasks, in contrast to user tasks, have the same scope as the process instance they are created in.

 

If you want to be sure to set a variable at the scope you are at, then you should use the setVariableLocal method instead:

 

execution.setVariableLocal("greetingLocal", "Hello World Local!");

 

This has no effect on a service task as it does not have its own scope like a user task, unless it’s in an external process invoked via <callActivity id="someProcessId".

 

Let’s say we now have the following implementation:

 

public void execute(DelegateExecution execution) throws Exception {
  logger.info("[Process=" + execution.getProcessInstanceId() +
              "][Java Delegate=" + this + "]");
  logger.info("[ActivityName=" + execution.getCurrentActivityName() +
              "][ActivityId=" + execution.getCurrentActivityId() + "]");

  String initiator = (String)execution.getVariable("initiator");
  logger.info("Initiator of the process has user ID = " + initiator);

  execution.setVariable("greeting", "Hello World!");
  execution.setVariableLocal("greetingLocal", "Hello World Local!");

  logger.info("--- Process variables:");
  Map<String, Object> procVars = execution.getVariables();
  for (Map.Entry<String, Object> procVar : procVars.entrySet()) {
      logger.info("   [" + procVar.getKey() + " = " + procVar.getValue() + "]");
  }
}

 

This will print a log looking something like this:

 

08:58:55,896 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=97501][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@132faf11]

08:58:55,896 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 1 (Java Delegate D1)][ActivityId=sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE]

08:58:55,896 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - Initiator of the process has user ID = 1

08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - --- Process variables:

08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [initiator = 1]

08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greeting = Hello World!]

08:58:55,897 [http-nio-8080-exec-1] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greetingLocal = Hello World Local!]

 

Important. as this is not a user task you cannot use the TaskService and get a task instance, such as:

 

TaskService taskService = execution.getEngineServices().getTaskService();
Task task = taskService.createTaskQuery().singleResult();
String taskId = task.getId();

 

You will get a NullPointerException on line 3.

 

Now, what happens if we move one of the Service tasks into a sub-process:

 

 

This now produces the following log:

 

09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=100011][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@44749768]

09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 1 (Java Delegate D1)][ActivityId=sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE]

09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - Initiator of the process has user ID = 1

09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - --- Process variables:

09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [initiator = 1]

09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greeting = Hello World!]

09:21:20,862 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greetingLocal = Hello World Local!]

09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=100011][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@5183bd3f]

09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 2 (Java Delegate D1)][ActivityId=sid-69D06583-0BF7-43A6-8C28-2A65EEF1508C]

09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - Initiator of the process has user ID = 1

09:21:20,864 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - --- Process variables:

09:21:20,865 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [initiator = 1]

09:21:20,865 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greeting = Hello World!]

09:21:20,865 [http-nio-8080-exec-4] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  -    [greetingLocal = Hello World Local!]

 

If you have a normal sub-process like in our example above with <subProcess id="subProcess1", it is modelled as a part of the parent process (in the same BPMN and as a child of the XML element).

 

If the sub-process you want to call is subject to change, independent of the parent-process, you can use a <callActivity id="someProcessId". This sub-process is called an external process (not part of the parent BPMN, but lives in it's own BPMN). This allows you to redeploy the child-process without the need to alter the parent-process.

 

A normal sub-process is treated as a direct child of the process-instance, so you can access the parent's process variables from within the sub-process, as you have seen above. On the contrary, a call-activity does not have access to the parent variables and is a "process instance" itself. You can expose variables from the parent-process to the call-activity by using the in/out declarations.

How many variables are really fetched with getVariable?

This might sound like a weird question, one right? No, behind the scenes all variables are fetched and cached when you call the getVariable method for the first time. This might not always be ideal, if you for example have loads of variables. To just fetch the specified variable you need to use another method signature that takes an extra parameter:

 

String initiator = (String)execution.getVariable("initiator", false);

 

In this case the false parameter says that we do not want to fetch all variables. Just the one we actually specified, initiator in this case.

Using Field Injection in the Java Delegate

Now, let’s say you wanted to use the Java Delegate from multiple service tasks in your process but have the implementation behave a little bit different depending on from which service task it is invoked. Basically you want configure the implementation for each service task.

 

This can be done easily using Class fields and field injection. Let’s set a separate greeting for each service task. This can be done as follows, first click on Class fields for the service task:

 

 

Then set them up, for Service Task 1:

 

 

And for Service Task 2:

 

 

The BPMN 2.0 looks like this:

 

<serviceTask id="sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE" 
             name="Service Task 1 (Java Delegate D1)"
             activiti:class="com.activiti.extension.bean.HelloWorldJavaDelegate">

  <extensionElements>
      <activiti:field name="greeting">
          <activiti:string><![CDATA[Hello from Service Task 1]]></activiti:string>
      </activiti:field>
  </extensionElements>
</serviceTask>

 

We can now access this field in the Java Delegate implementation via an org.activiti.engine.delegate.Expression:

 

public class HelloWorldJavaDelegate implements JavaDelegate {
  private static Logger logger = LoggerFactory.getLogger(HelloWorldJavaDelegate.class);

  private Expression greeting;

  public void setGreeting(Expression greeting) {
     this.greeting = greeting;
  }

  @Override
  public void execute(DelegateExecution execution) throws Exception {
      logger.info("[Process=" + execution.getProcessInstanceId() +
                  "][Java Delegate=" + this + "]");
      logger.info("[ActivityName=" + execution.getCurrentActivityName() +
                  "][ActivityId=" + execution.getCurrentActivityId() + "]");

      String greetingText = (String) greeting.getValue(execution);
      logger.info("The greeting set for this service task is: " + greetingText);
  }
}

 

The field value is injected through a public setter method on your Java Delegate class, following the Java Bean naming conventions (e.g. field <activiti:field name="greeting"> has setter setGreeting(…)).

 

The output from this implementation looks like this:

 

10:46:50,956 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102511][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@6618bfad]

10:46:50,956 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 1 (Java Delegate D1)][ActivityId=sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE]

10:46:50,957 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - The greeting set for this service task is: Hello from Service Task 1

 

10:46:50,958 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [Process=102511][Java Delegate=com.activiti.extension.bean.HelloWorldJavaDelegate@588a2f17]

10:46:50,958 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - [ActivityName=Service Task 2 (Java Delegate D1)][ActivityId=sid-69D06583-0BF7-43A6-8C28-2A65EEF1508C]

10:46:50,959 [http-nio-8080-exec-10] INFO  com.activiti.extension.bean.HelloWorldJavaDelegate  - The greeting set for this service task is: Hello from Service Task 2

 

So as you might have figured out, using class fields are thread safe as they are set on the class when it is first initiated. And there is one class instance per service task. As usual, Java Delegate object instances are shared between process instances.

Field Injection and Thread Safety

When using the activiti:class attribute, using field injection is always thread safe. For each service task that references a certain class, a new Java Delegate instance will be instantiated and fields (org.activiti.engine.delegate.Expression) will be injected once when the instance is created. Reusing the same class in multiple service tasks and process definitions is no problem.

Calling Java Delegates Asynchronously

So far the service tasks have been defined in the process definition in the default way, which means that they will be called synchronously in the same transaction and thread as the transaction and thread the process was started in.

 

So when the process instance execution reaches Service Task 1, it will have to stop and wait for the Java Delegate implementation to complete, and then the same for Service Task 2. When a service task contains long-running logic, like the invocation of an external Web Service or the construction of a large PDF document, this may not be the desired behaviour.

 

Activiti provides a solution for these cases in the form of async continuations. From a BPMN 2.0 XML perspective, the definition of an asynchronous service task (or another type of task) is easy. You only have to add an async attribute to the service task configuration:

 

<serviceTask id="sid-05C898E5-B1B0-4A1A-89D0-5639FAE5A3BE"
             name="Service Task 1 (Java Delegate D1)"
             activiti:async="true"
             activiti:class="com.activiti.extension.bean.HelloWorldJavaDelegate">

 

When we define a service task with the async attribute set to true, the execution of the

service task logic will be executed in a separate transaction and thread.