Hi Christopher,

This is definitely a future-proof solution.

On moving ".static" out to Java code (inside "JavaClass" function): ".static" creates an Nashorn internal class StaticClass instance. Since you can't refer to it from Java code (it is from a package that is listed in "sensitive packages" list in java.security), it is important to do ".static" from script code.

You can of source introduce a script function that you "eval" as first script. Something like:

e.eval("function JavaType(name) { return JavaClass(name).static; }"); // where JavaClass is that function exposed from your java code.

You can call JavaType from other scripts.

Thanks
-Sundar


Christopher Brown wrote:
Hello Sundar,

Your suggestion works too. Would it be the recommended (more future-proof) approach, as opposed to setting the context classloader when instantiating the ScriptEngine instance? I'm guessing that it is.

Is there any way to make it even neater, by moving the ".static" logic from the JavaScript side into the "JavaClass" function, so that the JavaScript code can just invoke the JavaClass("className") function without needing to add ".static" after each usage (to avoid typing, and to avoid side effects when forgotten)?

Thanks,
Christopher


On 16 February 2015 at 04:38, A. Sundararajan <[email protected] <mailto:[email protected]>> wrote:

    Actually, you don't need to modify the thread context class
    loader. You can just expose Java Class objects via a script
    "function" exposed to scripts. You can implement "script function"
    as a Java lambda. Then, you can use "static" property to make Java
    type object from it (as Attila mentioned in his email).

    Example:

    import javax.script.*;
    import java.util.function.*;

    class Main {
       public static void main(String[] ar) throws Exception {
          ScriptEngineManager m = new ScriptEngineManager();
          ScriptEngine e = m.getEngineByName("nashorn");

          e.put("JavaClass", (Function<String, Class>)
              s -> {
                try {
                  // replace this whatever Class finding logic here
                  // say, using your own class loader(s) based search
                  return Class.forName(s);
                } catch (ClassNotFoundException cnfe) {
                  throw new RuntimeException(cnfe);
                }
              });

          // Using "static" property on Class object to make a
          // JS Java type object from a Java Class object
          e.eval("var System = JavaClass('java.lang.System').static");

          // call static method on that Java type
          e.eval("System.out.println('hello world')");
       }
    }

    This avoids any class loader setting or even changing existing API.

    -Sundar



    On Sunday 15 February 2015 08:16 PM, Christopher Brown wrote:

        Hello,

        Setting the context classloader when instantiating the script
        engine, as
        you suggested, solved my problem (note: the
        ScriptEngineManager had to be
        instantiated before changing the context classloader, as
        otherwise the
        "nashorn" engine couldn't be found).  Here's the code:

                 ScriptEngineManager scriptEngineManager = new
        ScriptEngineManager();
thread.setContextClassLoader(MyApiInterface.class.getClassLoader());
                 ScriptEngine engine =
        scriptEngineManager.getEngineByName("nashorn");
                 thread.setContextClassLoader(contextClassLoader);

        Will this technique supported in the future (all foreseeable
        versions), or
        is it an implementation side-effect that is subject to change?

        Following on from your reply, making Java.type(...) accept an
        actual Class
        instance would be great.  Also, a variant, such as:

        Java.type(className, classLoaderOrArrayOfClassLoaders)

        ...would be great.

        Thank you for your very helpful answer!

        --
        Christopher


        On 11 February 2015 at 09:56, Attila Szegedi
        <[email protected] <mailto:[email protected]>>
        wrote:

            Java.type will only resolve names against script engine's
            "app loader"
            (which is whatever was the thread context loader at the
            time engine
            instance was created) and additionally from the path
            passed in as
            "-classpath" command line option (to get an engine with
            extra command line
            options programmatically you'll need to use
            NashornScriptEngineFactory
            class directly instead of only relying on javax.script.*
            APIs; actually you
            can pass explicit "app loader" with some of
            NashornScriptEngineFactory
            factory methods too).

            In absence of this, you can do the following: if you pass
            in a Class
            object representing your interface (let's call it
            ForeignInterface) into
            the runtime somehow (e.g. through Bindings):

                 bindings.put("foreignInterfaceClass",
            ForeignInterface.class)

            then you can use "foreignInterfaceClass.static" to
            represent the static
            aspect of the type[1] in the script. Thereafter,

                 var ForeignInterface = foreignInterfaceClass.static;
                 var adapter = Java.extend(ForeignInterface ...

            should work, with the caveat that Nashorn's own classes
            must be visible
            through the class loader for ForeignInterface, as Nashorn
            will essentially
            be loading an adapter class that will have an "implements
            ForeignInterface"
            clause but it will also rely on some Nashorn internals in
            its bytecode, so
            it must be able to resolve symbolic bytecode references
            both to
            ForeignInterface and to some Nashorn classes. If your
            class loader has the
            extension class loader (which is where Nashorn lives)
            somewhere in its
            parent chain, you're good. Otherwise, if you have some
            more involved class
            loader topology, then I'm sorry to say that you're out of
            luck[2] :-(

            As for your additional questions:

            #1 you can decide to not implement all methods of the
            interface (in fact,
            you could choose to implement none of them :-) ). If the
            underlying script
            object provides no implementation for an invoked method,
            then interface
            default method is invoked if one is provided by the
            interface, otherwise
            java.lang.UnsupportedOperationException is thrown.

            #2 yes, javax.script has no explicit cleanup mechanism for
            script engines.
            It gets GC'd if it's unreachable. If you need resource
            cleanup tied to it
            not being reachable anymore, you'll need to use
            PhantomReference.

            HTH,
               Attila.

            ---
[1] the one you would in Java simply call "ForeignInterface", except in
            Java it's not a reified object, but purely a lexical
            language construct. In
            Nashorn, static types are reified, and you can think of
            ".static" as being
            the inverse operation of ".class"; .class on a static type
            gives you a
            runtime type (java.lang.Class) object, while .static on a
            j.l.Class object
            gives you back the static type which is used as both the
            constructor
            function for the type and a namespace for static
            fields/properties/methods
            (just as in Java). This whole discussion about static
            types makes me want
            to extend the Java.extend so that it accepts either a
            static type or a
            runtime j.l.Class object in its arguments, though.

            [2] The truth is actually somewhat more complex; when
            Nashorn defines the
            adapter class, it does so in a small new class loader, and
            it needs to find
            a suitable parent for this class loader: one that sees the
            class being
            extended (if there's one), all the interface(s) being
            implemented, and the
            Nashorn classes themselves. This can get tricky if you're
            implementing
            multiple interfaces coming from multiple class loaders.
            Nashorn will choose
            a class loader from among all involved classes' and
            interfaces' class
            loaders that can see all the classes (the "maximum
            visibility class
            loader") by probing Class.forName from one loader with a
            name of a class
            from another loader and seeing if it resolves to the same
            class. If it
            finds one, it uses that as the parent loader for the
            adapter's loader. If
            it can't find one, you get a TypeError at runtime. You
            know I have a fun
            job when some days I wake up thinking "I need to write an
            algorithm for
            finding a maximum in a lattice of ClassLoader objects
            partially ordered by
            can-see-classes-in-the-other relation".

            On Feb 6, 2015, at 10:05 AM, Christopher Brown <
            [email protected]
            <mailto:[email protected]>> wrote:

                Hello,

                Is it possible to use the Java.type() then the
                Java.extend() syntax
                described here:
                https://wiki.openjdk.java.net/display/Nashorn/Nashorn+extensions

                ...with interface types loaded from a classloader
                other than the JRE
                classloader and the main application classloader.

                As a simple example, suppose in the main() method of
                an application, I
                create a URL classloader to load arbitrary JAR files
                containing interface
                definitions, and then I create an instance of the
                Nashorn scripting
                engine.  Is it possible to make these types visible to
                the scripting
                engine, such that I could (in a loaded script) use
                Java.type() then
                Java.extend() to create an implementation of such a
                dynamically-loaded
                interface, and return it from the script when invoking
                a given function?

                I'm guessing that it's not possible, because I've not
                seen this

            documented

                anywhere.  I would assume that it might be possible
                via the "context
                classloader" of the current thread, but I'd prefer not
                to rely on
undocumented features (supposing that it's the case). The most flexible
                approach would be to pass a classloader (or a
                map/array of classloaders)
                into the binding, and pass it as a second parameter to
                the Java.type()
                function (especially if in a more complicated example,
                multiple URL
                classloaders were loaded and defined types with the
                same fully-qualified
                names).

                Additional question #1: would the implementation of
                the interface in
                Nashorn require that I implement default methods, or
                could they be

            invoked

                in the script without defining them?

                Additional question #2: how should resources added to
                a binding, and more
                generally, resources used by a ScriptEngine instance,
                be cleaned up when
                the engine instance is no longer required?  For
                example, if adding
                classloaders is (or can be made) possible, I can't see
                any way to
                explicitly "close" the script engine... I'm assuming
                the only way to

            clean

                things up is to de-reference the engine and wait for
                it to be
                garbage-collected.

                Thanks,
                Christopher





Reply via email to