Skip to Content
Author's profile photo Eng Swee Yeoh

Using Camel’s Simple in CPI Groovy scripts

Introduction

While working on CPI developments, you would inevitably have come across Camel’s Simple Expression Language. Whether you use it consciously or not, its presence is pervasive in most CPI integration flows, within elements like Content Modifier, Router, and many others. If you have not already recognised it, any dynamic placeholder using ${ } is essentially a Simple expression.

 

Get to know Camel’s Simple expression language in HCI by Morten Wittrock, as the title suggests is an excellent read to know more about Simple.

 

Simple meets Groovy

While Simple meets a lot of simple needs in CPI, Groovy scripting is still the way if you need more muscle power.

 

But what if you want to use Groovy and still leverage some of the built-in expressions that are already available in Simple. Meet SimpleBuilder, a Java-based API that is part of the Camel framework, which can be used to evaluate Simple expressions.

 

The most straightforward way to use the API is to first instantiate a SimpleBuilder object providing the expression to be evaluated.

SimpleBuilder builder = SimpleBuilder.simple('${camelId}')

The example above uses ${camelId} to retrieve the name of the CamelContext.

 

Subsequently, the expression is evaluated and the value is returned as a String.

String result = builder.evaluate(exchange, String)

 

Pretty simple (no pun intended) isn’t it?

 

Not exactly – notice that the evaluate() method requires an Exchange object as one of the input. Within a CPI Groovy script (sample below), you do not have direct access to the exchange, but only the Message object.

def Message processData(Message message) {
//	enter code here	
	return message
}

 

It’s not a bug, it’s a feature

If you managed to get a hold of the library file containing com.sap.gateway.ip.core.customdev.util.Message (maybe you bothered to read about it here), you might notice that the implementing class of the Message interface contains the Exchange object as one of its instance attributes.

 

Unfortunately, it is a private instance attribute, so you can’t really do much about it.

 

Or, can you??!!

 

One of the quirky things about Groovy is that it ignores the access modifier attributes. In layman terms, you can access private attributes from Groovy!

 

Whether it is a bug or a feature, I’ll leave that to you to debate and decide.

 

Moving on, this means we can access the exchange with just the code below (using Groovy’s dot notation):-

Exchange exchange = message.exchange

 

Putting it together

Okay, let’s put it all together and see what we can make out of it.

 

We put together all the logic into the following sample script and implement it in a CPI integration flow. The main intent is to evaluate a few built-in Simple expressions, and populate it into the message body.

 

Note: It is important that the Simple expressions be encapsulated in single quotes so that it is passed as-is. Using double quotes will cause them to be interpreted as GStrings (yes, they are really called that in Groovy!) rather than Simple expressions.

import com.sap.gateway.ip.core.customdev.util.Message
import org.apache.camel.Exchange
import org.apache.camel.builder.SimpleBuilder

def Message processData(Message message) {
	Exchange ex = message.exchange
	StringBuilder sb = new StringBuilder()
	
	def evaluateSimple = { simpleExpression ->
		SimpleBuilder.simple(simpleExpression).evaluate(ex, String)
	}
	
	sb << 'Camel ID: ' + evaluateSimple('${camelId}') + '\r\n'
	sb << 'Exchange ID: ' + evaluateSimple('${exchangeId}') + '\r\n'
	sb << evaluateSimple('${messageHistory}') + '\r\n'
	message.setBody(sb.toString())	
	
	return message
}

 

Once we execute the integration flow, the following content will be displayed in the message body. Noticed that we are now able to retrieve the details for the Camel ID, Exchange ID as well as the Message History.

Camel ID: CPI_SimpleBuilder
Exchange ID: ID-vsa3637487-43346-1521314171141-295-2

Message History
---------------------------------------------------------------------------------------------------------------------------------------
RouteId              ProcessorId          Processor                                                                        Elapsed (ms)
[Process_1         ] [Process_1         ] [sap-http:/simple                                                              ] [        73]
[Process_1         ] [to10801           ] [sap-pp-util://sender                                                          ] [         1]
[Process_1         ] [removeHeaders295  ] [removeHeaders[*]                                                              ] [         0]
[Process_1         ] [CallActivity_1_152] [setHeader[scriptFile]                                                         ] [         2]
[Process_1         ] [setHeader28418    ] [setHeader[scriptFileType]                                                     ] [         0]
[Process_1         ] [bean5828          ] [bean[ref:scriptprocessor method:process]                                      ] [        63]

Exchange
---------------------------------------------------------------------------------------------------------------------------------------
Exchange[###REPLACE_ME###	Id                  ID-vsa3637487-43346-1521314171141-295-2###REPLACE_ME###	ExchangePattern     InOut###REPLACE_ME###	Headers             {CamelHttpMethod=GET, CamelHttpPath=, CamelHttpQuery=null, CamelHttpUrl=https://xyz.hana.ondemand.com/http/simple, CamelServletContextPath=/simple, SAP_MessageProcessingLogID=AFrFfk8uQihDbdRTlyZEiOZd7qvr, scriptFile=simple.groovy, scriptFileType=groovy}###REPLACE_ME###	BodyType            org.apache.camel.converter.stream.InputStreamCache###REPLACE_ME###	Body                [Body is not logged]]

 

Conclusion

This illustrates the possibility of combining both Simple and Groovy together in a CPI development.

The initial driver behind this was the need to access Camel’s Message History. When the integration flow becomes more complex, the traversal path and point of failure are not always immediately visible. This could also be achieved with a Content Modifier with the relevant Simple expressions followed by a Groovy script, but I wanted to combine them in a single step.Fortunately, such custom development is no longer required since a similar looking history has now been introduced for troubleshooting messages in WebUI.

One last note of caution regarding the bug/feature of Groovy – as always, use such undocumented feature wisely especially in a productive environment.

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Morten Wittrock
      Morten Wittrock

      Really cool stuff, as usual 🙂 And thank you for the link to the Simple blog. The Camel framework in CPI has been updated quite a few times since then, so I should probably revisit the topic soon.

      Author's profile photo Eng Swee Yeoh
      Eng Swee Yeoh
      Blog Post Author

      Thanks for the comment. This piece of work never went productive in the end, but it was a good learning experience into the inner workings of CPI and Groovy 🙂

      Author's profile photo Martin Koch
      Martin Koch

      Great stuff!

      You saved me a lot of time

      THX

      Author's profile photo Eng Swee Yeoh
      Eng Swee Yeoh
      Blog Post Author

      Glad to hear that this was helpful - curious what you used it for?

      Author's profile photo Martin Koch
      Martin Koch

      We developed a Cross Tenant CPI Alerting and Monitoring Solution with SMS Alerts, Mobile Client and Content Search that will be available in the SAP App Center within the next days...

      Author's profile photo Brecht Bauwens
      Brecht Bauwens

      Hi Eng Swee Yeoh,

      ${camelId} is returning the iflow/artifact id, is there also a runtime variable returning the name of the artifact? I'm not able to find it in any documentation.

      Thx!

      Author's profile photo Navdeep Singh
      Navdeep Singh

      Hi Brecht Bauwens , try with ${camelContext.getName}