package org.molgenis.js.nashorn;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import org.molgenis.util.ResourceUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.script.*;
import java.io.IOException;
import java.util.List;
import static java.util.Arrays.asList;
@Component
public class NashornScriptEngine
{
private static final Logger LOG = LoggerFactory.getLogger(NashornScriptEngine.class);
private static final List<String> RESOURCE_NAMES;
static
{
RESOURCE_NAMES = asList("/js/es6-shims.js", "/js/math.min.js", "/js/script-evaluator.js");
}
private ScriptEngine scriptEngine;
private ThreadLocal<Bindings> bindingsThreadLocal;
public NashornScriptEngine()
{
initScriptEngine();
}
public Object invokeFunction(String functionName, Object... args)
{
Bindings bindings = bindingsThreadLocal.get();
Object returnValue = ((JSObject) bindings.get(functionName)).call(this, args);
return convertNashornValue(returnValue);
}
public Object eval(String script) throws ScriptException
{
Bindings bindings = bindingsThreadLocal.get();
return scriptEngine.eval(script, bindings);
}
private void initScriptEngine()
{
LOG.debug("Initializing Nashorn script engine ...");
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
scriptEngine = factory.getScriptEngine(s -> false); // create engine with class filter exposing no classes
// construct common JavaScript content string from defined resources
StringBuilder commonJs = new StringBuilder(1000000);
RESOURCE_NAMES.forEach(resourceName ->
{
try
{
commonJs.append(ResourceUtils.getString(getClass(), resourceName)).append('\n');
}
catch (IOException e)
{
throw new RuntimeException("", e);
}
});
// pre-compile common JavaScript
CompiledScript compiledScript;
try
{
compiledScript = ((Compilable) scriptEngine).compile(commonJs.toString());
}
catch (ScriptException e)
{
throw new RuntimeException("", e);
}
// create bindings per thread resulting in a JavaScript global per thread
bindingsThreadLocal = ThreadLocal.withInitial(() ->
{
Bindings bindings = scriptEngine.createBindings();
try
{
// evaluate pre-compiled common JavaScript
compiledScript.eval(bindings);
}
catch (ScriptException e)
{
throw new RuntimeException("", e);
}
return bindings;
});
LOG.debug("Initialized Nashorn script engine");
}
private static Object convertNashornValue(Object nashornValue)
{
if (nashornValue == null)
{
return null;
}
Object convertedValue;
if (nashornValue instanceof ScriptObjectMirror)
{
ScriptObjectMirror scriptObjectMirror = (ScriptObjectMirror) nashornValue;
if (scriptObjectMirror.isArray())
{
convertedValue = scriptObjectMirror.values();
}
else
{
throw new RuntimeException("Unable to convert [ScriptObjectMirror]");
}
}
else
{
convertedValue = nashornValue;
}
return convertedValue;
}
}