增加脚本模块

This commit is contained in:
zhouhao 2022-09-26 17:25:35 +08:00
parent 14cc4224ef
commit c2e39e9ca9
16 changed files with 1085 additions and 0 deletions

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>jetlinks-components</artifactId>
<groupId>org.jetlinks.community</groupId>
<version>1.20.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>script-component</artifactId>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.graalvm.js</groupId>-->
<!-- <artifactId>js-scriptengine</artifactId>-->
<!-- <version>21.2.0</version>-->
<!-- <optional>true</optional>-->
<!-- </dependency>-->
<dependency>
<groupId>org.jetlinks</groupId>
<artifactId>reactor-ql</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,100 @@
package org.jetlinks.community.script;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.io.File;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public abstract class AbstractScriptFactory implements ScriptFactory {
static Class<?>[] DEFAULT_DENIES = {
System.class,
File.class,
Paths.class,
ObjectInputStream.class,
ObjectOutputStream.class,
Thread.class,
Runtime.class,
ScriptEngine.class,
ScriptEngineFactory.class
};
static Class<?>[] DEFAULT_ALLOWS = {
byte.class, short.class, int.class, long.class, char.class, float.class, double.class, boolean.class,
Byte.class, Short.class, Integer.class, Long.class, Character.class, Float.class, Double.class, Boolean.class,
BigDecimal.class, BigInteger.class,
String.class,
HashMap.class, ConcurrentHashMap.class, LinkedHashMap.class,
Date.class, LocalDateTime.class,
ArrayList.class,LinkedList.class
};
private final Set<String> denies = new HashSet<>();
private final Set<String> allows = new HashSet<>();
public AbstractScriptFactory() {
denies.add("*");
allows(DEFAULT_ALLOWS);
//denies(DEFAULT_DENIES);
}
@Override
public final void allows(Collection<Class<?>> allowTypes) {
allows.addAll(allowTypes.stream().map(Class::getName).collect(Collectors.toList()));
}
@Override
public final void allows(Class<?>... allowTypes) {
allows(Arrays.asList(allowTypes));
}
@Override
public final void denies(Collection<Class<?>> allowTypes) {
denies.addAll(allowTypes.stream().map(Class::getName).collect(Collectors.toList()));
}
@Override
public final void denies(Class<?>... allowTypes) {
denies(Arrays.asList(allowTypes));
}
@Override
public void allowsPattern(String... allowTypes) {
allowsPattern(Arrays.asList(allowTypes));
}
@Override
public void allowsPattern(Collection<String> allowTypes) {
}
@Override
public void deniesPattern(String... allowTypes) {
deniesPattern(Arrays.asList(allowTypes));
}
@Override
public void deniesPattern(Collection<String> allowTypes) {
}
public final boolean isDenied(Class<?> type) {
return isDenied(type.getName());
}
public final boolean isDenied(String typeName) {
if (allows.contains(typeName)) {
return false;
}
return denies.contains("*") || denies.contains(typeName);
}
}

View File

@ -0,0 +1,22 @@
package org.jetlinks.community.script;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public abstract class AbstractScriptFactoryProvider implements ScriptFactoryProvider {
private final Set<String> supports = new HashSet<>();
public AbstractScriptFactoryProvider(String... supports) {
this.supports.addAll(Arrays.asList(supports));
}
@Override
public boolean isSupport(String langOrMediaType) {
return supports.contains(langOrMediaType);
}
@Override
public abstract ScriptFactory factory();
}

View File

@ -0,0 +1,32 @@
package org.jetlinks.community.script;
import org.jetlinks.community.script.context.ExecutionContext;
import java.util.Collections;
import java.util.Map;
/**
* 已编译的脚本信息.
*
* @author zhouhao
* @since 2.0
*/
public interface CompiledScript {
/**
* 使用指定上下文执行脚本
*
* @param context 上下文
* @return 脚本返回结果
*/
Object call(ExecutionContext context);
default Object call(Map<String, Object> context) {
return call(ExecutionContext.create(context));
}
default Object call() {
return call(Collections.emptyMap());
}
}

