Spin: add Jackson JSON array to task variables and evaluate it in Expressions

Hi,

we are currently communcating via REST with Camunda. I am adding following JSON variable to the process instance variable:

However, I want to do that now directly in a JavaDelegate:

delegateExecution.setVariable("tasks", ....)

I have a Jackson ArrayNode or the same representation as a String.

How can I add it to get the same result? I have tested following, but both are throwing exceptions…

    JsonValue jsonValue = SpinValues.jsonValue(json).create();
    SpinJsonNode jsonNode = Spin.JSON(tasksVariable.toString());

Edit:
If I see in the cockpit that the variable type is JSON, is it then a org.camunda.spin.plugin.variable.value.JsonValue?

If I read the variable type which was created via REST, then it is a JacksonJsonNode. So, how do I create a JacksonJsonNode?

Here is an example without using Spin… while, still using Jackson. You’ll notice that Jackson sits in a different package within the Spin library - so, it might get a little confusing.

Using Jackson directly:

// create a list of case Ids for return
List<String> caseIds = new ArrayList<String>();
for(CaseInstance caseInstance :  caseInstances) {
  caseIds.add(caseInstance.getId());
} 

// this time we build a proper JSON return value - using Jackson
ObjectMapper mapper = new ObjectMapper();
ObjectNode rootObjectNode = mapper.createObjectNode();

// create the JSON node to hold the BPM returned variables
// ObjectNode processInstanceList = mapper.createObjectNode();
ArrayNode caseInstanceList = mapper.createArrayNode();
// attach to parent
rootObjectNode.set("case IDs", caseInstanceList);
for (String caseId : caseIds) {
  caseInstanceList.add(caseId);
}

This example is a little more fluent - while setting variables at the the root level:

// this time we build a proper JSON return value - using Jackson
ObjectMapper mapper = new ObjectMapper();
ObjectNode rootObjectNode = mapper.createObjectNode();

// Extract completed process results or output
// set the process processID, InstanceID, and isEnded 
// variables as child JSON nodes
rootObjectNode
    .put("processID", processID)
    .put("processInstanceID", piid)
    .put("isEnded", isEnded);

If you’re using JSON types as process variables - recommend testing. I typically parse out what I need and use Camunda’s built-in types.

1 Like

Maybe I need to mention that I want to use this JSON in the expression of an activity, like getting the elements:

${tasks.elements()}

And that is, I think, only possible with a Spin JSON variable, not with a serialized object which might be a Jackson ArrayNode.

So,

delegateExecution.setVariable("tasks", myJacksonArrayNode)

will not work. I think, I need a converter:

delegateExecution.setVariable("tasks", (SpinJsonNode) myJacksonArrayNode)

Off course, this does not work directly.

Sounds like you working towards this example?

And using the spin JSON object to hold an array?

Actually it is a JacksonJsonNode which extends SpingJsonNode

In REST call, I add the variable this way:

ObjectNode varNode = mapper.createObjectNode();

varNode.put("type", "json");
varNode.put("value", val.toString());
rootNode.set(variable, varNode);

If I read it in DelegateExpression, I can do it this way:

JacksonJsonNode jsonNode = (JacksonJsonNode) execution.getVariable("taskproc");

where “taskproc” is a variable generated by Camunda Engine from “tasks” variable while passing a single element in a CallActivity taskproc for each ${tasks.elements()}

But as mentioned first, I would do it in the Engine directly:

ArrayNode myJacksonArrayNode = ....
...
execution.setVariable('tasks', ....)

And then Camunda should still be able to evaluate ${tasks.elements()}

@garysamuelson:
The example you mentioned, is what I want, but it seems the example is buggy, too.

In

 JsonValue jsonValue = SpinValues.jsonValue(json).create();

I have the same problem that the value of jsonValue is null.

Since I’ve avoided the spin library - I can’t help you with direct answers…

What caused my troubles was a bad pom.xml. It was sort-of limping along in Tomcat… then when later ported to Wildfly it just would-not-work. I later discovered the error. However, at that point… the easy fix was to remove spin from the build. But, I’m somewhat of a purist (aka lacked patience). Build inclusions with the only difference being nesting package levels (and somebody correct me if I’m wrong here). It’s very easy to import the wrong library with Eclipse making the various recommendations.

Apologies for the obvious questions:

  1. did you see anything upstream in the logs? Something to indicate that the null is caused by an upstream exception/recovery?
  2. triple check your pom.xml to make sure it’s including/not-including the correct libraries.

