Skip to Content
Technical Articles
Author's profile photo Morten Wittrock

HCITracker is now CPITracker

Updated March 31st, 2021: Because the SAP Cloud Platform brand has been retired, SAP Cloud Platform Integration is now known as SAP Cloud Integration or just Cloud Integration. I’ve updated the name in the blog post, but I’m keeping the CPITracker name. The Script API version is no longer being tracked (for this reason), so I removed the reference to that. Also, please note that the described technique for determining the CPI build number only works in the Neo environment.

Back in March, I launched HCITracker, which is a Twitter feed that tracks updates to the core components of SAP Cloud Integration, such as the underlying Apache Camel framework, the Java runtime etc.

Officially, though, the HCI abbreviation has not existed since the HANA Cloud Integration name was dropped, so HCITracker needed a new name.

Better late than never, right? HCITracker is now CPITracker! If you were already following the account on Twitter, you don’t need to do anything; you are still following it under its new name. If you are not following it, well… you know what to do 🙂

About the CPI build number

A couple of people have asked me how I determine the CPI build number in CPITracker, in order to track how it changes over time.

That’s a really good question, and the answer is all about OSGi bundles. In a nutshell, a bundle is a collection of Java classes and configuration files, that you can dynamically install and uninstall as a unit. The SAP Cloud Integration service consists of hundreds of such bundles installed on your tenant.

CPITracker’s approach to finding the CPI build number was based on the idea, that at least one of these bundles would have a version number matching the build number. In the following, I will describe the steps I took to find such a bundle.

The first order of business is to get programmatic access to every installed bundle. How do we go about that? Take a look at the Bundle interface documentation. If we can get a Bundle object, we can call its getBundleContext() method to get a BundleContext object. The BundleContext interface has a getBundles() method, which returns all installed bundles.

So, the problem of accessing all installed bundles has been reduced to getting a single Bundle object. To that end, we can use the static method getBundle of the FrameworkUtil class, which takes a Class object, and returns a Bundle object representing the bundle containing that class (assuming it belongs to a bundle, of course).

Now the problem of accessing all installed bundles has been reduced to finding a Java class that belongs to a bundle. Here’s a likely candidate, that you interact with every time you add a script to an integration flow: com.sap.gateway.ip.core.customdev.util.Message.

Putting all this together, here’s a few lines of Groovy that iterates over each installed bundle:

def knownBundleClass = 'com.sap.gateway.ip.core.customdev.util.Message'
def entryBundle = FrameworkUtil.getBundle(Class.forName(knownBundleClass))
def bundleContext = entryBundle.getBundleContext()
bundleContext.getBundles().each { b ->
   // Process each Bundle object here
}

Now that we know how to access all installed bundles, it’s straightforward to find every bundle, whose version number matches the current CPI build number (which is 2.35.7 at the time of writing). The following complete Groovy script does exactly that, and builds an XML message body containing the bundles.

import com.sap.gateway.ip.core.customdev.util.Message
import groovy.xml.MarkupBuilder
import org.osgi.framework.FrameworkUtil

def Message matchingVersionBundles(Message message) {
   def sw = new StringWriter()
   def builder = new MarkupBuilder(sw)
   def matchingVersionBundles = getMatchingVersionBundles('2.35.7')
   builder.bundles {
      matchingVersionBundles.each { b ->
         bundle {
            id(b.getSymbolicName())
            name(b.getHeaders().get('Bundle-Name'))
            version(b.getVersion().toString())
         }
      }
   }
   message.setBody(sw.toString())
   return message
}

def getBundleContext() {
   def knownBundleClass = 'com.sap.gateway.ip.core.customdev.util.Message'
   def entryBundle = FrameworkUtil.getBundle(Class.forName(knownBundleClass))
   if (entryBundle == null) {
      throw new AssertionError("No OSGi bundle for class ${knownBundleClass}")
   }
   entryBundle.getBundleContext()
}

def getMatchingVersionBundles(String version) {
    getBundleContext().getBundles().findAll() { b -> 
        b.getVersion().toString() == version
    }
}

As I write this, the script generates the following output:

<bundles>
    <bundle>
        <id>com.sap.it.node.stack.profile</id>
        <name>Node Stack Profile Bundle for Stack iflmap</name>
        <version>2.35.7</version>
    </bundle>
    <bundle>
        <id>com.sap.it.node.stack.repository</id>
        <name>Node Stack Repsository Bundle for Stack iflmap</name>
        <version>2.35.7</version>
    </bundle>
</bundles>

I chose the com.sap.it.node.stack.profile bundle, and watched it over a period of time, to confirm that its version number does in fact match the CPI build number. The CPITracker code uses this bundle, and the technique described above, to determine the CPI build number.

Assigned tags

      3 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Bhaskar mamilla
      Bhaskar mamilla

      Good one.

       

      Bhaskar

      Author's profile photo Vijay Kapuganti Kumar
      Vijay Kapuganti Kumar

      Superb Morten 🙂 what are your upcoming projects 🙂

      Author's profile photo Morten Wittrock
      Morten Wittrock
      Blog Post Author

      Hi Vijay!

      Thank you 🙂 I do indeed have another project out. It's called CPILint, and it's an open source governance tool for SAP Cloud Platform Integration.

      Regards,

      Morten