Skip to Content
Technical Articles

Rapid Groovy Scripting in CPI: Tips to Improve Developer Productivity when Rerunning Dynamically Loaded Script

Intro

Earlier this year, I published a blog post where it was described how a generic wrapper script step that makes use of Groovy Shell can be employed to execute dynamically submitted code snippets of Groovy scripts in an iFlow of SAP CPI. Motivation behind demonstration of that technique was built upon thinking that such approach can become convenient for one time execution of required Groovy scripts by a runtime node of CPI, and it was illustrated with the help of a minimal toolset:

  • An IDE – to develop Groovy scripts,
  • An HTTP client – to send Groovy script to an endpoint of an iFlow,
  • A deployed iFlow with a Groovy script step that implements a generic wrapper and expects dynamically submitted script code that is to be passed to it.

During earlier demonstration preparation, I used IntelliJ IDEA as an IDE and Postman as an HTTP client: Groovy script was developed in IntelliJ IDEA where corresponding required dependencies (such as some Camel and CPI dependencies) had been added, and after a script was ready, it was copied to a body of a request in Postman, the request was sent to the endpoint of the iFlow that contained only one step – a Groovy script step that implemented the described wrapper logic, and finally a dynamically submitted script got executed by a runtime node.

That approach can become useful in certain use cases, but it has a shortcoming: a developer needs to use two different tools – an IDE and an HTTP client – and switch between them. Consequently, a Groovy script has to be copied from an IDE to an HTTP client manually. It doesn’t take significant effort, but when performing minor tweaks to a code snippet and being in need of re-executing adjusted versions of a request rapidly and many times, this sequence of steps can cause copy-paste errors eventually.

Wouldn’t it be nice to combine two tools and let a developer avoid switching between an IDE and an HTTP client back and forth? In that case, shall we bring code editing capabilities to an HTTP client or find a way to execute HTTP requests from within an IDE? A first option (enrich an HTTP client with advanced code editing capabilities) doesn’t appear to be optimal, but the latter one does so. In this blog post, I will focus on developer productivity and will illustrate how developer experience can be improved by invoking an HTTP client from within an IDE and using it to send HTTP requests to CPI. Given that HTTP requests that we are going to send, are relatively simple, we will not need advanced HTTP capabilities, and will see two sample approaches in action:

  • Usage of an HTTP client utility with a command line interface (CLI) that is invoked in a terminal from within an IDE,
  • Usage of a custom developed simple HTTP request generator that is implemented as a Groovy script.

 

Environment and tools

Before we get to demonstration of mentioned approaches, let us set the scene.

Throughout this blog post, all Groovy development will be performed using IntelliJ IDEA. IntelliJ IDEA provides a convenient and feature-rich environment and tooling for Groovy development: code completion, syntax check, code refactoring capabilities, integration with version control systems, dependency management systems, static code analysis tools are just few to name. These features and many more others shall help a CPI developer make Groovy development more comfortable and productive – though, obviously, IntelliJ IDEA is by no means the only IDE that CPI developers might get used to when developing Groovy scripts.

For reference purposes, corresponding project’s structure is provided on a screenshot below, and project artifacts that will matter, are highlighted in red colour:

In sake of demonstration, a Groovy script that is intended to be executed within the iFlow by a runtime node, has been placed to a file script.groovy. As soon as the script is adjusted and saved, we are ready to submit it in an HTTP request, should we want to get it executed by a runtime node.

A corresponding iFlow has been developed and deployed to a CPI tenant. To pass a Groovy script to the iFlow, the code snippet has to be placed in a body of a HTTP POST request that is sent to the endpoint of the iFlow. Details of composition of the iFlow can be found in the earlier mentioned blog post.

cURL will be used as a CLI HTTP client.

 

High level overview of described approaches is depicted on an illustration below:

 

Approach 1: Execute cURL from a terminal in IntelliJ IDEA

A terminal window can be accessed in IntelliJ IDEA in several ways:

  • Menu: View > Tool Windows > Terminal,
  • Keyboard shortcut: Alt + F12,
  • Terminal window tile in tools area (by default, in the bottom),
  • Using tools icon (by default, in the bottom left) and choosing ‘Terminal’.

In a terminal window, cURL can be executed with required parameters and response that is generated by the iFlow, is displayed in console output:

With the cURL command above, an HTTP POST request is sent to the specified endpoint of the iFlow, where request body is read from the provided script file in binary mode (argument –data-binary), and response – both headers (argument –include) and body – is output to a terminal.

