Browse Source

Merge remote-tracking branch 'origin/2.0' into 2.0

liujq 2 years ago
parent
commit
d1746f323f

+ 1 - 1
jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/parser/strateies/ScriptPayloadParserBuilder.java

@@ -45,7 +45,7 @@ public class ScriptPayloadParserBuilder implements PayloadParserBuilderStrategy
         String script = config.getString("script")
                               .orElseThrow(() -> new IllegalArgumentException("script不能为空"));
         String lang = config.getString("lang")
-                            .orElseThrow(() -> new IllegalArgumentException("lang不能为空"));
+                            .orElse("js");
 
         CompiledScript compiledScript = Scripts
             .getFactory(lang)

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

@@ -15,6 +15,8 @@ import java.util.stream.Collectors;
 
 public abstract class AbstractScriptFactory implements ScriptFactory {
 
+    private final Utils utils = new Utils();
+
     static Class<?>[] DEFAULT_DENIES = {
         System.class,
         File.class,
@@ -97,4 +99,20 @@ public abstract class AbstractScriptFactory implements ScriptFactory {
         return denies.contains("*") || denies.contains(typeName);
     }
 
+
+    public Utils getUtils(){
+        return utils;
+    }
+
+
+    public class Utils {
+
+        private Utils(){}
+
+        public Object toJavaType(Object obj) {
+            return AbstractScriptFactory.this.convertToJavaType(obj);
+        }
+
+    }
+
 }

+ 11 - 6
jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/JavaScriptFactory.java

@@ -30,8 +30,8 @@ public abstract class JavaScriptFactory extends Jsr223ScriptFactory {
                      "this.eval = function(e){};" +
                      "function readFully(){};" +
                      "function readLine(){};" +
-                     "const print = console.log;" +
-                     "const echo = console.log;");
+                     "const print = function(e){console.log(e)};" +
+                     "const echo = print;");
 
         wrap.add("/*  script start */");
 
