Calling Java from JavaScript

When writing type methods of life cycle hooks in JavaScript it is sometimes convenient to call Java code. Perhaps you have existing code or a 3rd party library that is only available in Java or perhaps you want to start a long running background process on another thread. In such cases it is possible to add a jar file to Cordra and call code in that jar file from JavaScript.

Any jar files you wish to call should be placed into a directory called lib in your Cordra data directory. If the lib directory doesn’t exist, create the lib directory in the Cordra data directory.

Consider the following simple Java class:

package net.example;

public class Point {

    public double x;
    public double y;
    public double z;

    public Point(double x, double y, double z) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ", " + z + ")";
    }
}

To create an instance of a Point and call its toString method from a type method in JavaScript you would do the following:

exports.staticMethods = {};
exports.staticMethods.exampleStaticMethod = exampleStaticMethod;

function exampleStaticMethod(context) {
    const Point = Java.type("net.example.Point");
    const point = new Point(1.3, 3.44, 2.58);
    const result = {
        point: point.toString()
    };
    return result;
}

More details on how to interact with Java objects from Cordra JavaScript can be found here https://www.graalvm.org/reference-manual/js/JavaInteroperability/ (for the newer GraalVM JavaScript engine) or here https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/nashorn/api.html (for the older Nashorn engine).

CordraHooksSupport

If you find yourself with a need to execute a long running Java background process you may want to listen for a shutdown event when Cordra shuts down to cleanly terminate your code. Support for this is provided by the class CordraHooksSupport. The below show example code that uses CordraHooksSupport to listen to the Cordra shutdown event. It also uses CordraHooksSupport to get an instance of a CordraClient that talks directly to the local Cordra instance.

package net.example.background;

import net.cnri.cordra.CordraHooksSupport;
import net.cnri.cordra.CordraHooksSupportProvider;
import net.cnri.cordra.api.*;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class BackgroundTasks {
    private static BackgroundTasks instance = null;
    private final ExecutorService exec;
    private final CordraClient cordra;
    private static volatile boolean shutdown = false;

    private BackgroundTasks() {
        exec = Executors.newSingleThreadExecutor();
        CordraHooksSupport hooks = CordraHooksSupportProvider.get();
        cordra = hooks.getCordraClient();
        hooks.addShutdownHook(this::shutdown);
    }

    public synchronized static BackgroundTasks instance() {
        if (instance == null) {
            instance = new BackgroundTasks();
        }
        return instance;
    }

    public synchronized void shutdown() {
        if (shutdown) {
            return;
        }
        shutdown = true;
        exec.shutdown();
        try {
            exec.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
        } catch (InterruptedException e) {

        }
    }

    public synchronized void doBackgroundTask(String objectId) throws CordraException {
        exec.submit(() -> {
            try {
                CordraObject obj = cordra.get(objectId);
                //...
                //Do some long running task with obj
                //...
            } catch (Exception e) {

            }
        });
    }
}

And then from a JavaScript instance method you could start the above Java background process:

exports.methods = {};
exports.methods.exampleInstanceMethod = exampleInstanceMethod;

function exampleInstanceMethod(obj, context) {
    const BackgroundTasks = Java.type("net.example.background.BackgroundTasks").;
    const backgroundTasks = BackgroundTasks.instance();
    backgroundTasks.doBackgroundTask(obj.id);
    return "background task started started";
}