Having code editor with the developed and tested Groovy script and a terminal window opened side by side on a single screen of IntelliJ IDEA provides the developer an ability to rerun the same cURL command over and over again as soon as corresponding adjustments to a script are completed and the script shall get executed by a runtime node.

 

Approach 2: Run a custom Groovy script

A custom Groovy script CPIDynamicScriptLoadingCaller.groovy which only purpose is to generate an HTTP request with a body that contains content of a specified file (Groovy script file), send it to a specified endpoint and output response status code, headers and body to a console, has been developed. Request configuration has been externalized and a corresponding properties file is used in sake of flexibility, exception handling is deliberately omitted in sake of simplicity of the script (any runtime exception will terminate script execution and exception details will be displayed in console output), no external dependencies or 3rd partly libraries are used in the script. Sample code snippet of such a Groovy script is provided below:

if (args.length < 1) {
    System.err.println('Mandatory command line argument is missing, script is terminating')
    System.exit(1)
}

// Retrieve request configuration parameters
File reqPropsFile = new File(args[0])
println("Request properties file: ${reqPropsFile.canonicalPath}")

if (!reqPropsFile.exists()) {
    System.err.println('Request properties file does not exist, script is terminating')
    System.exit(1)
}

List<String> mandatoryConnParams = ['url', 'method', 'user', 'password', 'script'].asUnmodifiable()

Properties reqProps = new Properties()
reqPropsFile.withInputStream { properties ->
    reqProps.load(properties)
}

if (!reqProps.keySet().containsAll(mandatoryConnParams)) {
    System.err.println('One or several mandatory request configuration parameters are missing, script is terminating')
    System.exit(1)
}

// Retrieve request body (script)
File reqBodyFile = new File(reqProps.script as String)
println("Script file:             ${reqBodyFile.canonicalPath}")

if (!reqBodyFile.exists()) {
    System.err.println('Script file does not exist, request body cannot be constructed, script is terminating')
    System.exit(1)
}

byte[] reqBody = reqBodyFile.bytes

// Prepare and send request to CPI iFlow endpoint
HttpURLConnection conn = new URL(reqProps.url as String).openConnection() as HttpURLConnection
conn.tap {
    requestMethod = reqProps.method
    setRequestProperty('Authorization', 'Basic ' + Base64.encoder.encodeToString([reqProps.user, reqProps.password].join(':').bytes))
    doOutput = true
    outputStream << new ByteArrayInputStream(reqBody)
}

// Parse and display response
println('-------------------------------------------------------------------------')
println('Response status code: ' + conn.responseCode)

println('----- Response headers --------------------------------------------------')
conn.headerFields.each { headerField, fieldValue ->
    fieldValue.each { value ->
        println(headerField + ': ' + value)
    }
}

println('----- Response body -----------------------------------------------------')
println(conn.inputStream.text)
println('-------------------------------------------------------------------------')

In order to use the script, it is necessary to provide a single command line argument that specifies the file where request configuration properties are maintained – in the demo, such a file is request.properties. Required (mandatory) request configuration properties are:

Property Description
url An endpoint of the iFlow to which an HTTP request shall be sent
method An HTTP method that shall be used in the request. In the demonstration, POST method will be used
user A username for authentication of the request at CPI
password A password of the user that is used for authentication of the request at CPI
script A full path and file name of the file that contains a Groovy script that shall be sent in a body of the request

A sample request properties file:

A run configuration for the developed script with indication of a path to a request properties file in program arguments:

Every time a Groovy script that we intend to send to the iFlow is ready to go, we can just rerun the sample custom script described above with an illustrated run configuration in a matter of a single click, and a corresponding HTTP request will be generated and sent to the endpoint of the iFlow:

 

Outro

With the help of a terminal that is available in IntelliJ IDEA and command line tools or a custom developed Groovy script, we can now rapidly push a tested CPI Groovy script to the generic iFlow and get it executed by a runtime node in few clicks.

Choosing between two demonstrated approaches, I personally prefer the first one that makes use of cURL in an IntelliJ IDEA’s terminal, but it is always nice to be aware of alternative methods. Moreover, both cURL and a custom script implementation were presented for illustration purposes only – it is very much possible that other appropriate CLI tools can be found, or different implementations of a required HTTP request generator in Groovy can be developed, that will turn to be more relevant to your needs and fulfil your requirements.

1 Comment
You must be Logged on to comment or reply to a post.