Rhino javascript runtime and IBM API Connect
When we started looking at DataPower a few years back XSLT was the only way to write translations.
Recently (and maybe someone can tell me when), gateway script became an option for developing DataPower translations.
We have had support for the XSLT that is used by DataPower and by the ACE/IIB/WMB XSLTransform node.?
Adding support for gateway script should also useful for the teams we support that make use of APIC/DataPower.
Also, I did find this really interesting LinkedIn post about the energy cost and impact for different programming languages.
In the case of gateway script, DataPower will be running JavaScript. It's 4x more costly then C, so anything we can do to help improve the code quality and performance?of gateway script is good for the environment. Also, with a big move to cloud and charge by usage, better code that is more efficient can help you save money in the long term on CPU costs,?as well as the intrinsic cost benefits of lowering your technical debt.
Taking into account the SonarQube ecosystem, SonarQube has support for Typescript and JavasSript as first class languages - meaning no additional plugins are needed and you can run the analysis straight out of the box.
The issue is, that when we analyze the a APIC file that contains gateway script code, we are starting with yaml file. So there's no current way to?leverage SonarQube's in built support for JavaScript as we don't have a JavaScript file (we have like a yaml file with some JavaScript and XSLT all merged together).
It would be interesting if some from SonarQube comes across this post and sends me the details of an API that does this. That would be a good read.?
So without first class support and without re-engineering the entire JavaScript plugin, I was looking for some way of?having some static code analysis without re-engineering the whole SonarQube offering.
What I thought would useful that would add value for developers is to be at least able to :
For (1), code checks, we have some already in place, such as APIC7 - Console.debug used in gatewayscript (APIC)
For (2) and (3) we can might need some coding magic.
JavaScript doesn't have a compiler per-se. Typescript has. So with Typescript you can have classes and interfaces that you can validate your code against at compile time to pick up errors.
But gateway script is JavaScript. So there is not compiler to validate the code.
There are some frameworks for working JavaScript code from java.
The first attempt was to embed Graalvm?
And then use the Graalvm library to parse and validate the JavaScript code that we extract from the yaml file.
It looked promising, but there are some quirks around how SonarQube loads classes and how Graalvm wants to be loaded.
SonarQube has something called a ClassRealm, which Graalvm can't load classes out of. So all the test cases worked perfectly, but then when it ran with a different classloader it failed.
I wasn't able to work out how to get around that issue. So I looked at another project - Rhino?
Rhino is a slightly older framework that has been super-seceded by Graalvm and probably others.
Again, I was able to use Rhino to to develop logic that would check the gateway script was syntactically correct using context.compileString()
To validate that the object references were at least valid signatures, I used script.exec()
This basically ran the logic and mocked out the gateway objects that were called.
The idea being if a mocked object was called then that suggests that at least the signatures were correct, but if the object didn't exist (no mock) or the method on the mock was missing,?then the signatures were incorrect. I was also able to run this from a test case.
But then again once I used the different class loader, Rhino broke.
So I created some dirty hacky code to get around this issue and made use of a separate classloader of a single jar that contained the Rhino classes.
File jarFile = new File("rhino-1.7.15-BCT-2-jar-with-dependencies.jar")
URL url = jarFile.toURI().toURL();
URL[] urls = {url};
ClassLoader cl = new URLClassLoader(urls);;
From there I could call Rhino from that separate classloader. Sort of.
Outside of the classloader and calls from inside the classloader involve different classes. To you and me they look the same but the JVM treats them as separate types.
So I couldn't use any classes to make calls when using the new custom classloader. I think it was because the compiled class that was being casted was different.
I ended up having to use reflection to instantiate new objects:
领英推荐
Class cls = cl.loadClass("org.mozilla.javascript.Context")
System.out.println(cls.getClass().getCanonicalName());
Object obj = cls.newInstance();
Even casting to these newly created instances had classloading issues.
So when to call any methods I again had to use reflection to find the method and invoke it with the appropriate arguments:
Method setMethod = mozillaJsContext.getClass().getMethod("setApplicationClassLoader", ClassLoader.class);
Object[] classLoaderArgs = {cl};
setMethod.invoke(mozillaJsContext, classLoaderArgs);
Far from ideal, error prone, type unsafe and slow. But it worked. So progress.
Next I had to create some mocks to represent gateway objects that scripts interact with:
So for the "apim" object, I created a
public class APIMWrapper extends ScriptableObject
and added methods such as?
@JSFunction
??public void setvariable(ScriptableObject thisObj)
and
@JSFunction
public Object getvariable(ScriptableObject thisObj)
Again, I hit some class cast issues, as the ScriptableObject that was created wasn't from the same classloader.
So I used a GSON trick and converted the object that I had into JSON and then recreated the same object with the running classloader.
I still had to know the class name I was working with, so there's a bit ugly if statement that does that, but again it works. Again, progress.
if (createName != null && createName.equals("au.com.bettercodingtools.sonar.messagebrokersonarplugin.apiconnect.wrappers.APIMWrapper")
{
?? proto = (Scriptable)?gson.fromJson(gson.toJson(o1), APIMWrapper.class);???????
})
Then when I called script.execute() I could see my mock object being created and the methods being called.
I did have a few other places where the class casting and instance of across the loaders failed.
It didn't throw an exception which was worse. The logic dropped out. But after some debugging it's working.
I ended creating a patched Rhino, with all the little tweaks and extra checks that lets it work. Which you can see here if you are interested.
The rule that we ended up adding to help with APIC validation is:
More information on our products and on pricing can be found on our website:
You can also reach me via email at:
Or contact me via the contact page on our website:
Regards
Richard
I help lead and protect Australian / New Zealand businesses and their customers | Leadership | Mentor | Speaker | Architect
2 年Awesome work Richard! keep these tutorials coming through