Skip to Content

Today, I’m going to make your life as integration developer a whole lot easier. Whenever there’s a complex transformation requirement, for instance combining rows from an excel sheet into one object by a common identifier, Scripting is a great way to tackle the issue. SAP offers two flavors of scripting – JavaScript and Groovy.

The only downside is that the SAP Cloud Platform Integration WebUI does development environment. So, basically, it is like developing whilst blindfolded and then hoping for the best during deployment and runtime. As an integration developer you want a little more than syntax highlighting. You at least want to have syntax checks, code completion and the ability to test messages easily without deploying entire iFlows and executing them.

Fortunately, there are some clever people at the SAP community, who can show us how to setup a development environment for those CPI scripts.

I am going to build upon their amazing work and equip you with a custom iFlow and a Postman request that allows you to “hunt” for the Java-Libraries residing on your own Cloud Platform Integration tenant. You will need them to be able to execute your groovy script from eclipse.

Find out how my custom iFlow finds java libraries on your CPI tenant on Vadim’s amazing blog post.

Also, Eng Swee produced a fantastic write up on how to start your first functional tests with a groovy script.

Ready to groove?

So let’s say you’ve installed Eclipse Neon, the groovy plugin and added Eng Swee’s initial scripts for message processing and running the test. Then, you notice that the project produces errors due to missing dependencies. This is what we need to fix.

Have a look at my slightly adapted demo structure below, which I am going to refer to from hereon.

Message processing script “src/com/convista/groovydemo/Script1.groovy”:

package com.convista.groovydemo
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;

def Message processData(Message message) {
    //Body 
       def body = message.getBody();
       message.setBody(body + "Body is modified");
       //Headers 
       def map = message.getHeaders();
       def value = map.get("oldHeader");
       message.setHeader("oldHeader", value + "modified");
       message.setHeader("newHeader", "newHeader");
       //Properties 
       map = message.getProperties();
       value = map.get("oldProperty");
       message.setProperty("oldProperty", value + "modified");
       message.setProperty("newProperty", "newProperty");
       return message;
}

Note: If you run into encoding problems, for example, with messages, created by the csv converter, consider adding the java.lang.String class to your get-method (e.g. message.getBody(java.lang.String)).

And the script “Tester.groovy” that executes the actual test. This piece simulates the message passed by the CPI framework so to say.

package com.equalize.groovy.testing

import com.sap.gateway.ip.core.customdev.processor.MessageImpl
import com.sap.gateway.ip.core.customdev.util.Message

// Load Groovy Script
GroovyShell shell = new GroovyShell()
def script = shell.parse(new File("src/com/convista/groovydemo/Script1.groovy"))

// Initialize message with body, header and property
Message msg = new MessageImpl()
msg.setBody(new String("Hello Groovy World"))
msg.setHeader("oldHeader", "MyGroovyHeader")
msg.setProperty("oldProperty", "MyGroovyProperty")

// Execute script
script.processData(msg)

// Display results of script in console
println("Body:\r\n" + msg.getBody())

def displayMaps = { String mapName, Map map ->
	println mapName
	map.each { key, value -> println( key + " = " + value) }
}
displayMaps("Headers:", msg.getHeaders())
displayMaps("Properties:", msg.getProperties())

The blogs, mentioned above, walk you through the dependency problem with SAP’s standard class “Message” and explains, in detail, how to solve it.

So: “What are we doing here then?”

Vadim and Eng Swee left you with a very cumbersome approach to retrieve the java libraries. I’d like to show you a much more integrated way of doing this without comprising Eng Swee’s goal of teaching a man to fish instead of simply giving him a fish to eat, so he has the skills to feed himself going forward.

What else do you get?

Vadim’s and Eng Swee’s approach involves executing your incomplete groovy project, identifying an error relating to a missing dependency in eclipse, and using that missing class name on an additional groovy script, which you need to deploy on CPI to download the corresponding java library from your tenant. Once you have the java library file you can add it to your class path and re-execute your project to find the next missing class. For this next one you need to alter the groovy script on the iFlow and re-deploy.

Ugh, that is quite a lot of work, because you currently need to download ten java library files to get a complete set and the deployment times of the iFlow can take several minutes. It can easily take 20 minutes to complete them. So that is why I decided to develop an iFlow, which can do all of this in one go. You can download it here.

To achieve what I wanted I needed the ability to pass the java class name dynamically without hard coding it on a groovy script. That way we can circumvent the re-deployment process. So, to achieve that, I use an https connector that allows the forwarding of the HTTP query string parameters. SAP is running the Apache Camel server for that reason. You can read more about this here.

A couple of weeks ago my colleague Dominic Beckbauer gave you an introduction on how to use Postman for testing your iFlows. You are going to need that knowledge now.

As you can see the URL for my iFlow https endpoint requires the following pattern:

/external/trigger?lib=<java class name>

The yellow part is required for the Camel server, the blue part addresses the https endpoint of the iFlow and the red part is the name of the query string variable, which can be accessed on the iFlow later on. Check it out below:

You can access the query string parameters by reading the header “CamelHttpQuery”. I decided to parse it by applying a substring to actually get the value of the parameter (lib=some.java.class.name).

I feed the result into Vadim’s script logic that utilizes the Java API to lookup the jar file, which contains the class name. The result is then base64 encoded and added as a string attachment to an iFlow message.

For the convenience of the developer I added the jar file name including its version as the title of the attachment.

Finally you need to copy the base64 encoded string and create a jar file from it. I decided to do this by running another groovy script once more. As preparation you need to copy the encoded string into a text file and put it on the libs folder of my demo project. You can also download it from my GitHub repository. The script is called Base64Decoder.groovy. Add the jar file to your project’s build path, try to run the groovy script “Tester” once again and start the process of “hunting” for more missing jar files again.