View File

@ -0,0 +1,33 @@
package org.jetlinks.community.script;
import org.jetlinks.community.script.context.ExecutionContext;
import java.util.Map;
/**
* 提供支持暴露方法的脚本
*
* @param <T> 暴露方法的实例类型
* @author zhouhao
* @since 2.0
*/
public interface ExposedScript<T> {
/**
* 使用指定的暴露实例和上下文来执行脚本.在脚本中访问暴露的方法将调用指定实例的指定方法.
*
* @param expose 需要暴露的实例
* @param context 上下文
* @return 脚本执行结果
*/
Object call(T expose, ExecutionContext context);
default Object call(T expose, Map<String, Object> context) {
return call(expose, ExecutionContext.create(context));
}
default Object call(T expose) {
return call(expose, ExecutionContext.create());
}
}

View File

@ -0,0 +1,24 @@
package org.jetlinks.community.script;
import lombok.*;
@Getter
@AllArgsConstructor(staticName = "of")
public class Script {
@NonNull
private final String name;
@NonNull
private final String content;
private final Object source;
public static Script of(String name, String content) {
return Script.of(name, content, null);
}
public Script content(String content) {
return of(name, content, source);
}
}

View File

@ -0,0 +1,92 @@
package org.jetlinks.community.script;
import java.util.Collection;
import java.util.Map;
public interface ScriptFactory {
void allows(Collection<Class<?>> allowTypes);
void allows(Class<?>... allowTypes);
void denies(Collection<Class<?>> allowTypes);
void denies(Class<?>... allowTypes);
void allowsPattern(Collection<String> allowTypes);
void allowsPattern(String... allowTypes);
void deniesPattern(Collection<String> allowTypes);
void deniesPattern(String... allowTypes);
Object convertToJavaType(Object data);
/**
* 编译脚本,编译后通过@{@link CompiledScript#call(Map)}来执行脚本.
*
* <pre>{@code
* CompiledScript script = factory.compile(Script.of("test","return arg0+2;"));
*
* // val = 12
* Object val = script.call(Collections.singletonMap("arg0",10));
* }</pre>
*
* @param script 脚本
* @return 编译后的可执行脚本
*/
CompiledScript compile(Script script);
/**
* 编译脚本并将指定的类的方法暴露到脚本中,在脚本中可以直接调用内嵌对象的方法.比如:
*
* <pre>{@code
*
* public class Helper{
* public int max(int a,int b){
* return Math.max(a,b);
* }
* }
*
* CompiledScript script = factory.compile(Script.of("test","return max(1,2);"),Helper.class)
*
* Object val = script.call(new Helper());
*
* }</pre>
*
* @param script 脚本
* @param expose 要暴露的方法
* @return CompiledScript
*/
<T> ExposedScript<T> compileExpose(Script script, Class<? super T> expose);
/**
* 将脚本构造为一个接口的实现,在脚本中定义方法,然后将脚本的方法绑定到接口上.
* <p>
* 如果在脚本中没有定义方法的实现,调用方法后将返回<code>null</code>
*
* <pre>{@code
*
* public interface MyInterface{
*
* Object encode(Object data);
*
* }
*
* MyInterface inf = factory.bind(Script.of("function encode(data){ return 1; }"),MyInterface.class);
*
* //执行将调用脚本中的encode方法
* Object val = inf.encode(arg);
*
* }</pre>
*
* @param script 脚本
* @param interfaceType 接口类型
* @param <T> 泛型
* @return 接口代理实现
*/
<T> T bind(Script script,
Class<T> interfaceType);
}

View File

@ -0,0 +1,9 @@
package org.jetlinks.community.script;
public interface ScriptFactoryProvider {
boolean isSupport(String langOrMediaType);
ScriptFactory factory();
}

View File

