Suppose I have a Javascript file
function js_main(args){
/* some code */
var x = api_method1(some_argument);
/* some code */
}
And I try to run it with javax.scripting the usual way
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval(...);
Now the I'd like to handle the call to api_method1 in Javascript with my Java class. I'd like to have some kind of mapping/binding of calls i.e. each time the script calls api_method1(arg) a method
public Object api_method1(Object arg){ ... }
(placed in the same class as the engine) would be called.
Can I achieve this?
use engine.createBindings() to make a Bindings object;
put an object exposing your method into the bindings with some name:
Bindings b = engine.createBindings();
b.put("api", yourApiObject);
engine.setBindings(b, ScriptContext.ENGINE_SCOPE);
Then in JavaScript there'll be a global "api" object you can call:
api.method1( "foo", 14, "whatever" );
The facility is easy to use, but be careful with what you pass back and forth; it doesn't do that much to convert JavaScript types to Java types.
Related
I am writing something that may be called workflow engine. For that I have created data model for the workflow as XML following specific XML Schema.
Below is an example of XML representing this data model:
<dm:agentModel xmlns:dm="ProcessObjects" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="ProcessObjects agentModel.xsd">
<dm:cfp isList="false" objName="myCFP"></dm:cfp>
<dm:proposal isList="true" objName="receivedProposals"></dm:proposal>
<dm:feedbackList objName="cfpFeedbacks">
<dm:item>
<dm:to>Andrew</dm:to>
<dm:from>Paul</dm:from>
<dm:heading>That is bad</dm:heading>
<dm:body>Fix points a, b and c, please.</dm:body>
</dm:item>
<dm:item>
<dm:to>Frank</dm:to>
<dm:from>Paul</dm:from>
<dm:heading>Very good!</dm:heading>
<dm:body>I see no drawbacks. You can also ask Matthew for additional feedback.</dm:body>
</dm:item>
</dm:feedbackList>
</dm:agentModel>
The workflow definition, which is defined by the user by the means of web editor is the BPMN XML standard. For not going too deep in the details, i need to give user possibility to define some scripting interface. User needs to be able to writhe something like:
/*JavaScript code*/
for(var i=0; i<agentModel.cfpFeedbacks.length; i++) {
if(agentModel.cfpFeedbacks[i].to == "Frank") {
agentModel.cfpFeedbacks[i].to += " Sinatra";
}
}
By now, I wrote Java class (DataModel) that can access data built from XML given above. Because XML may contain many different objects, there are getters and setters that looks like:
/*Java code*/
DataModel agentModel = new DataModel(xmlString);
agentModel.getValue("cfpFeedbacks[1].to");
//returns String "Frank"
agentModel.setValue("cfpFeedbacks[0].from", "Paul Anka");
//obvious
To run user-written script I am trying to use Java Scripting API
/*Java code*/
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
//binding object
engine.put("agentModel", agentModel);
String script = "var i = 0;"
+ "println(agentModel.getValue(\"cfpFeedbacks[\" + i + \"].from\"));";
engine.eval(script);
Which is more or less working. What I want to archieve is something like this working:
/*Java code*/
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
//binding object
engine.put("agentModel", agentModel);
String script = "var i = 0;"
+ "println(agentModel.cfpFeedbacks[i].from);";
/**
* any magic operations here
*/
engine.eval(script);
Goal is to provide easiest-possible interface for end users to write their scripts.
I am a little bit lost and I would be grateful for any inspiration. Personally I did consider three scenarios:
Creating Java-Bean style classes, and compiling them instead of working on XML
Parsing script string from second to the first form (which seems to be most easy, but time expensive and definitely not 'clean')
Developing some kind of magic interface to the object where calling object.field is synonym for calling object.getValue("field")
Maybe there is some obvious workaround I don't see.
Thanks in advance for any replies,
PS. If my description is unclear, or you find it is worth to provide more source code I'll clarify question immidiately. Getting it done is right now priority for me.
The best way is deserialize xml to java object and then just put result into engine.
You are able to direct operate on objects putted via engine.put(..) method
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
engine.put("a", 1);
engine.put("b", 5);
engine.eval("a = 2;");
Object result = engine.eval("c = a + b;");
System.out.println("a + b = " + result);
Finally get object from script engine and serialize object to xml, jackson will be useful
In Java 7 (1.7), I could access a Java method from JavaScript by running this:
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval("importClass(net.apocalypselabs.symat.Functions);");
jse.eval("SyMAT_Functions = new net.apocalypselabs.symat.Functions();");
String input = "notify(\"Foo\");"; // This is user input
jse.eval("with(SyMAT_Functions){ "+input+" }");
Which would run the notify() function from the Functions java class:
public class Functions {
private Object someObjectThatCannotBeStatic;
public void notify(Object message) {
JOptionPane.showMessageDialog(null, message.toString());
}
/* Lots more functions in here, several working with the same non-static variable */
}
How do I access the Functions class in Java 1.8 with the Nashorn engine? My goal is to run different code for the first snippet if the user has Java 1.8, while still allowing people with 1.7 to use the app.
I've tried http://www.doublecloud.org/2014/04/java-8-new-features-nashorn-javascript-engine/ , https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html , and How to instantiate a Java class in JavaScript using Nashorn? without luck. None of them seem to allow me the same thing as Java 1.7 did, instead assuming I only want to access static functions and objects.
The most common error I get:
I start with...
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval("var SyMAT_Functions;with (new JavaImporter(Packages.net.apocalypselabs.symat)) {"
+ "SyMAT_Functions = new Functions();}");
...then...
jse.eval("with(SyMAT_Functions){ "+input+" }");
...spits out...
TypeError: Cannot apply "with" to non script object in <eval> at line number 1
I was able to reproduce. First of all, Nashorn doesn't try to make it difficult to use Java objects (non-static or otherwise) in general. I have used it in other projects and not had any major issue converting from Rhino in Java 7 beyond what is covered in the migration guide. However, the issue here appears to deal with the use of the with statement which is "not recommended" and is even disallowed in strict mode of ECMAScript 5.1, both according to MDN.
Meanwhile, I found a thread on the Nashorn-dev mailing list discussing a similar case. The relevant part of the response was:
Nashorn allows only script objects (i.e., objects created by a JS
constructor or JS object literal expression) as scope expression for
"with" statement. Arbitrary objects . . . can not be used as 'scope' expression for
'with'.
In jdk9, support has been added to support script objects mirror other
script engines or other globals (which are instances of ScriptObjectMirror).
It's not the most elegant solution but, without using JDK 9, I was able to get your intended use of with to function by writing a proxy object inside the Javascript to mirror the public API of the Java class:
package com.example;
import javax.script.*;
public class StackOverflow27120811
{
public static void main(String... args) throws Exception {
ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript");
jse.eval(
"var real = new Packages.com.example.StackOverflow27120811(); " +
"var proxy = { doSomething: function(str) { return real.doSomething(str); } }; "
);
jse.eval("with (proxy) { doSomething(\"hello, world\"); } ");
}
public void doSomething(String foo) {
System.out.println(foo);
}
}
Attila Szegedi pointed out the non-standard Nashorn Object.bindProperties function. While it can't be expected to work with anything but the Nashorn engine, it does eliminate the complexity of re-declaring all of the public API inside the proxy object. Using this approach, the first jse.eval(...) call can be replaced by:
jse.eval(
"var real = new Packages.com.example.StackOverflow27120811(); " +
"var proxy = { }; " +
"Object.bindProperties(proxy, real); " // Nashorn-only feature
);
I decided to compile and bundle the "old" Rhino interpreter with my application instead of using Nashorn.
https://wiki.openjdk.java.net/display/Nashorn/Using+Rhino+JSR-223+engine+with+JDK8
I've using the java ScriptEngine to execute a script that could alter a shared Java class. I'm wondering, if it's possible to support dynamically created variables in the java class?
// create a script engine manager
ScriptEngineManager factory = new ScriptEngineManager();
// create a JavaScript engine
ScriptEngine engine = factory.getEngineByName("JavaScript");
engine.put("javaclass", jClass);
engine.eval("javaclass.propertyThatDoesNotExist = 'test'"); // throws exception
You can register a javascript variable in the engine, by using the ScriptEngine#put(String key, Object value) method. For example:
engine.put("i", 10);
This is how can you retrieve the registered variable:
int i = ((Double) engine.eval("i")).intValue();
System.out.println("JavaScript variable in Java; i = " + i);
Java is not a dynamic language. So you can not add a property/variable to that class/object. For that you need to use a different language like Groovy or Java Script.
The JSR223 Bindings class allows you to expose arbitrary Java objects to scripting languages. But they have to be objects. I would like to define a function quit() that can be called from the scripting environment that turns into quitObject.run() in Java. But JSR223 doesn't define the concept of a function object. Is there a language-independent way to do the following in Javascript, namely to take a Runnable() and create a function in the scripting environment?
static private Object asFunction(ScriptEngine engine, Runnable r)
throws ScriptException
{
final Bindings bindings = engine.createBindings();
bindings.put("r", r);
return engine.eval(
"(function (r) { var f = function() { r.run(); }; return f;})(r)",
bindings);
}
Runnable quitObject = /* get/create a Runnable here */
Bindings bindings = engine.createBindings();
bindings.put("quit", asFunction(engine, quitObject));
With the builtin Javascript support for JSR223 this creates a sun.org.mozilla.javascript.internal.InterpretedFunction which does what I want. But it obviously won't work in Jython or whatever, and I'd like to make this language-independent.
I don't want my script users to have to type quitObject.run() as that's clumsy, and I don't want to parse script input to find quit() as it could be buried within other code.
If you look at javascript engine source code you'll find how oracle/sun implemented 2 functions (print, and println) which are magically (or not so magically) present when you fire up your engine.
Those function are 'scripted' , which is more or less what you did.
What I would do is : load and evaluate a bootstrap.[language_extension] before evaluating any other input in the new context.
You could easily create such scripts for each language you intend to support.
I am embedding some javascript in a Java application using Rhino. I am following the example on the Rhino website, executing a script by calling the Context's evaluateString method and passing the actual script in as a String.
I have a whole bunch of existing javascript code that I would like to make use of. I don't want to concatenate it all into an enormous String and pass it in to evaluateString. I would rather be able to load the code in so that I can call it from the code that I do pass into evaluateString (kind of like the AddCode method works in Microsoft's scripting control). I would like to add code like I can currently add variables by using the ScriptableObject.putProperty method.
Is there a way to do this? Can someone provide a code snippet or a link to the documentation. Thanks!
From the documentation and examples it looks like references to previously evaluated objects are controlled by scopes.
Context context = Context.enter();
try {
ScriptableObject scope = context.initStandardObjects();
Object out = Context.javaToJS(System.out, scope);
ScriptableObject.putProperty(scope, "out", out);
context.evaluateString(scope,
"function foo() { out.println('Hello, World!'); }", "<1>", 1, null);
context
.evaluateString(scope, "function bar() { foo(); }", "<2>", 1, null);
context.evaluateString(scope, "bar();", "<3>", 1, null);
} finally {
Context.exit();
}
(Rhino 1.7 release 2)
I know some people use Rhino directly to get the latest version, but the Java 6 implementation can evaluate scripts like this:
ScriptEngine engine = new ScriptEngineManager().getEngineByExtension("js");
engine.eval("function foo() { println('Hello, World!'); }");
engine.eval("function bar() { foo(); }");
engine.eval("bar();");
In my code I had that need (utility scripts and such), and I just simply concatenated them together in a giant StringBuilder and evaled it (Java 6). Its the only way since javascript can't do (without Java wrapper objects) otherJSScript.someUsefulFunction().