1

Not able to get my head over SPEL for Message payloads. I want to extract data from certain fields of my message payload which is essentially the following JSON converted to Map<String, Object> and passed to a @Transformer

{
  "expand":"renderedFields,names,schema,transitions,operations,editmeta,changelog,versionedRepresentations",
  "id":"14730",
  "self":"https://jira.foo.com/rest/api/2/issue/14730",
  "key":"SDP-145",
  "fields":{
    "issuetype":{
      "self":"https://jira.foo.com/rest/api/2/issuetype/10200",
      "id":"10200",
      "description":"gh.issue.epic.desc",
      "iconUrl":"https://jira.foo.com/ghanghor/viewkaka?size=xsmall&kakaId=10501&kakaType=issuetype",
      "name":"Epic",
      "subtask":false,
      "kakaId":10501
    },
    "priority":{
      "self":"https://jira.foo.com/rest/api/2/priority/3",
      "iconUrl":"https://jira.foo.com/images/icons/priorities/major.svg",
      "name":"Major",
      "id":"3"
    },
    "labels":[
      "Lizzy",
      "kanban",
      "rughani"
    ],
    "updated":"2021-01-21T10:33:38.000+0000",
    "status":{
      "self":"https://jira.foo.com/rest/api/2/status/1",
      "description":"The issue is open and ready for the assignee to start work on it.",
      "iconUrl":"https://jira.foo.com/images/icons/statuses/open.png",
      "name":"Open",
      "id":"1",
      "statusCategory":{
        "self":"https://jira.foo.com/rest/api/2/statuscategory/2",
        "id":2,
        "key":"new",
        "colorName":"blue-gray",
        "name":"To Do"
      }
    },
    "summary":"new epic for Tazzy",
    "creator":{
      "self":"https://jira.foo.com/rest/api/2/user?username=skadmin",
      "name":"skadmin",
      "key":"skadmin",
      "emailAddress":"Lizzy.t@foo.com",
      "displayName":"Lizzy Rughani",
      "active":true,
      "timeZone":"Asia/Kolkata"
    },
    "subtasks":[

    ],
  }
}

I'm interested in three nested values here which I'm trying to fetch via following expressions

issueDataMap = {LinkedHashMap@4867}  size = 3
 "name" -> "#payload['fields']['summary']"
 "description" -> "#payload['description']"
 "text3" -> "#payload['key']"

I get this error when the expression is applied

org.springframework.expression.spel.SpelEvaluationException: EL1012E: Cannot index into a null value

Here's how I get the payload in the as argument to my transformer

@Transformer
public Map<String, Object> generateCardData(Map<String, Object> payload,
                                            @Header("X-UPSTREAM-WEBHOOK-SOURCE") String projectId) {

followed by

StandardEvaluationContext evaluationContext = evaluationContextFactory.getObject();

and here's how I evaluate it

new SpelExpressionParser().parseExpression(issueDataMap.get(key)).getValue(
                            evaluationContext, payload, String.class)));

I have the app annotated with @SpringBootApplication and @EnableInegrationand I autowire an instance of IntegrationEvaluationContextFactoryBean to get the StandardEvaluationContext

I also tried the variant

issueDataMap = {LinkedHashMap@4867}  size = 3
 "name" -> "payload['fields']['summary']"
 "description" -> "payload['description']"
 "text3" -> "payload['key']"

but then I get

EL1008E: Property or field 'payload' cannot be found on object of type 'java.util.LinkedHashMap' - maybe not public or not valid?
Anadi Misra
  • 1,925
  • 4
  • 39
  • 68

1 Answers1

1

First of all it is not clear why would one use SpEL in the code manually, when you have full access to the object. Plus you should keep in mind that create StandardEvaluationContext, parse an expression and evaluate it on every single call is kinda an overhead by performance. You probably just need to change your generateCardData() signature to accept a result of the expression instead of the whole map. See @Payload.expression attribute.

Anyway this is not what you would like to hear for your problem. And it is here: getValue(evaluationContext, payload, String.class))). The root evaluation object is your payload - a Map. So, what you just need to assume in your expression definition that you get access to that root object. Therefore expressions must be like this: fields.summary, description, key.

You typically see in the docs and samples a payload (or header) as a first token in the expression. That is just because Spring Integration uses a Message as a root object for expressions to evaluate.

Now in regards to performance. Even if your logic to select an expression by some key at runtime (issueDataMap.get(key)), you still could parse it only once.

Artem Bilan
  • 113,505
  • 11
  • 91
  • 118
  • While I understand your concern, the reason I can’t put annotations is because the payload will vary for different kinds of upstream, now we either write one transformer with its annotation every time a new upstream is added and go through the build, test, deploy, release cycle or make it configurable. I’d runtime hit any given day to avoid that cycle for every upstream I have to add to this system. – Anadi Misra Jan 25 '21 at 16:36
  • Understood. Anyway that `issueDataMap` could come with pre-parsed expressions. Is anything else not clear from my answer? – Artem Bilan Jan 25 '21 at 16:45
  • Yup noticed that, I’d refactor, coming to the original question, it’s clear, I misinterpreted the expression language. – Anadi Misra Jan 25 '21 at 17:02