@ -0,0 +1,43 @@
package org.jetlinks.community.script;
import org.jetlinks.community.script.nashorn.NashornScriptFactoryProvider;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class Scripts {
private final static List<ScriptFactoryProvider> providers = new CopyOnWriteArrayList<>();
private final static Map<String, ScriptFactory> globals = new ConcurrentHashMap<>();
static {
providers.add(new NashornScriptFactoryProvider());
try {
for (ScriptFactoryProvider scriptFactoryProvider : ServiceLoader.load(ScriptFactoryProvider.class)) {
providers.add(scriptFactoryProvider);
}
} catch (Throwable ignore) {
}
}
private static ScriptFactoryProvider lookup(String lang) {
for (ScriptFactoryProvider provider : providers) {
if (provider.isSupport(lang)) {
return provider;
}
}
throw new UnsupportedOperationException("unsupported script lang:" + lang);
}
public static ScriptFactory getFactory(String lang) {
return globals.computeIfAbsent(lang, Scripts::newFactory);
}
public static ScriptFactory newFactory(String lang) {
return lookup(lang).factory();
}
}

View File

@ -0,0 +1,139 @@
package org.jetlinks.community.script.context;
import lombok.AllArgsConstructor;
import javax.script.Bindings;
import javax.script.ScriptContext;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
@AllArgsConstructor
public class CompositeExecutionContext implements ExecutionContext {
private ExecutionContext[] contexts;
@Override
public synchronized ExecutionContext merge(ExecutionContext target) {
contexts = Arrays.copyOf(contexts, contexts.length + 1);
contexts[contexts.length - 1] = target;
return this;
}
@Override
public void setBindings(Bindings bindings, int scope) {
}
@Override
public Bindings getBindings(int scope) {
return null;
}
@Override
public void setAttribute(String name, Object value, int scope) {
contexts[contexts.length - 1].setAttribute(name, value, scope);
}
@Override
public Object getAttribute(String name, int scope) {
return getAttribute(name);
}
@Override
public Object removeAttribute(String name, int scope) {
for (ExecutionContext context : contexts) {
if (context.hasAttribute(name)) {
return context.removeAttribute(name, scope);
}
}
return null;
}
@Override
public Object getAttribute(String name) {
for (ExecutionContext context : contexts) {
if (context.hasAttribute(name)) {
return context.getAttribute(name);
}
}
return null;
}
@Override
public int getAttributesScope(String name) {
for (ExecutionContext context : contexts) {
if (context.hasAttribute(name)) {
return context.getAttributesScope(name);
}
}
return ENGINE_SCOPE;
}
@Override
public boolean hasAttribute(String key) {
for (ExecutionContext context : contexts) {
if (context.hasAttribute(key)) {
return true;
}
}
return false;
}
@Override
public Writer getWriter() {
for (ExecutionContext context : contexts) {
Writer writer = context.getWriter();
if (writer != null) {
return writer;
}
}
return null;
}
@Override
public Writer getErrorWriter() {
for (ExecutionContext context : contexts) {
Writer writer = context.getErrorWriter();
if (writer != null) {
return writer;
}
}
return null;
}
@Override
public void setWriter(Writer writer) {
}
@Override
public void setErrorWriter(Writer writer) {
}
@Override
public Reader getReader() {
for (ExecutionContext context : contexts) {
Reader reader = context.getReader();
if (reader != null) {
return reader;
}
}
return null;
}
@Override
public void setReader(Reader reader) {
}
@Override
public List<Integer> getScopes() {
return DefaultExecutionContext.scopes;
}
}

View File

