For a Java application, is there a better way to support configuration files based on a custom DSL (such as relying on Groovy or Kotlin) than using JSR223 ScriptEngine's eval or compile methods?
Loading script's content as String and then using it in a code like that is working but maybe there are better and more efficient ways of doing it.
String type = "groovy"; // or "kts"
String script = "..."; // code of DSL script
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByExtension(type);
Compilable compiler = (Compilable) engine;
CompiledScript code = compiler.compile(script);
Object result = code.eval();
Actually as JMeter, you should save your compiled script in cache and use it:
CompiledScript compiledScript = compiledScriptsCache.get(newCacheKey);
Same for ScriptEngine
You can share a ScriptEngine and CompiledScript objects across threads. They are threadsafe. Actually, you should share them, as a single engine instance is a holder for a class cache and for JavaScript objects' hidden classes, so by having only one you cut down on repeated compilation.
Related
I'm working on a java spring app which I need to provide the ability to add new code without altering standard code.
My challenges:
Store "custom" code in a database
Inject code at execution time into existing standard code
Isolate custom code so that two users do not execute each others code.
In other words, what I am looking for is upon method execution, look to the db if user has custom code for this method, if no, execute standard code; if yes, inject code, execute modified method then dispose of custom code.
I've looked at AspectJ and Javassist. I have a process working with AspectJ but I can only get it to work if the custom code is in it's own jar and added at build/runtime. I haven't spent much time with Javassist but from the docs, it looks like i'm in the same boat.
Here's an extremely simplified example of what i'm looking to do.
#RestController
#RequestMapping("/test")
public class TestController {
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
String results = "";
results = "foo";
//<-- insert custom code here (if exists) each time method is invoked
results += "bar";
return results;
}
}
As for Java scripting support according to JSR-223, here is some introductory documentation for you.
By default the Nashorn JavaScript engine is contained in the JRE, but since Java 11 you get a warning that it is deprecated and will be removed in the future. This is not a big problem because you will still be able to use it and there are also other JSR-223-compatible scripting languages available.
This example shows how to use
JavaScript,
Groovy and
Lua
scripts from Java. Please make sure that you add the corresponding
Groovy (e.g. groovy-all-2.4.14.jar) and
Lua (e.g. luaj-jse-3.0.1.jar)
libraries to your classpath if you want to use the additional two languages and not just the built-in JS engine.
The scripts in each language demonstrate the same basics things, i.e. how to
call a method on a Java object (File.getCanonicalPath()) from the script,
how to convert a string to upper case,
how to repeat a string (in JS we have to define our own prototype method for that because Nashorn does not implement the repeat(n) method by itself).
package de.scrum_master.app;
import java.io.File;
import javax.script.*;
public class Application {
private static final ScriptEngineManager MANAGER = new ScriptEngineManager();
public static void main(String[] args) throws ScriptException {
listScriptingEngines();
System.out.println(runScript("JavaScript", "print(file.getCanonicalPath()); String.prototype.repeat = function(num) { return new Array(num + 1).join(this) }; (text.toUpperCase() + '_').repeat(2)"));
System.out.println(runScript("Groovy", "println file.canonicalPath; (text.toUpperCase() + '_') * 2"));
System.out.println(runScript("lua", "print(file:getCanonicalPath()); return string.rep(string.upper(text) .. '_', 2)"));
}
public static void listScriptingEngines() {
for (ScriptEngineFactory factory : MANAGER.getEngineFactories()) {
System.out.printf("Script Engine: %s (%s)%n", factory.getEngineName(), factory.getEngineVersion());
System.out.printf(" Language: %s (%s)%n", factory.getLanguageName(), factory.getLanguageVersion());
factory.getNames().stream().forEach(name -> System.out.printf(" Alias: %s%n", name));
}
}
public static String runScript(String language, String script) throws ScriptException {
ScriptEngine engine = MANAGER.getEngineByName(language);
// Expose Java objects as global variables to script engine
engine.put("file", new File("/my/" + language + "/test.txt"));
engine.put("text", language);
// Use script result for generating return value
return engine.eval(script) + "suffix";
}
}
If you run the program with groovy-all on the classpath, the output should look like this:
Script Engine: Groovy Scripting Engine (2.0)
Language: Groovy (2.4.13)
Alias: groovy
Alias: Groovy
Script Engine: Luaj (Luaj-jse 3.0.1)
Language: lua (5.2)
Alias: lua
Alias: luaj
Script Engine: Oracle Nashorn (1.8.0_211)
Language: ECMAScript (ECMA - 262 Edition 5.1)
Alias: nashorn
Alias: Nashorn
Alias: js
Alias: JS
Alias: JavaScript
Alias: javascript
Alias: ECMAScript
Alias: ecmascript
C:\my\JavaScript\test.txt
JAVASCRIPT_JAVASCRIPT_suffix
C:\my\Groovy\test.txt
GROOVY_GROOVY_suffix
C:\my\lua\test.txt
LUA_LUA_suffix
Of course in your own code you should replace the inline JS, Groovy or Lua code with something user-specific loaded from your code snippet database. You should also think about which variables you want to expose to the scripting engine and document it for your users.
So in a lot of AWS Lambda tutorials, it teaches us to write a few lines of code, package it, and upload it.
Is there a code example where you can just trigger/call the lambda in your current project using the ARN or something? My current project is huge and I can't/it's not preferable to upload the function package to AWS Lambda, I just want to trigger it in my current code.
One link I found is: https://aws.amazon.com/blogs/developer/invoking-aws-lambda-functions-from-java/ but it does not work for me so far.
Apologies if it's been asked already; I didn't find anything useful to me.
EDIT:
My problem is the lambda function only gets invoked because I've uploaded it as a JAR (ie. its not a part of my main project, I just did it as a test), but I want to write the code to be invoked in my main project. I don't know how to invoke the lambda in my Java code. Like #MaxPower said, perhaps I have this all wrong and this is not possible.
What I do is create an interface with the #LambdaFunction annotation.
public interface Foo {
#LambdaFunction(functionName = "LambdaName")
OutputObject doFoo(InputObject inputObject);
}
Then in the class that is to call the lambda I make a Lambda client
private final Foo fooCaller;
RunTest() {
ProfileCredentialsProvider lambdaCredentialsProvider = new ProfileCredentialsProvider("lambda");
AWSLambdaClientBuilder builder = AWSLambdaClientBuilder.standard().withCredentials(lambdaCredentialsProvider);
builder.setRegion("us-east-1");
AWSLambda awsLambda = builder.build();
LambdaInvokerFactory.Builder lambdaBuilder = LambdaInvokerFactory.builder();
lambdaBuilder.lambdaClient(awsLambda);
fooCaller = lambdaBuilder.build(Foo.class);
}
then when you want to call the lambda
fooCaller.doFoo();
ScriptEngineManager.getEngineByName looks up and creates a ScriptEngine for a given name.
Rhino registers itself as "js", "rhino", "JavaScript", "javascript", "ECMAScript", and "ecmascript"
Nashorn registers itself as "nashorn", "Nashorn", "js", "JS", "JavaScript", "javascript", "ECMAScript", and "ecmascript"
If I use a name like "js" which both Nashorn and Rhino have registered with, which script engine will be used? Will it use Nashorn on Java 8 and Rhino otherwise?
Looking at the JavaDoc for registerEngineName:
Registers a ScriptEngineFactory to handle a language name. Overrides
any such association found using the Discovery mechanism.
And also at the registerEngineName source code (note that nameAssociations is a hash map):
public void registerEngineName(String name, ScriptEngineFactory factory) {
if (name == null || factory == null) throw new NullPointerException();
nameAssociations.put(name, factory);
}
So, it seems that, for a given name, getEngineByName will return the script engine factory that was the last to be registered for that name.
As script engine factories are loaded through the ServiceLoader mechanism, the loading order will depend on the order that the service configuration files are enumerated by the relevant class loaders' getResources method.
For a default installation, all this does not matter too much as Java 8 only includes Nashorn, and Java 7 and earlier only include Rhino. If you would add an additional engine through the system class path, it will be loaded after the one loaded by the bootstrap/extension class loader, and thus take precedence.
Reading the code, registerEngineName is indeed deterministic, however the discovery mechanism is a separate thing (as implied by the JavaDoc), and it is non-deterministic, because it adds all the engines to a HashSet during discovery, and when asked for an engine by name, it just uses the first match it finds.
You can run into this if you install an updated Rhino ScriptEngine in Java 7 and request it by any of the usual names (js, rhino, etc).
But unless you do that, both Java 7 and Java 8 come with exactly one implementation, which answers to js, javascript, ecmascript, etc. As long as you don't ask for rhino or nashorn, it should work in both cases.
I have a script with utility methods I would like to access from my other script.
I load my script like this in my java code
static {
GroovyShell shell = new GroovyShell();
//This is the script that has the utility
groovyUtils = shell.parse(new InputStreamReader(MyJavaClass.class.getResourceAsStream("scripts/json/MyUtils.groovy")));
//This is the script that does thing
groovyScript = shell.parse(new InputStreamReader(MyJavaClass.class.getResourceAsStream("scripts/json/MyScript.groovy")));
}
I would like to expose the methods from MyUtils.groovy to be usable in MyScript.groovy (and also other scripts in the future)
There is a number of ways how you can achieve this.
You're talking about methods, so I guess you have a class in MyUtils.groovy.
In this case you can specify a Binding, e.g.
def myUtils = new MyUtils()
def binding= new Binding([ method1: myUtils.&method1 ])
def shell= new GroovyShell(binding)
shell.evaluate(new File("scripts/json/MyScript.groovy"))
In the above you can reference method1 in your script and you will end up invoking it on myUtils instance.
Another solution is to specify a script base-class, e.g.
def configuration = new CompilerConfiguration()
configuration.setScriptBaseClass('MyUtils.groovy')
def shell = new GroovyShell(this.class.classLoader, new Binding(), configuration)
MyUtils class must extend Script then; all its methods are available in scripts you parse using shell.
There are essentially multiple ways how to embed / run Groovy. These are quite often discussed while designing DSLs. You can take a look e.g. here, if you haven't searched for it before.
I'm trying to use Groovy to create an interactive scripting / macro mode for my application. The application is OSGi and much of the information the scripts may need is not know up front. I figured I could use GroovyShell and call eval() multiple times continually appending to the namespace as OSGi bundles are loaded. GroovyShell maintains variable state over multiple eval calls, but not class definitions or methods.
goal: Create a base class during startup. As OSGi bundles load, create derived classes as needed.
I am not sure about what you mean about declared classes not existing between evals, the following two scripts work as expected when evaled one after another:
class C {{println 'hi'}}
new C()
...
new C()
However methods become bound to the class that declared them, and GroovyShell creates a new class for each instance. If you do not need the return value of any of the scripts and they are truly scripts (not classes with main methods) you can attach the following to the end of every evaluated scrips.
Class klass = this.getClass()
this.getMetaClass().getMethods().each {
if (it.declaringClass.cachedClass == klass) {
binding[it.name] = this.&"$it.name"
}
}
If you depend on the return value you can hand-manage the evaluation and run the script as part of your parsing (warning, untested code follows, for illustrative uses only)...
String scriptText = ...
Script script = shell.parse(scriptText)
def returnValue = script.run()
Class klass = script.getClass()
script.getMetaClass().getMethods().each {
if (it.declaringClass.cachedClass == klass) {
shell.context[it.name] = this.&"$it.name"
}
}
// do whatever with returnValue...
There is one last caveat I am sure you are aware of. Statically typed variables are not kept between evals as they are not stored in the binding. So in the previous script the variable 'klass' will not be kept between script invocations and will disappear. To rectify that simply remove the type declarations on the first use of all variables, that means they will be read and written to the binding.
Ended up injecting code before each script compilation. End goal is that the user written script has a domain-specific-language available for use.
This might be what you are looking for?
From Groovy in Action
def binding = new Binding(x: 6, y: 4)
def shell = new GroovyShell(binding)
def expression = '''f = x * y'''
shell.evaluate(expression)
assert binding.getVariable("f") == 24
An appropriate use of Binding will allow you to maintain state?