Selaa lähdekoodia

增加脚本模块

zhouhao 2 vuotta sitten
vanhempi
commit
c2e39e9ca9
16 muutettua tiedostoa jossa 1085 lisäystä ja 0 poistoa
  1. 43 0
      jetlinks-components/script-component/pom.xml
  2. 100 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/AbstractScriptFactory.java
  3. 22 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/AbstractScriptFactoryProvider.java
  4. 32 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/CompiledScript.java
  5. 33 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/ExposedScript.java
  6. 24 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/Script.java
  7. 92 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/ScriptFactory.java
  8. 9 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/ScriptFactoryProvider.java
  9. 43 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/Scripts.java
  10. 139 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/context/CompositeExecutionContext.java
  11. 125 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/context/DefaultExecutionContext.java
  12. 30 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/context/ExecutionContext.java
  13. 119 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/JavaScriptFactory.java
  14. 182 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/Jsr223ScriptFactory.java
  15. 76 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/nashorn/NashornScriptFactory.java
  16. 16 0
      jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/nashorn/NashornScriptFactoryProvider.java

+ 43 - 0
jetlinks-components/script-component/pom.xml

@@ -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>

+ 100 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/AbstractScriptFactory.java

@@ -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);
+    }
+
+}

+ 22 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/AbstractScriptFactoryProvider.java

@@ -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();
+}

+ 32 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/CompiledScript.java

@@ -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());
+    }
+
+}

+ 33 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/ExposedScript.java

@@ -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());
+    }
+
+}

+ 24 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/Script.java

@@ -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);
+    }
+
+}

+ 92 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/ScriptFactory.java

@@ -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);
+
+}

+ 9 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/ScriptFactoryProvider.java

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

+ 43 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/Scripts.java

@@ -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();
+    }
+}

+ 139 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/context/CompositeExecutionContext.java

@@ -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;
+    }
+}

+ 125 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/context/DefaultExecutionContext.java

@@ -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;
+    }
+}

+ 30 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/context/ExecutionContext.java

@@ -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);
+    }
+
+
+}

+ 119 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/JavaScriptFactory.java

@@ -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(",",
+                                        "{", "}"));
+    }
+
+}

+ 182 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/Jsr223ScriptFactory.java

@@ -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();
+    }
+
+
+}

+ 76 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/nashorn/NashornScriptFactory.java

@@ -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);
+    }
+}

+ 16 - 0
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/nashorn/NashornScriptFactoryProvider.java

@@ -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();
+    }
+}