@ -0,0 +1,125 @@
package org.jetlinks.community.script.context;
import com.google.common.collect.Maps;
import javax.script.Bindings;
import javax.script.ScriptContext;
import java.io.Reader;
import java.io.Writer;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
class DefaultExecutionContext implements ExecutionContext {
static final List<Integer> scopes = Arrays.asList(ENGINE_SCOPE, GLOBAL_SCOPE);
private final Map<String, Object>[] ctx;
private final Function<String, Object> fallback;
public DefaultExecutionContext(Map<String, Object>[] ctx) {
this(ctx, ignore -> null);
}
public DefaultExecutionContext(Map<String, Object>[] ctx,
Function<String, Object> fallback) {
this.ctx = Arrays.copyOf(ctx, ctx.length + 1);
this.fallback = fallback;
}
@Override
public void setBindings(Bindings bindings, int scope) {
}
@Override
public Bindings getBindings(int scope) {
return null;
}
private Map<String, Object> self() {
Map<String, Object> self = ctx[ctx.length - 1];
return self == null ?
ctx[ctx.length - 1] = Maps.newHashMapWithExpectedSize(16)
: self;
}
@Override
public void setAttribute(String name, Object value, int scope) {
self().put(name, value);
}
@Override
public Object getAttribute(String name, int scope) {
return getAttribute(name);
}
@Override
public Object removeAttribute(String name, int scope) {
return self().remove(name);
}
@Override
public Object getAttribute(String name) {
for (Map<String, Object> attr : ctx) {
if (attr != null && attr.containsKey(name)) {
return attr.get(name);
}
}
return fallback.apply(name);
}
@Override
public boolean hasAttribute(String key) {
for (Map<String, Object> attr : ctx) {
if (attr != null && attr.containsKey(key)) {
return true;
}
}
return fallback.apply(key) != null;
}
@Override
public int getAttributesScope(String name) {
return ENGINE_SCOPE;
}
@Override
public Writer getWriter() {
throw new UnsupportedOperationException();
}
@Override
public Writer getErrorWriter() {
throw new UnsupportedOperationException();
}
@Override
public void setWriter(Writer writer) {
}
@Override
public void setErrorWriter(Writer writer) {
}
@Override
public Reader getReader() {
throw new UnsupportedOperationException();
}
@Override
public void setReader(Reader reader) {
}
@Override
public List<Integer> getScopes() {
return scopes;
}
}

View File

@ -0,0 +1,30 @@
package org.jetlinks.community.script.context;
import javax.script.ScriptContext;
import java.util.Map;
import java.util.function.Function;
public interface ExecutionContext extends ScriptContext {
boolean hasAttribute(String key);
@SafeVarargs
static ExecutionContext create(Map<String, Object>... context) {
return new DefaultExecutionContext(context);
}
@SafeVarargs
static ExecutionContext create(Function<String, Object> fallback, Map<String, Object>... context) {
return new DefaultExecutionContext(context, fallback);
}
static ExecutionContext compose(ExecutionContext... contexts) {
return new CompositeExecutionContext(contexts);
}
default ExecutionContext merge(ExecutionContext target) {
return compose(this, target);
}
}

View File

@ -0,0 +1,119 @@
package org.jetlinks.community.script.jsr223;
import org.jetlinks.community.script.CompiledScript;
import org.jetlinks.community.script.ExposedScript;
import org.jetlinks.community.script.Script;
import javax.script.ScriptContext;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class JavaScriptFactory extends Jsr223ScriptFactory {
public JavaScriptFactory() {
super();
}
protected final String prepare(Script script) {
StringJoiner wrap = new StringJoiner("\n");
//使用匿名函数包装,防止变量逃逸
wrap.add("(function(){");
//注入安全性控制代码
//企业版还支持资源限制(防止死循环等操作)
wrap.add("function exit(){};" +
"function Function(e){return function(){}};" +
"function quit(){};" +
"function eval(s){};" +
"this.eval = function(e){};" +
"function readFully(){};" +
"function readLine(){};" +
"const print = console.log;" +
"const echo = console.log;");
wrap.add("/* script start */");
wrap.add(script.getContent());
wrap.add("/* script end */");
wrap.add("})()");
return wrap.toString();
}
private final Set<Method> ignoreMethod = new HashSet<>(
Stream
.concat(
Arrays.stream(Object.class.getMethods()),
Arrays.stream(Callable.class.getMethods())
)
.collect(Collectors.toList())
);
@Override
public <T> ExposedScript<T> compileExpose(Script script,
Class<? super T> expose) {
StringJoiner joiner = new StringJoiner("\n");
Set<String> distinct = new HashSet<>();
joiner.add(
Arrays.stream(expose.getMethods())
.filter(method -> !ignoreMethod.contains(method))
.sorted(Comparator.comparingInt(Method::getParameterCount).reversed())
.map(method -> {
if (!distinct.add(method.getName())) {
return null;
}
StringBuilder call = new StringBuilder("function ")
.append(method.getName())
.append("(){");
if (method.getParameterCount() == 0) {
call.append("return $$__that.")
.append(method.getName())
.append("();");
} else {
for (int i = 0; i <= method.getParameterCount(); i++) {
String[] args = new String[i];
for (int j = 0; j < i; j++) {
args[j] = "arguments[" + j + "]";
}
String arg = String.join(",", args);
call.append("if(arguments.length==").append(i).append("){")
.append("return $$__that.")
.append(method.getName())
.append("(").append(arg).append(");")
.append("}");
}
}
call.append("}");
return call.toString();
})
.filter(Objects::nonNull)
.collect(Collectors.joining())
);
joiner.add(script.getContent());
CompiledScript compiledScript = compile(script.content(joiner.toString()));
return (instance, ctx) -> {
ctx.setAttribute("$$__that", instance, ScriptContext.ENGINE_SCOPE);
return compiledScript.call(ctx);
};
}
@Override
protected String createFunctionMapping(Method[] methods) {
return Arrays
.stream(methods)
.map(Method::getName)
.map(m -> m + ":typeof(" + m + ")==='undefined'?null:" + m)
.collect(Collectors.joining(",",
"{", "}"));
}
}

