HCITracker is a Twitter feed that informs you of updates to some of the infrastructure components of SAP Cloud Platform Integration, such as the Java VM and the Apache Camel framework. It is implemented as an integration flow, that posts to Twitter whenever it detects a change in one of the properties it tracks.

In this blog I’d like to share a couple of updates to HCITracker, and talk about two cool Groovy features, I used to implement them: Closures and dynamic programming.

Two new tracked properties

First off, HCITracker now also tracks the SAP Cloud Connector version and the Adapter Development Kit API version; two related products that I think quite a few HCI developers are interested in.

Unlike most of the other tracked properties, these two new ones are not extracted from the tenant, in which HCITracker runs. Rather, they are pulled from the SAP Development Tools page, specifically by parsing the page’s HTML source with the jsoup Java library.

Language variety via Groovy closures

Second, I wanted to introduce a bit of variety in how the tweets are phrased, instead of using the same pattern for each one. This is a good fit for one of the coolest features of the Groovy language: Closures.

A closure is basically a chunk of code, that is packaged up as an object. Closures can take parameters and return values, like a method. In the case of HCITracker, I use closures as sentence templates:

def template = { property, fromValue, toValue -> "$property has been updated from $fromValue to $toValue" }

Once you have a reference to a closure, you can execute it in one of the two following ways:

println template('The HCITracker version', '1.0', '1.1')
println template.call('The HCITracker version', '1.0', '1.1')

In my particular case, I want to choose a random closure to generate each tweet:

def messageClosures = [
   { property, fromValue, toValue -> "$property has been updated from $fromValue to $toValue" },
   { property, fromValue, toValue -> "$property has changed from $fromValue to $toValue" },
   { property, fromValue, toValue -> "$property, which was previously $fromValue, is now $toValue" }
]

5.times {
  // Choose a random closure...
  def template = messageClosures.get(new Random().nextInt(messageClosures.size()))
  // ...and call it
  println template.call('The HCITracker version', '1.0', '1.1')
}

Running it just now, I got the following output:

The HCITracker version, which was previously 1.0, is now 1.1
The HCITracker version has been updated from 1.0 to 1.1
The HCITracker version has changed from 1.0 to 1.1
The HCITracker version has changed from 1.0 to 1.1
The HCITracker version, which was previously 1.0, is now 1.1

Granted, a Pulitzer Price is not in the cards, but at least the tweets are a little more varied now 🙂

Adding methods with dynamic programming

In the above code, the part that chooses a random element from a list irks me a little bit. Shouldn’t the list itself be able to return a random element? The Array class in the Ruby programming language has a method that does exactly that: sample. Groovy, sadly, doesn’t. We can fix that, though, with Groovy’s dynamic programming model.

In a nutshell, dynamic programming lets us add new methods to existing classes on the fly. This is all there is to it:

ArrayList.metaClass.sample = { delegate.get(new Random().nextInt(delegate.size())) }

The delegate reference refers to the specific ArrayList instance that receives the call to the new sample method.

Having added the new method, the finished code looks like this:

// Add the sample method to the ArrayList class dynamically
ArrayList.metaClass.sample = { delegate.get(new Random().nextInt(delegate.size())) }

def messageClosures = [
   { property, fromValue, toValue -> "$property has been updated from $fromValue to $toValue" },
   { property, fromValue, toValue -> "$property has changed from $fromValue to $toValue" },
   { property, fromValue, toValue -> "$property, which was previously $fromValue, is now $toValue" }
]

5.times { println messageClosures.sample().call('The HCITracker version', '1.0', '1.1') }
To report this post you need to login first.

Be the first to leave a comment

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

Leave a Reply