Java Nashorn - ClassNotFoundException - Java.type() - javascript

I am currently creating a plugin for the Bukkit-Server, but i have a problem using the Nashorn scripting engine. I am evaluating the external javascript file from my Java-Plugin. I cant get my javascript to import the classes from my plugin, only standard java classes are working (like var JavaBool = Java.type('java.lang.Boolean');, but not var Holder = Java.type('io.github.advtest1.js.JSHolder');)
Whenever I try to load one of these I get the following error:
Caused by: java.lang.ClassNotFoundException: io.github.advtest1.js.JSHolder
After a bit of researching i found that it has something to do with my plugin-classes beeing in the classpath, but how can i add it to the classpath when Bukkit itself loads the plugin and i don't want any other startoptions for the server then java -jar bukkit.jar?
Feel free to ask, if you need additional information.
Thanks in advance!

Nashorn uses the thread context loader found at the time of engine creation to find Java classes from Java.type API.
Nashorn also uses another loader if you use "-classpath" nashorn command line option. Nashorn tries to load classes using a fresh loader created during engine creation initialized with that classpath specified. Note that nashorn command line options can be passed via "nashorn.args" Java System property. So, if you specific -Dnashorn.args="-classpath your_path" elsewhere (say, in your config), then Nashorn can access classes from that your_path specified.
If cannot pass Nashorn engine options via System property [or via command line in the case "jjs" tool use], you can set thread context loader to be appropriate loader as suggested by an earlier answer.
If that is not desired because of other application dependencies, you get java.lang.Class object of the desired class and expose the same as variable to the script (from your Java code, you could get Class object and call ScriptEngine.put method). The script then can access "static" property on that to get 'type' object. With type object, usual "new", static method calls etc. work as expected.
Example:
import javax.script.*;
public class Main {
public static void main(String[] args) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
e.put("Vec", java.util.Vector.class); // replace it with any Class object
e.eval("var T = Vec.static; var obj = new T()"); // create new Vector
e.eval("print(obj.getClass())");
}
}

As #wero mentioned, I needed to add
ClassLoader cl = plugin.getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
before I invoke the js-function from java.
plugin stands for the main class (the class which extends JavaPlugin)

Related

how can I re-instantiate a class instanced by javascript (Nashorn Engine)

so, i instantiate a new class in javascript, and then add it to a list ... Later I go through the list and instantiate all the classes again, to work with them.
the Javascript:
var BPmanager = Java.type('paperVS.tabs.blueprint.BlueprintManager');
var abstractFunction= Java.extend(Java.type('paperVS.logic.function.Function'));
var getYPos = new abstractFunction("getYPos") {
//Some Functions
}
BPmanager.allFunctions.add(getYPos);
and later in Java:
for (Function fun : allFunctions) {
try {
Class<?> c = Class.forName(fun.getClass().getName());
Object object = c.newInstance();
instances.add((Function)object);
} catch (Exception e) {
e.printStackTrace();
}
}
Exeption:
java.lang.ClassNotFoundException: jdk.nashorn.javaadapters.paperVS_logic_function_Function
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at paperVS.tabs.blueprint.BlueprintManager.getFromList(BlueprintManager.java:176)
This code works for all classes except the Javascript classes.
The Javascript works fine (loading and executing the single instance), but not instancing of the Class. The normal ClassLoader does the same
Java ClassLoaders use a delegation model, where the system instantiates a "primordial" (or "system") class loader, and then class loaders can be created that look in specialized places for classes, and if not found, look in the ancestor (or delegate) class loader.
Nashorn has its own internal class loaders to deal with script classes; see Java 8 ScriptEngine across ClassLoaders. So you are creating classes in those class loaders, and then trying to use them in an ancestor class loader (you can see that in the stack trace) by name.
So that's one problem to solve.
Another is that these types created by Java.extend are Nashorn-managed objects, bound to a particular Nashorn script context, and will be inconvenient to use from other Java code (see comments below, where the OP notes that the classes can't be used easily, even if they're accessible).
If you want to use the JavaScript-created objects in Java code, you'll want to create Java objects and then pass them back to Java. You seem to be doing this, so perhaps you're just using them incorrectly. Making guesses based on your class names, etc., I am guessing that rather than:
Class<?> c = Class.forName(fun.getClass().getName());
Object object = c.newInstance();
instances.add((Function)object);
... you may just want something like:
instances.add((Function)fun);

