This example describes how to configure a data format provided by Camunda Spin on process application level. It showcases the configuration of the Jackson-based JSON data format that Spin provides out-of-the-box. JSON serialization is customized by registering a Jackson module with a custom serializer and deserializer.
Process-application-specific configuration means that configuration code is part of he process application and therefore isolated from other applications. Note that this approach is currently limited to Object variable (de-)serialization. See the example on global data format configuration for how to configure a data format such that it applies to all applications using Spin.
In particular, we examine how to customize the serialization of the class Car:
public class Car {
protected Money price;
public Money getPrice() {
return price;
}
public void setPrice(Money price) {
this.price = price;
}
}
Car
references Money:
public class Money {
protected int amount;
public Money(int amount) {
this.amount = amount;
}
public int getAmount() {
return amount;
}
}
With default Jackson serialization, an object new Car(1000)
is serialized as {"price" : {"amount" : 1000}}
. In the following, we describe how to configure the serialization such that the result is {"price" : 1000}
and give an example how this integrates with process variables and process execution.
We use the following process:
The script task Extract price expects a Car
JSON object in the format of {"price" : 1000}
and fails otherwise.
- Checkout the project with Git
- Run the Maven build
mvn clean install -P${profile}
. Dependending on the application server you use, set${profile}
toweblogic
(for WebLogic, Websphere, Glassfish),tomcat
,jboss
orwildfly
- Deploy the resulting WAR file to your application server
- Perform a HTTP GET request to the url
http://localhost:8080/dataformat-example/start-process
either with a REST client or simply in the address bar of your browser - Go to Cockpit and verify that the process variable named
car
was serialized correctly
When a process application is deployed, a set of process-application-specific Spin data formats is bootstrapped. After data formats instantiation, Spin detects so-called data format configurators and calls these with the detected format instances. Users can provide custom configurators to influence the way a data format serializes and deserializes objects, which is what this example shows.
Configuring data formats requires to implement the SPI DataFormatConfigurator and declare implementations of it in a file META-INF/services/org.camunda.spin.spi.DataFormatConfigurator
. Here, the class JacksonDataFormatConfigurator is such an implementation. It has the following contents:
import org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat;
import org.camunda.spin.spi.DataFormatConfigurator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class JacksonDataFormatConfigurator implements DataFormatConfigurator<JacksonJsonDataFormat> {
public void configure(JacksonJsonDataFormat dataFormat) {
ObjectMapper objectMapper = dataFormat.getObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Money.class, new MoneyJsonDeserializer());
module.addSerializer(Money.class, new MoneyJsonSerializer());
objectMapper.registerModule(module);
}
public Class<JacksonJsonDataFormat> getDataFormatClass() {
return JacksonJsonDataFormat.class;
}
}
The method getDataFormatClass
declares the kind of data formats a configurator is able to configure. The method configure
is the callback invoked by the Spin runtime after data format instantiation. Here, we register in form of the classes MoneyJsonSerializer and MoneyJsonDeserializer a Jackson serializer and deserializer for the Money
class. These make sure that a Car
object is serialized in the way we need it.
The CDI bean ProcessInstanceStarterBean has a method that starts a process instance with a serialized Car
value.
@ApplicationScoped
public class ProcessInstanceStarterBean {
@InProcessApplicationContext
public ProcessInstance startProcess(Car car) {
ProcessEngine processEngine = BpmPlatform.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
return runtimeService.startProcessInstanceByKey("testProcess",
Variables.createVariables().putValueTyped("car",
Variables
.objectValue(car)
.serializationDataFormat(DataFormats.JSON_DATAFORMAT_NAME)
.create()));
}
}
As the process engine is not able to guess by itself which JSON data format to use for serializing the variable, we have to tell it that we want to use the format defined in the process application. This is solved here by defining a custom CDI annotation @InProcessApplicationContext
. A custom CDI interceptor ProcessApplicationContextInterceptor is notified whenever this annotation is present. This interceptor determines the context process application and declares it using the utility class ProcessApplicationContext:
@InProcessApplicationContext
@Interceptor
public class ProcessApplicationContextInterceptor {
@Inject
protected ProcessApplicationInterface processApplication;
@AroundInvoke
public Object performContextSwitch(InvocationContext invocationContext) throws Exception {
try {
ProcessApplicationContext.setCurrentProcessApplication(processApplication.getName());
return invocationContext.proceed();
} finally {
ProcessApplicationContext.clear();
}
}
public ProcessApplicationInterface getProcessApplication() {
return processApplication;
}
public void setProcessApplication(ProcessApplicationInterface processApplication) {
this.processApplication = processApplication;
}
}
ProcessApplicationContext#setCurrentProcessApplication
tells the process engine to access process-application-specific resources such as Spin data formats when engine API is used within the annotated method. The engine can then successfully use the data format configured in the process application.
For this to work on Tomcat (a non-Java-EE server), we have include Weld in the process application. However, note that using CDI is not required for this feature to work. The lowest common denominator is the utility class ProcessApplicationContext
. It can be used in any context to declare process application context before invoking engine API.
Read the documentation on Process Application Resource Access for why it is required to declare process application context.
The script task Extract price makes use of that structure by defining the following script:
var carJson = execution.getVariableTyped("car").getValueSerialized();
var price = S(carJson ).prop("price").numberValue();
execution.setVariable("price", price);
Here, we do not have to declare the process application context. For delegation code (i.e. code that is called by the process engine), the process engine implicitly knows the process application context.