I may take a renewed look at the spin library - but, better that somebody else with more experience using spin help with this question.

@miwoe

Wanted to get back to this question - I needed to begin using the SPIN type for a web-UI requirement (embedded forms).

I hope this snippet helps explain - problem was that Camunda needs to know that you’re creating a process variable of type (from a Java API perspective) SpinJsonNode. There are various tools to help with this. But, I leaned towards the simple approach in the following code otherwise… if I simply appended a spin object cloned from Jackson JsonNode, the spin library created very complex, though nicely detailed, JSON objects.

Sample JAX-RS POST JSON payload:

{
    "caseID": "case_simple_01_cid",
    "claim": "admin",
    "processVariables": [
        {
            "name": "hello",
            "value": "greetings BPM"
        }, {
            "name": "myName",
            "value": "Joe Smith"
        }, {
            "name": "spinCustomer",
            "value": {
                "firstname": "Bob",
                "lastname": "Smith",
                "customerrank" : "highvalue"
            }
        }
    ]
}

Java Example:

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Produces(MediaType.APPLICATION_JSON)
  @Path("casebasicstartspin")
  public JsonNode caseBasicStartSpin(JsonNode postpayload)  throws Exception {
    
    // Testing out a new approach to receiving and returning
    // Jackson JsonNode.
    
    LOGGER.info("********************************");
    LOGGER.info("*** caseBasicStartSpin - invoked");    
    LOGGER.info("********************************");   
    
    // get case ID
    // NOTE: Using the "General -> Case Id" field value from the CMMN case model
    String caseID = postpayload.findValue("caseID").asText();
    
    LOGGER.info("*** caseBasicStart - caseID: " + caseID);  
    
    // get the case/process variables
    final JsonNode arrNode = postpayload.get("processVariables");
    Map<String, Object> variables = new HashMap<String, Object>();

    ObjectMapper mapper = new ObjectMapper();
    for (final JsonNode jsonNode : arrNode) {
      // check for variable with children - which we'll assume are of type JSON
      LOGGER.info("*** jsonNode - name: " + jsonNode.get("name").asText() + " , size: " + jsonNode.get("value").size());
      if (jsonNode.get("value").size() > 0) {
        variables.put(jsonNode.get("name").asText(), Spin.JSON(mapper.writer().writeValueAsString(jsonNode.get("value"))));
      } else {
        variables.put(jsonNode.get("name").asText(), jsonNode.get("value").asText());
      }
    }

Log Output:
spinCustomer was created in the above for loop, via testing the JsonNode for children (implies this is a Json type)
spinJsonCustomerManualAdd was my baseline test - verifying manual process variable creation

07:37:40,778 INFO  [com.talkdata.service.Case] (default task-5) *** caseBasicStart - caseID: case_simple_01_cid
07:37:40,778 INFO  [com.talkdata.service.Case] (default task-5) *** jsonNode - name: hello , size: 0
07:37:40,778 INFO  [com.talkdata.service.Case] (default task-5) *** jsonNode - name: myName , size: 0
07:37:40,778 INFO  [com.talkdata.service.Case] (default task-5) *** jsonNode - name: spinCustomer , size: 3
07:37:40,807 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) *** caseTest
07:37:40,807 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) *******************
07:37:40,807 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) *** CaseDefinitionId ID: case_simple_01_cid:1:0cc51e8a-1d0b-11e7-b634-000c296e2b3e
07:37:40,807 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) *** CaseInstanceId ID: 522d34b6-1d21-11e7-90fd-000c296e2b3e
07:37:40,807 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) *** ActivityId ID: case_simple_01_id
07:37:40,807 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) *** Process Variables: 
07:37:40,808 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) myName : Joe Smith
07:37:40,808 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) hello : greetings BPM
07:37:40,808 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) spinJsonCustomerManualAdd : {"customer":"Kermit"}
07:37:40,808 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) spinCustomer : {"firstname":"Bob","lastname":"Smith","customerrank":"highvalue"}
07:37:40,808 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) >> Date Stamp: Sun Apr 09 07:37:40 CDT 2017
07:37:40,808 INFO  [com.talkdata.task.PrintProcessVars] (default task-5) *******************


1 Like

After some weeks, I gave it a try, again.

Actually, solution was easy:
I just need to do following:

 setVariable("myJsonVariable", Spin.JSON(myObject));
2 Likes