How to access c# variables from a JavaScript file?

I have a C# file that pulls various config settings from different config files. A JavaScript application I'm writing needs some of those settings to run. I'm having trouble bringing the C# variables into the js file. Not sure what is the best approach. They need to end up as a JS object on the page. Would adding them to a JSon object in the C# file & calling that from the JS file work? Not even sure how to do that to be honest. Any help appreciated.
You can just declare a global variable in one of your root pages (say, _layout.cshtml):
<script>
var settings = {
foo: #(IsFoo ? "true", "false"),
bar: #SomeNumber,
baz: "#ImportantString"
}
</script>
Or, if you're writing a Web API, you can just add a /settings endpoint you can query like so (Assuming you're using jQuery):
$.get("/settings", response => {
// Store the `response` in a global variable.
});
If it's an independent javascript project and .net is more of an API, you need to make an API call which will send you those config settings.
If your javascript is part of .net MVC application, i.e., you are adding your javascript through an tag at then end of a .cshtml/.aspx file, you can easily pass it through a global variable, but even then the C# code need to pass those values through Modal to the .cshtml/aspx file.
what you can do is:
var pageConfig = pageConfig || {};
pageconfig.settings = #Html.Raw(Model.Settings); // This model object is part of the C# code and my assumption is that Settings will have array of configurations.
This can also be done globally, depending upon how the C# code is written.
Assuming that when you say 'various config settings' you're referring to .NET's ApplicationSettings (defined in Visual Studio's Properties > Settings), we've done something similar, in a generic manner, as follows:
public void WriteSettings(TextWriter writer)
{
// Declare the nameSpace for the DLL you want to pull settings from
var nameSpace = "foo.Bar"
ApplicationSettingsBase properties = (System.Configuration.ApplicationSettingsBase)Activator.CreateInstance(nameSpace, string.Format("{0}.Properties.Settings", nameSpace)).Unwrap();
foreach (SettingsProperty property in properties.Properties)
{
writer.Write(string.Format("{0}=\"{1}\", property.Name, properties[property.Name]);
}
}
I've ignored a few issues in your case:
I've not bothered trying to deal with non-strings; consider looking at property.PropertyType and casting your values appropriately
I've wrapped this in a function that accepts a TextWriter; you can pass this method Response.Output

Calling Java function from Rhino

Calling Javascript functions running inside Rhino from Java is easy enough - that after all is why Rhino was created. The thing I am having trouble establishing is this:
Context: I have a Phonegap CLI (v 6.3.3) Android project (API 19+) where I do a great deal of processing via loadable JavaScript running inside rhino
A Phonegap plugin - which I am creating at the same time as the actual Phonegap app - contains class called Storage which provides public, static, methods such as readFromFile(String fileName), writeToFile(String fileName,String data) etc.
What I want to be able to do is to call Storage.readFromFile etc from my loaded JavaScript code in Rhino.
Just how this should be done is not too clear to me. From the searches I have done thus far it involves using ScriptableObject.putProperty to pass the Java class in question, Storage in my case to JavaScript. However, how this should be done and then how it should be used at the JS end leaves me rather confused.
I would be most grateful to anyone here who might be able to point me in the right direction
Given that Rhino has less than 100 followers here it should perhaps come as little surprise that this question was not answered. In the mean time I have managed to find the solution myself and it turns out to be very simple. I share it below for the benefit of anyone else running into this thread.
My Storage class is very simple. It goes something like this
public class Storage
{
public static boolean haveFile(){}
public static boolean readFromFile(String fname){}
...
}
When I call Javascript from Java via Rhino I simply pass a new instance of the Storage class as the last of my function parameters
Context rhino = Context.enter();
Object[] functionParams = new Object[] {"Other parameters",new Storage()};
rhino.setOptimizationLevel(-1);
try
{
Scriptable scope = rhino.initStandardObjects();
String rhinoLog = "var log = Packages.io.vec.ScriptAPI.log;";
String code = /*Javascript code here* as shown separately below/;
rhino.evaluateString(scope, rhinoLog + code, "ScriptAPI", 1, null);
Function function = (Function) scope.get("jsFunction", scope);
Object jsResult = function.call(rhino,scope,scope,functionParams);
}
where the Javascript code is
function jsFunction(a,s)
{
//a - or a,b,c etc - here will be the "other" parameters
//s - will be the instance of the Java side Storage class passed above
//now you can do things like
s.writeToFile('fileName','fileData');
var fd = s.readFromFile('fileName');
s.dropFile('fileName');
...
}

OnContextCreated() in Cef not being called

I have a similar problem to the person in this post; I'm trying to extend the cefsimple.exe app included with the chromium embedded framework binaries to include a V8 handler. I implemented the OnContextCreated() method and made sure to extend RenderProcessHandler in the SimpleHandler class. I'm trying to implement a simple window bound variable called test_string; here's what my code looks like;
void SimpleHandler::OnContextCreated(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context)
{
CefRefPtr<CefV8Value> object = context->GetGlobal();
object->SetValue("test_string", CefV8Value::CreateString("this is a test"), V8_PROPERTY_ATTRIBUTE_NONE);
}
But the program never arrives at any breakpoints I add within the method, and the variable is undefined on any webpages I load within the app. I saw that one of the solutions in the other thread is to enable the settings.single_process flag, which i've done, but my code still doesn't reach the breakpoint.
To be clear, I'm accessing the variable on pages with window.test_string.
Make sure that you are sending that CefApp to CefExecuteProcess.
CefRefPtr<SimpleApp> app(new SimpleApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
Found this solution here
Have you read through the General Usage guide? Some key points below
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-cefapp
https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-processes
The single_process mode is not supported so I've never used it. In general I'd avoid it. The multi process architecture means you need to attach the debugger to the process. The Chromium guide is relevant to CEF in this instance.
https://www.chromium.org/developers/how-tos/debugging-on-windows#TOC-Attaching-to-the-renderer
you need to ensure your App is derived from CefRenderProcessHandler
not SimpleHandler!!!
class SimpleApp : public CefApp
, public CefRenderProcessHandler
{
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefV8Context> context) OVERRIDE;
valdemar-rudolfovich says you need to pass instance of SimpleApp in
CefExecuteProcess

Using a .NET DLL in JAVA SCRIPT - Registered through REGASM

I have registered a .NET DLL using REGASM and registration is successful. I'm able to create object of the classes and use them in my Javascript. The class I consume i sa non static one.
This is how I'm using and it is working,
var objDP = new ActiveXObject("PE.Core.PacketExtractor");
objDP.PrintMessage();
objDP.Start();
My problem :
I have a methods in my C# class which I would like to call and get the value. The method signature is given below,
Signature : Get_Current_Faults(ref PEFaults my_faults);
Usage : Get_Current_Faults(my_faults);
In this method the "PEFaults" is a public C# Struct. This is also COM Visible and registered as I could see an entry in the Registry.
I tried to get data in JavaScript by doing the following,
var objFault = new ActiveXObject("PE.Core.PE_FAULTS");
objDP.Get_Current_Faults(ref objFault);
WScript.echo(objFault.Temperature);
But this is not working and giving an error "Automation Server cant create an object" # PE.Core.CURRENT_FAULTS object creation.
Cant we create an object of Struct in JS ? Any idea how to handle a ref parameter ? How else I can make this work ?
Please advise me. Thanks.

Categories