View File

@ -0,0 +1,182 @@
package org.jetlinks.community.script.jsr223;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.script.AbstractScriptFactory;
import org.jetlinks.community.script.CompiledScript;
import org.jetlinks.community.script.Script;
import org.jetlinks.community.script.context.ExecutionContext;
import org.jetlinks.reactor.ql.utils.CastUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.script.Compilable;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@Slf4j
public abstract class Jsr223ScriptFactory extends AbstractScriptFactory {
private final ScriptEngine engine;
public Jsr223ScriptFactory() {
this.engine = createEngine();
}
protected abstract ScriptEngine createEngine();
@Override
public final CompiledScript compile(Script script) {
return compile(script, true);
}
private CompiledScript compile(Script script, boolean convert) {
ExecutionContext ctx = ExecutionContext.create();
ctx.setAttribute("console", new Jsr223ScriptFactory.Console(
LoggerFactory.getLogger("org.jetlinks.community.script." + script.getName())),
ScriptContext.ENGINE_SCOPE);
ctx.setAttribute("engine", null, ScriptContext.ENGINE_SCOPE);
javax.script.CompiledScript compiledScript = compile0(script);
return (context) -> Jsr223ScriptFactory.this
.eval(compiledScript,
script,
ExecutionContext.compose(ctx, context),
convert);
}
@SneakyThrows
private Object eval(javax.script.CompiledScript script,
Script source,
ExecutionContext context,
boolean convert) {
Object res = script.eval(acceptScriptContext(source, context));
return convert ? convertToJavaType(res) : res;
}
protected ExecutionContext acceptScriptContext(Script script, ExecutionContext context) {
return context;
}
@AllArgsConstructor
public static class Console {
private final Logger logger;
public void trace(String text, Object... args) {
logger.trace(text, args);
}
public void warn(String text, Object... args) {
logger.warn(text, args);
}
public void log(String text, Object... args) {
logger.debug(text, args);
}
public void error(String text, Object... args) {
logger.error(text, args);
}
}
@Override
@SuppressWarnings("all")
public final <T> T bind(Script script, Class<T> interfaceType) {
String returns = createFunctionMapping(interfaceType.getDeclaredMethods());
String content = script.getContent() + "\n return " + returns + ";";
CompiledScript compiledScript = compile(script.content(content), false);
Object source = compiledScript.call(Collections.emptyMap());
Set<Method> ignoreMethods = new HashSet<>();
return (T) Proxy.newProxyInstance(
interfaceType.getClassLoader(),
new Class[]{interfaceType},
(proxy, method, args) -> {
//方法已经被忽略执行
if (ignoreMethods.contains(method)) {
return convertValue(method, null);
}
try {
return this.convertValue(method,
((Invocable) engine).invokeMethod(source, method.getName(), args));
} catch (Throwable e) {
if (e instanceof NoSuchMethodException) {
log.info("method [{}] undefined in script", method, e);
//脚本未定义方法
ignoreMethods.add(method);
}
}
return convertValue(method, null);
});
}
protected boolean valueIsUndefined(Object value) {
return value == null;
}
public Object convertToJavaType(Object value) {
return value;
}
private Object convertValue(Method method, Object value) {
if (valueIsUndefined(value)) {
return null;
}
value = convertToJavaType(value);
Class<?> returnType = method.getReturnType();
if (returnType == void.class) {
return null;
}
if (returnType == int.class) {
return CastUtils.castNumber(value).intValue();
}
if (returnType == float.class) {
return CastUtils.castNumber(value).floatValue();
}
if (returnType == double.class) {
return CastUtils.castNumber(value).doubleValue();
}
if (returnType == long.class) {
return CastUtils.castNumber(value).longValue();
}
if (returnType == byte.class) {
return CastUtils.castNumber(value).byteValue();
}
if (returnType == short.class) {
return CastUtils.castNumber(value).shortValue();
}
return value;
}
protected abstract String createFunctionMapping(Method[] methods);
@SneakyThrows
private javax.script.CompiledScript compile0(Script script) {
String rewriteScript = prepare(script);
log.debug("compile script :\n{}", rewriteScript);
return ((Compilable) engine).compile(rewriteScript);
}
protected String prepare(Script script) {
return script.getContent();
}
}

