Skip to Content
Technical Articles
Author's profile photo Bhalchandra Wadekar

Groovy Scripters, this was “def”initely a head-scratcher!!!

Hello Groovy Scripters,

I believe, Groovy has grown in popularity with the popularity of SAP Cloud Platform Integration Suite. Gartner naming SAP a Leader in 2020 Magic Quadrant for EiPaaS only adds to the popularity of SAP Cloud Platform Integration Suite, and by extension, that of Groovy. We have more content on Groovy Scripting in CPI than ever before with the release of Developing Groovy Scripts for SAP Cloud Platform Integration by Vadim Klimov and Eng Swee Yeoh along with countless blogs on Groovy on SAP Community and the wider internet.

Well, we better get well versed with all the best practices in Groovy Script.

Setting the Stage

Recently, I came across this script for mapping E1EDK01-ZTERM:

def String getTermsOfPaymentKey(String paymentTerms, String creditDays) {

	// subtract the length of credit days from 3
	creditDaysLength = 3 - (creditDays.length());

	// substring paymentTerms
	termsOfPaymentKey = paymentTerms.substring( 0, creditDaysLength + 1);

	// concatenate creditDays to the substring of paymentTerms
	termsOfPaymentKey = termsOfPaymentKey.concat(creditDays)

	return termsOfPaymentKey;
}

The script is pretty simple.

Example calls are like:

getTermsOfPaymentKey('B007', '7');
getTermsOfPaymentKey('B007', '21');

The Script receives (default) paymentTerms (taken from customer master) in the form of B000, where 000 is a number of credit days and creditDays for a given order. The credit days for the order can be different from that in customer master.

Then, the script removes as many characters from paymentTerms as the length of creditDays in the variable termsOfPaymentKey.

Finally, the script concatenates creditDays to termsOfPaymentKey and returns termsOfPaymentKey.

So, for the two inputs above, the expected output is B007 and B021 respectively.

assert getTermsOfPaymentKey('B007', '7') == 'B007'
assert getTermsOfPaymentKey('B007', '21') == 'B021'

And, we get the output as expected when I run them in the GroovyIDE. Well then, the blog is over. Ummmm, not quite.

Ummmm, not quite

Corrodias disagree in their answer to What is the difference between defining variables using def and without? on StackOverflow.

You see, the script does the intended job except when it comes to performance. How so?

Have a closer look at these two lines:

// subtract the length of credit days from 3
creditDaysLength = 3 - (creditDays.length());

// substring paymentTerms
termsOfPaymentKey = paymentTerms.substring( 0, creditDaysLength + 1);

The catch here is that they don’t have def or type declaration. By definition, the variables creditDaysLength and termsOfPaymentKey are binding variables. Binding variables can be altered from outside the script object or created outside of a script and passed into it [5]. They are not supposed to be used in a multi-threaded context [5].

This means that when a lot of orders are sent to this mapping, it may produce results like:

B07

B0021

B007

B021

Because the script runs in separate threads at runtime, however, the variables are shared.

Let’s check the execution when the calls are made in separate threads.

Thread 1 is called with B007 and 7. Thread 2 has inputs B007 and 21.

Thread 1 (‘B007′,’7’)
Thread 2 (‘B007′,’21’) creditDaysLength termsOfPaymentKey
creditDaysLength = 3 – (creditDays.length()); 2
creditDaysLength = 3 – (creditDays.length()); 1
termsOfPaymentKey = paymentTerms.substring( 0, creditDaysLength + 1); B0
termsOfPaymentKey = termsOfPaymentKey.concat(creditDays) B07
return termsOfPaymentKey; B07
termsOfPaymentKey = paymentTerms.substring( 0, creditDaysLength + 1); B0
termsOfPaymentKey = termsOfPaymentKey.concat(creditDays) B021
return termsOfPaymentKey; B021

In this run, it goes wrong when creditDaysLength is set to 1 by another thread. In the weirdest of runs, we might get B021 or B0021 for the input of B007 and 7. ?. In most cases, the thread gets the full attention of the CPU and the function returns the expected result. Sometimes, CPU gets to pull one over on us developers.

The Fix

The fix is simple. Unless you have a very specific reason to share the value of the variable after the script execution has finished, always use def or type declaration. When the script is written with the def declaration, it produces consistent and expected results even when under stress.

def String getTermsOfPaymentKey(String paymentTerms, String creditDays) {

	// subtract the length of credit days from 3
	def creditDaysLength = 3 - (creditDays.length());

	// substring paymentTerms
	def termsOfPaymentKey = paymentTerms.substring( 0, creditDaysLength + 1);

	// concatenate creditDays to the substring of paymentTerms
	termsOfPaymentKey = termsOfPaymentKey.concat(creditDays)

	return termsOfPaymentKey;
}

Do you know a variable we commonly used that is a binding variable? It is usually found when logging the body.

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

def Message processData(Message message) {
    
    def messageLog = messageLogFactory.getMessageLog(message)
    messageLog.addAttachmentAsString('Body', message.getBody(String), null)
    
    return message
}

The famous binding variable is messageLogFactory. It is defined at the parent class that runs the execution of every Groovy script in CPI and passes the object via a Binding [4].

For the curious one’s, we ended up dropping the script completely and used standard functions like so:Terms%20of%20Payment%20Key%20Mapping

Terms of Payment Key Mapping

We take the first letter of Payment Terms, format the Credit Days using the format 000 and concatenate the two together to get the terms of payment key.

It was fun for me to figure this one out. Hopefully, you had just as much fun reading the blog. Not every mapping can be solved using standard mapping, so, knowing how Groovy works will definitely help you.

Hope this helps,
Bala

References

  1. Question: What is the difference between defining variables using def and without?
  2. Corrodias‘s answer to What is the difference between defining variables using def and without?
  3. Avoid Binding Variables in Groovy Scripts by Markus Muenkel
  4. Eng Swee Yeoh‘s suggestion
  5. Groovy Documentation on Binding class

Further Readings

Assigned Tags

      4 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Eng Swee Yeoh
      Eng Swee Yeoh

      The specific term for it is called a binding (or binding variable) rather than global variable. The following blog describes more about it.

      https://blogs.sap.com/2017/06/22/avoid-binding-variables-in-groovy-scripts/

       

      messageLogFactory is not really a global variable that is accessible by separate threads. If it were, then all the MPL attachments would be messed up as many threads are accessing it. Instead, it is defined at the parent class that runs the execution of every Groovy script in CPI, and passes the object via a Binding.

      Author's profile photo Bhalchandra Wadekar
      Bhalchandra Wadekar
      Blog Post Author

      Thank you Eng Swee Yeoh.

      Sanjali Salian told me this was documented by SAP before, however, I couldn’t find it.

      I have added the reference and also updated the text. Please let me know if any further suggestions.

      Author's profile photo Fatih Pense
      Fatih Pense

      Thank you for sharing this post, Bhalchandra Wadekar!

      It is something very easy to fix once you know the reason. But if you have no knowledge about this mechanism it can cost a lot of development time & lost hair. I know feel vaccinated for this specific bug.

      Best regards,
      Fatih

      Author's profile photo Allen Chew
      Allen Chew

      Nice blog. Great help and good practice to follow for all grookies 😉