Once you are done “hunting” for all the SAP jar files and added all of them to your build path, you can finally execute tests with your groovy scripts:

That wasn’t too hard, was it?

Eng Swee Yeoh suggests you take the testing approach even further. If you want to delve further into Test Driven Development with your groovy scripts take a look here.

Before concluding, I would like to emphasize the power of creating message logs (with attachments) with groovy scripts. In our case we used it to output the content of the jar files from the CPI tenant. But, of course, possible use cases for custom logging are only limited by your imagination and implementation needs.

Final Words

So I’ve introduced you to the setup procedure and told you where to find instructions on how to setup your groovy development environment in eclipse to get started with groovy scripting for your CPI iFlows. I also showed you how to download the required java libraries more easily and quicker than my SAP community colleagues Vadim and Eng Swee suggest.

Next time I am going to combine the topics of our blogs, which we posted so far and talk about end-to-end testing of your iFlows. So stay tuned for more.

As always feel free to leave comments and ask lots of follow up questions.

Thanks

Martin

To report this post you need to login first.

5 Comments

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

  1. Eng Swee Yeoh

    Hi Martin

     

    Nice to see your blog expanding on Vadim Klimov and my ideas (but does the title really have to be in all UPPER CASE??)

     

    It’s good to know that you understood our intention of just highlighting the fundamental concepts, and then leaving the rest to the imagination of developers like yourself. IMHO, to appreciate automation, one needs to have experienced how things are done “the hard way” 😉

     

    A few comments on your approach:-

    1) You can further bypass the Base64 encoding/decoding steps by setting the byte content of the file into the message body. With this you can use the “Send and Download” feature in Postman to directly retrieve the binary contents of the JAR files. If you want the filename, you can additionally store it back in the HTTP header of the response.

     

    2) While MPL attachments are indeed useful – take note that SAP have repeatedly cautioned against using it unnecessarily – see here and here.

     

    Regards

    Eng Swee

    (0) 
    1. Martin Pankraz Post author

      Hi Eng Swee,

      Thanks for your input. I drafted the blog in a different place where the styling of titles need to be “shouting” ;-). When I copied from there I forgot about the styling. So thanks for pointing that out.

       

      Good point to enhance the base64 encoding step by moving it also into the message. I was simply itching to do some groovy instead.

       

      Kind regards

      Martin

      (0) 
  2. Vadim Klimov

    Hello Martin,

     

    Very nice blog – thank you for sharing it with the community in a detailed way, clear and easy to follow every step you walk through, as well as it presents a good example of technique applied in action and its practical utilization and usability.

     

    I agree with Eng Swee Yeoh in part that our posts were not intended to provide a ready to run packaged solution, as only imagination and creativity of the developer being put into context of a specific use case / requirement, will be boundary of practical usage of the described techniques – Eng Swee provided reference examples on technique as a core, so that it can be wrapped and bundled into a specific case and tailored to specific needs. This brings us to a variety of options on how to:

    • invoke functionality. For example expose it as an API and call it via Postman or its equivalents, or build user-friendly UI on top of it, or embed libraries retrieval logic into scripts of dependency management system, and
    • digest and process output. For example, save it to local file, or upload to central binaries repository for fellow developers’ usage across the team. Or, as a more advanced option, to build script that will download updated versions of libraries when CPI tenant gets updated – to keep your libraries in sync with those used by CPI tenant’s runtime. Here it is worth referring to work already done by Morten Wittrock and CPI Tracker that he developed and shared in blog series – here, here and here, as it can be used as a core to track CPI updates and use it as a trigger for required actions – such as earlier downloaded libraries’ refresh on client side.

    In certain cases, it was also driven by non-technical factors – such as legal / end user license agreement – cases, where it might have been considered as violation of EULA articles, techniques were described in very high level and agnostic way (note disclaimer that I put at the beginning of several blogs – it was actually put for this specific reason).

     

    Regarding the script, to protect it from issues caused by incorrect URL constructed by the consumer of the flow, the check against query parameter name might become handy, so that class name retrieved from the expected query parameter. This is to avoid situation if somebody will enter URL with multiple parameters for some reason, where they shouldn’t have done so, and the script will get executed with some multi-parameter query.

    String queryParameterName = 'lib';
    
    if (httpQuery) {
       def queryParameters = [:];
       def queryParameter;
    
       httpQuery.split('&').each {
          queryParameter = it.split('=');
          queryParameters.put(queryParameter[0], queryParameter[1]);
       }
    
       className = queryParameters[queryParameterName];
    }

    The above logic assumes the required query parameter name is ‘lib’, but obviously it can be replaced with any other reasonable query parameter name of your choice (I have chosen ‘lib’ to follow URL you passed in the request and captured in Postman screenshot).

     

     

    Regards,

    Vadim

    (1) 
    1. Martin Pankraz Post author

      Hi Vadim,

      Thanks a lot for your thorough comment. You guys made it easy to build upon your great research to bundle something usable. Furthermore your thoughts on the topic really allowed a deep insight of the workings of the groovy related topics and how the cpi messages can be manipulated.

      Your suggestion for enhancing the example script will increase the robustness. So I will give it another look. But there is always something that can be added, isn’t there? 😉

       

      Kind regards

      Martin

      (0) 
      1. Vadim Klimov

        That’s very true, Martin – I agree, there shall be a certain point, when the one makes full stop and releases findings to community, otherwise enhancements, tweaks, introduction of extra features and functionality might go forever, and will finally quite significantly deviate from the original scope, and even distract from the main subject. And all other ideas can be put in new blogs or blog series – so keep on exploring, developing and sharing your thoughts and findings, those are valuable inputs and appreciated efforts!

        Regards,

        Vadim

        (1) 

Leave a Reply