View File

@ -0,0 +1,76 @@
package org.jetlinks.community.script.nashorn;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.JSObject;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import jdk.nashorn.internal.runtime.Undefined;
import lombok.extern.slf4j.Slf4j;
import org.jetlinks.community.script.jsr223.JavaScriptFactory;
import javax.script.ScriptEngine;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
public class NashornScriptFactory extends JavaScriptFactory implements ClassFilter {
@Override
protected ScriptEngine createEngine() {
return new NashornScriptEngineFactory()
.getScriptEngine(new String[]{"-doe", "--language=es6", "--global-per-engine"},
NashornScriptFactory.class.getClassLoader(),
this);
}
@Override
public boolean exposeToScripts(String s) {
return !isDenied(s);
}
@Override
protected boolean valueIsUndefined(Object value) {
return value == null || value instanceof Undefined;
}
@Override
public Object convertToJavaType(Object value) {
return convertToJavaObject(value);
}
public static Object convertToJavaObject(Object object) {
if (object instanceof JSObject) {
return convertJSObject(((JSObject) object));
}
if (object instanceof Undefined) {
return null;
}
return object;
}
public static Object convertJSObject(JSObject jsObject) {
if (jsObject.isArray()) {
return jsObject
.values()
.stream()
.map(obj -> {
if (obj instanceof JSObject) {
return convertJSObject(((JSObject) obj));
}
return obj;
}).collect(Collectors.toList());
}
if (jsObject instanceof Map) {
Map<Object, Object> newMap = new HashMap<>(((Map<?, ?>) jsObject).size());
for (Map.Entry<?, ?> entry : ((Map<?, ?>) jsObject).entrySet()) {
Object val = entry.getValue();
if (val instanceof JSObject) {
val = convertJSObject(((JSObject) val));
}
newMap.put(entry.getKey(), val);
}
return newMap;
}
throw new UnsupportedOperationException("unsupported type:" + jsObject);
}
}

View File

@ -0,0 +1,16 @@
package org.jetlinks.community.script.nashorn;
import org.jetlinks.community.script.AbstractScriptFactoryProvider;
import org.jetlinks.community.script.ScriptFactory;
public class NashornScriptFactoryProvider extends AbstractScriptFactoryProvider {
public NashornScriptFactoryProvider() {
super("js", "javascript", "nashorn");
}
@Override
public ScriptFactory factory() {
return new NashornScriptFactory();
}
}