@@ -57,6 +57,7 @@ public abstract class JavaScriptFactory extends Jsr223ScriptFactory {
                                               Class<? super T> expose) {
         StringJoiner joiner = new StringJoiner("\n");
         Set<String> distinct = new HashSet<>();
+        joiner.add("var _$this = $this;");
         joiner.add(
             Arrays.stream(expose.getMethods())
                   .filter(method -> !ignoreMethod.contains(method))
@@ -70,10 +71,14 @@ public abstract class JavaScriptFactory extends Jsr223ScriptFactory {
                           .append(method.getName())
                           .append("(){");
                       if (method.getParameterCount() == 0) {
-                          call.append("return $$__that.")
+                          call.append("return _$this.")
                               .append(method.getName())
                               .append("();");
-                      } else {
+                      } else if (method.getParameterCount() == 1 && method.getParameterTypes()[0].isArray()) {
+                          call.append("return _$this.")
+                              .append(method.getName())
+                              .append("(utils.toJavaType(arguments));");
+                      }else {
 
                           for (int i = 0; i <= method.getParameterCount(); i++) {
                               String[] args = new String[i];
@@ -82,7 +87,7 @@ public abstract class JavaScriptFactory extends Jsr223ScriptFactory {
                               }
                               String arg = String.join(",", args);
                               call.append("if(arguments.length==").append(i).append("){")
-                                  .append("return $$__that.")
+                                  .append("return _$this.")
                                   .append(method.getName())
                                   .append("(").append(arg).append(");")
                                   .append("}");
@@ -101,7 +106,7 @@ public abstract class JavaScriptFactory extends Jsr223ScriptFactory {
         CompiledScript compiledScript = compile(script.content(joiner.toString()));
 
         return (instance, ctx) -> {
-            ctx.setAttribute("$$__that", instance, ScriptContext.ENGINE_SCOPE);
+            ctx.setAttribute("$this", instance, ScriptContext.ENGINE_SCOPE);
             return compiledScript.call(ctx);
         };
     }

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

@@ -45,6 +45,7 @@ public abstract class Jsr223ScriptFactory extends AbstractScriptFactory {
         ctx.setAttribute("console", new Jsr223ScriptFactory.Console(
                              LoggerFactory.getLogger("org.jetlinks.community.script." + script.getName())),
                          ScriptContext.ENGINE_SCOPE);
+        ctx.setAttribute("utils", getUtils(), ScriptContext.ENGINE_SCOPE);
 
         ctx.setAttribute("engine", null, ScriptContext.ENGINE_SCOPE);
 

+ 281 - 0
jetlinks-components/script-component/src/test/java/org/jetlinks/community/script/JavaScriptFactoryTest.java

@@ -0,0 +1,281 @@
+package org.jetlinks.community.script;
+
+import jdk.nashorn.internal.objects.Global;
+import lombok.SneakyThrows;
+import lombok.extern.java.Log;
+import org.jetlinks.community.script.jsr223.JavaScriptFactory;
+import org.junit.jupiter.api.Test;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public abstract class JavaScriptFactoryTest {
+
+    protected abstract JavaScriptFactory getFactory();
+
+    @Test
+    void testBadAccess() {
+        JavaScriptFactory factory = getFactory();
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return this.engine"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return global"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return context"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "delete this.engine;return this.engine"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "delete quit;return quit()"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return exit()"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return exit(0)"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "delete this.eval; return eval('1')"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return eval('1')"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return eval('1')"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return Function('return 1')()"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return this.eval('1')"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "const func = function() { return eval(1); };  return func();"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+        {
+            CompiledScript script = factory.compile(Script.of("test", "return (function(){ return eval('return 1') })()"));
+            Object resul = script.call(Collections.emptyMap());
+            assertNull(resul);
+        }
+
+
+    }
+
+
+    @Test
+    void testArray() {
+        JavaScriptFactory factory = getFactory();
+        Object val = factory.compile(Script.of("test", "var arr = []; arr.push({a:1,b:2}); return arr;"))
+                            .call(Collections.emptyMap());
+        System.out.println(val);
+        assertNotNull(val);
+        assertTrue(val instanceof List);
+    }
+
+    @Test
+    void testPrint() {
+        JavaScriptFactory factory = getFactory();
+        factory.compile(Script
+                            .of("test", "print(123);console.log('test 123');"))
+               .call(Collections.emptyMap());
+
+    }
+
+    @Test
+    void testTernary() {
+        JavaScriptFactory factory = getFactory();
+        Object val = factory.compile(Script.of("test", "return 1 + (1>0?2:1);"))
+                            .call(Collections.emptyMap());
+        System.out.println(val);
+        assertEquals(3, val);
+    }
+
+    @Test
+    void testNullSafe() {
+        JavaScriptFactory factory = getFactory();
+        Object val = factory.compile(Script.of("test", "return temp1==null? 10 :0"))
+                            .call(Collections.emptyMap());
+        assertEquals(10, val);
+    }
+
+    @Test
+    void testBenchmark() {
+        JavaScriptFactory factory = getFactory();
+        CompiledScript script = factory.compile(Script.of("test", "if(temp1==null) {return temp} else {return temp+1}"));
+        script.call(Collections.singletonMap("temp", 10));
+
+        long time = System.currentTimeMillis();
+        for (long i = 0; i < 10_0000; i++) {
+            script.call(Collections.singletonMap("temp", 10));
+        }
+        System.out.println(System.currentTimeMillis() - time);
+    }
+
+    @Test
+    void testAnonymous() {
+        JavaScriptFactory factory = getFactory();
+        factory.compile(Script.of("test", "parser.fixed(4)\n" +
+            "       .handler(function(buffer){\n" +
+            "            var len = buffer.getShort(2);\n" +
+            "            parser.fixed(len).result(buffer);\n" +
+            "        })\n" +
+            "       .handler(function(buffer){\n" +
+            "            parser.result(buffer)\n" +
+            "                   .complete();\n" +
+            "        });"));
+
+    }
+
+    @Test
+    void testVarNest() {
+        JavaScriptFactory factory = getFactory();
+        TestNest nest = new TestNest();
+
+        factory.compile(Script.of("test", "const test = this.test;  test.setup(function(e){ return e+test.data() })"))
+               .call(Collections.singletonMap("test", nest));
+
+        assertNotNull(nest.func);
+
+        System.out.println(nest.func.apply(10));
+    }
+
+    public static class TestNest {
+        Function<Object, Object> func;
+
+        public Object data() {
+            return 2;
+        }
+
+        public TestNest setup(Function<Object, Object> func) {
+            this.func = func;
+            return this;
+        }
+    }
+
+    @Test
+    @SneakyThrows
+    void testUtils() {
+        {
+            JavaScriptFactory factory = getFactory();
+            MyClazz utils = new MyClazz();
+
+            ExposedScript<MyClazz> script = factory.compileExpose(Script.of("test", "return $recent($recent(),$recent(temp))"), TestExtend.class);
+
+            assertEquals(utils.$recent(utils.$recent(), utils.$recent(2)), script.call(utils, Collections.singletonMap("temp", 2)));
+        }
+    }
+
+    @Test
+    void testNestFunction() {
+        JavaScriptFactory factory = getFactory();
+
+        Map<String, Object> ctx = new HashMap<>();
+        ctx.put("val", 1);
+
+        ExposedScript<TestExtend> script = factory.compileExpose(
+            Script.of("test", "var $val = val; function decode(){ return $recent()+$val }; return call(decode);"),
+            TestExtend.class);
+
+        TestExtend extend = new TestExtend();
+        script.call(extend, ctx);
+
+
+        assertNotNull(extend.call);
+        assertEquals(2.0, extend.call.get());
+
+    }
+
+    public static class MyClazz extends TestExtend {
+
+    }
+
+    public static class TestExtend {
+        Supplier<Object> call;
+
+        public int max(int i, int j) {
+            return Math.max(i, j);
+        }
+
+        public int $recent() {
+            return 1;
+        }
+
+        public int $recent(int i) {
+            return i;
+        }
+
+        public int $recent(int i, int j) {
+            return i + j;
+        }
+
+        public Object call(Supplier<Object> call) {
+            this.call = call;
+            return 0;
+        }
+
+    }
+
+    @Test
+    void testMake() {
+
+        Api api = getFactory().bind(Script.of(Api.class.getName(), "function add(a,b){return a+b};"), Api.class);
+
+        assertEquals(3, api.add(1, 2));
+
+        assertNull(api.reduce(1, 2));
+
+        assertNull(api.reduce(1, 2));
+
+    }
+
+    public interface Api {
+        int add(int a, int b);
+
+        Object reduce(int a, int b);
+    }
+}

+ 15 - 0
jetlinks-components/script-component/src/test/java/org/jetlinks/community/script/nashorn/NashornScriptFactoryTest.java

@@ -0,0 +1,15 @@
+package org.jetlinks.community.script.nashorn;
+
+import org.jetlinks.community.script.JavaScriptFactoryTest;
+import org.jetlinks.community.script.jsr223.JavaScriptFactory;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+class NashornScriptFactoryTest extends JavaScriptFactoryTest {
+    NashornScriptFactory factory = new NashornScriptFactory();
+
+    @Override
+    protected JavaScriptFactory getFactory() {
+        return factory;
+    }
+}

+ 92 - 0
jetlinks-components/script-component/src/test/resources/javascript-utils.html

@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Title</title>
+    <script src="javascript-utils.js" type="application/javascript"></script>
+</head>
+<body>
+
+<div class="container">
+
+</div>
+
+</body>
+</html>
+
+<script type="text/javascript">
+    var nodes = {
+        CallExpression:{
+            render:function (json){
+                return `<span class="CallExpression">
+                          <div class="expression">
+                          <span>执行函数</span><span class="callee">${render(json.callee)}</span></div>
+                          <span>参数</span><span>${renderList(arguments)}</span>
+                        </span>`
+            }
+        },
+        ExpressionStatement:{
+            render:function (json){
+                return `<span class="ExpressionStatement">
+                          <div class="expression">${render(json.expression)}</div>
+                        </span>`
+            }
+        },
+        ThisExpression:{
+            render:function (json){
+                return "this";
+            }
+        }
+        ,
+        MemberExpression:{
+            render:function (json){
+                return `<span class="MemberExpression">
+                          <span class="member-object">${render(json.object)}</span><span class="member-property">.${json.property}</span>
+                        </span>`
+            }
+        },
+        Identifier:{
+            render:function (json){
+                return json.name;
+            }
+        },
+        VariableDeclaration: {
+            render: function (json) {
+                return `<span class="VariableDeclaration">
+                         <span>声明变量</span>
+                         <span class="var-name"><input value="${render(json.id)}"></span>
+                         <span class="var-is">值为</span>
+                         <span class="var-init">${render(json.init)}</span>
+                       </span>`
+            }
+        }
+    }
+
+
+
+    function render(json) {
+        let type = nodes[json.type];
+        if(!type){
+            return "";
+        }
+        return type.render(json);
+    }
+
+    function renderList(json) {
+        let list = [];
+        for(let i=0;i<json.length;i++){
+            list[i] = render(json[i]);
+        }
+        return list.join("");
+    }
+
+    function parse(json){
+        let container = document.getElementsByClassName('container')[0];
+
+        for(let i=0;i<json.length;i++){
+            container.innerHTML=render(json[i]);
+        }
+
+    }
+
+</script>

+ 9 - 0
jetlinks-components/script-component/src/test/resources/javascript-utils.js

@@ -0,0 +1,9 @@
+
+
+
+
+function parse(json){
+
+    var jsonObject = JSON.parse(json);
+
+}