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.
Related
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.
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'm looking to use the Jython in a Java project I'm working on.
And I'm wondering 2 things.
Can I disable access to Java classes from Python scripts. IE. stop scripts from being able to do things like from java.util import Date?
And can I change the output stream that print "Hello" etc write to, so that I can redirect a scripts output for my implementation?
Or will I have to edit the actual Jython classes to disable and change this?
In order to restrict access to specific Java classes you could implement a custom class loader and register it to Jython:
this.pyInterpreter.getSystemState().setClassLoader(this.userCodeClassLoader);
If you are doing this because of security issues (disallow some actions on server machine that runs user code) you have to notice that Jython also provides built-in function implementations that won't be caught by your class loader:
Built-in Python function implementations
One additional approach is to analyze all imports in Python parse tree. I think it's better having more than one security measure:
String code = "import sys\n"+"from java.io import File\n"+"sys.exit()";
AnalyzingParser anPar = new AnalyzingParser(new ANTLRStringStream(code), "", "ascii");
mod tree=anPar.parseModule();
Visitor vis = new Visitor() {
#Override
public Object visitImport(Import node) throws Exception {
System.out.println(node);
return node;
}
#Override
public Object visitImportFrom(ImportFrom node) throws Exception {
System.out.println(node);
return node;
}
};
List<PythonTree> children=tree.getChildren();
for (PythonTree c : children){
vis.visit(c);
}
I need a way to execute a jruby script in a multi-threaded environment, where each execution of the script is passed a different java object.
Currently, I am able to run self-contained scripts using code like:
public class Executor {
public Executor() {
this.container = new ScriptingContainer(LocalContextScope.THREADSAFE, LocalVariableBehavior.TRANSIENT);
this.evalUnit = this.container.parse(getScriptContent());
}
public execute(HttpServletRequest request) {
this.evalUnit.run()
}
}
Which appears to perform well since the ruby script is parsed once in the constructor into the evalUnit and does not need to be re-parsed on each execution.
However, I want to be able to pass the request object to the script, but I cannot find the correct way to do that. There will be multiple simultaneous requests, so I don't think I can use this.container.put("$request", request), correct?
UPDATE
In JRuby 1.6 there is now a LocalContextScope.CONCURRENT which appears to be what I am looking for. From what I can tell, if I construct ScriptingContainer as new ScriptingContainer(LocalContextScope.CONCURRENT, LocalVariableBehavior.TRANSIENT) then I can call
container.getProvider().getVarMap().put("#request", request);
service.getEvalUnit().run();
and each thread will have its own #request value.
Am I understanding this usage correctly?
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?