Camunda Team Blog

Data mapping and transformation with Camunda Spin and Java 8

Written by Daniel Meyer on , under Execution category.
Working with text based data formats such as Xml and Json is a common requirement when implementing BPMN processes.

Since version 7.2 Camunda provides an optional library which is called Camunda Spin. Spin is a lightweight wrapper library which provides a easy to use API when working with text based data formats such as XML and Json.

In this post I show how the Camunda Spin can be used for implementing data transformations and mapping in combination with the Java 8 Stream processing API and contrast this to the classical Java 6 / 7 way of doing it.

Example

The example we use is how to transform a list of The Big Bang Theory episodes (provided as Xml) into Json output.

The source is obtained from http://services.tvrage.com/feeds/episode_list.php?sid=8511 and looks like this:
<?xml version="1.0" encoding="UTF-8" ?>
<show>
  <name>The Big Bang Theory</name>
  <totalseasons>8</totalseasons>
  <episodelist>
    <season no="1">
      <episode>
        <epnum>1</epnum>
        <seasonnum>01</seasonnum>
        <prodnum>276023</prodnum>
        <airdate>2007-09-24</airdate>
        <link>http://www.tvrage.com/The_Big_Bang_Theory/episodes/550436</link>
        <title>Pilot</title>
      </episode>
      <episode>
        <epnum>2</epnum>
        <seasonnum>02</seasonnum>
        <prodnum>3T6601</prodnum>
        <airdate>2007-10-01</airdate>
        <link>http://www.tvrage.com/The_Big_Bang_Theory/episodes/603610</link>
        <title>The Big Bran Hypothesis</title>
      </episode>
...
As Output we want to obtain a json list of all episodes which aired after 2012:
[{
  "name":"The Shiny Trinket Maneuver",
  "air-date":"2012-01-12"
 },
 {
  "name":"The Recombination Hypothesis",
  "air-date":"2012-01-19"
 }, ... ]

Enable Spin in your project

In order to enable Camunda Spin in your project,

1) add it to your maven dependencies:

<dependencymanagement>
    <dependencies>
      <dependency>
        <groupid>org.camunda.bpm</groupid>
        <artifactid>camunda-bom</artifactid>
        <version>7.3.0-alpha1</version>
        <scope>import</scope>
        <type>pom</type>
      </dependency>
    </dependencies>
  </dependencymanagement>

  <dependencies>
    <dependency>
      <groupid>org.camunda.spin</groupid>
      <artifactid>camunda-spin-core</artifactid>
    </dependency>

    <dependency>
      <groupid>org.camunda.spin</groupid>
      <artifactid>camunda-spin-dataformat-json-jackson</artifactid>
    </dependency>

    <dependency>
      <groupid>org.camunda.spin</groupid>
      <artifactid>camunda-spin-dataformat-xml-dom</artifactid>
    </dependency>
  </dependencies>
2) In your Java Code, add the following static import:
import static org.camunda.spin.Spin.*;

Parsing the Xml input with Spin

In order to parse the Xml input, we can use Spin's XML(...) method:
XML(xmlInput)
Spin can consume Xml in the form of a string or a java.io.Reader instance.

The Java 6/7 way

We'll start with the classical Java way of transforming the input.

Using Spin's xPath function to iterate through all episodes

In oder to iterate through all episodes in the document, we can use Spin's xPath funciton:
for (SpinXmlElement element : XML(xmlInput).xPath("/Show/Episodelist/Season/episode").elementList()) {
  // work with the element        
}

Getting the production Year as Integer

Since we only want to retain those episodes which aired after 2012, we need to get the production year element for the episode and interpret it as integer:
for (SpinXmlElement element : XML(xmlInput).xPath("/Show/Episodelist/Season/episode").elementList()) {

  int productionYear = Integer.parseInt(element.childElement("airdate").textContent().substring(0, 4));

}

Creating an empty Json Document

Next, lets create an empty Json document into which we can collect the episodes which aired after 2012:
SpinJsonNode resultJson = JSON("[]");

Transform the Episodes into Json

Next we create a json object into which we copy the values from the xml source:
SpinJsonNode episodeJson = JSON("{}")
      .prop("name", episode.childElement("title").textContent())
      .prop("air-date", episode.childElement("airdate").textContent());
The complete Java 6/7 source code then looks like this:
SpinJsonNode resultJson = JSON("[]");

for (SpinXmlElement episode : XML(xmlInput).xPath("/Show/Episodelist/Season/episode").elementList()) {
  int productionYear = Integer.parseInt(episode.childElement("airdate").textContent().substring(0, 4));
  if(productionYear >= 2012) {
    
    SpinJsonNode episodeJson = JSON("{}")
      .prop("name", episode.childElement("title").textContent())
      .prop("air-date", episode.childElement("airdate").textContent());
    
    resultJson.append(episodeJson);
  }
}

The Java 8 Way

Java 8 introduces the Stream API and lambdas. This allows us to write a very compact representation for obtaining the same thing:
final SpinJsonNode resultJson = JSON("[]");

XML(xmlInput)
  .xPath("/Show/Episodelist/Season/episode").elementList()
  .stream()
  .filter(e -> Integer.parseInt(e.childElement("airdate").textContent().substring(0, 4)) >= 2012)
  .map(e -> JSON("{}")
              .prop("name", e.childElement("title").textContent())
              .prop("air-date", e.childElement("airdate").textContent()))
  .forEach(e -> resultJson.append(e));

What is missing?

The above example is not "purely" functional. In the forEach method we collect the generated customers and append them to the resultJson object.

A more functional approach would use a java.util.stream.Collector:

SpinJsonNode resultJson = XML(xmlInput)
  .xPath("/Show/Episodelist/Season/episode").elementList()
  .stream()
  .filter(e -> Integer.parseInt(e.childElement("airdate").textContent().substring(0, 4)) >= 2012)
  .map(e -> JSON("{}")
              .prop("name", e.childElement("title").textContent())
              .prop("air-date", e.childElement("airdate").textContent()))
  .collect(asJsonList());

The SpinCollectors.asJsonList() method does not exist yet. This would be something the Spin library could provide. In case anybody would use this?