diff --git a/jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/parser/strateies/ScriptPayloadParserBuilder.java b/jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/parser/strateies/ScriptPayloadParserBuilder.java index d191cd89..0ac7d01d 100755 --- a/jetlinks-components/network-component/tcp-component/src/main/java/org/jetlinks/community/network/tcp/parser/strateies/ScriptPayloadParserBuilder.java +++ b/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) diff --git a/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/AbstractScriptFactory.java b/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/AbstractScriptFactory.java index bb3a21a5..290f31b2 100644 --- a/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/AbstractScriptFactory.java +++ b/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); + } + + } + } diff --git a/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/JavaScriptFactory.java b/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/JavaScriptFactory.java index 270e23e4..49be7448 100644 --- a/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/JavaScriptFactory.java +++ b/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 expose) { StringJoiner joiner = new StringJoiner("\n"); Set 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); }; } diff --git a/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/Jsr223ScriptFactory.java b/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/Jsr223ScriptFactory.java index e24f6395..433511d2 100644 --- a/jetlinks-components/script-component/src/main/java/org/jetlinks/community/script/jsr223/Jsr223ScriptFactory.java +++ b/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); diff --git a/jetlinks-components/script-component/src/test/java/org/jetlinks/community/script/JavaScriptFactoryTest.java b/jetlinks-components/script-component/src/test/java/org/jetlinks/community/script/JavaScriptFactoryTest.java new file mode 100644 index 00000000..00f51687 --- /dev/null +++ b/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 func; + + public Object data() { + return 2; + } + + public TestNest setup(Function func) { + this.func = func; + return this; + } + } + + @Test + @SneakyThrows + void testUtils() { + { + JavaScriptFactory factory = getFactory(); + MyClazz utils = new MyClazz(); + + ExposedScript 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 ctx = new HashMap<>(); + ctx.put("val", 1); + + ExposedScript 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 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 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); + } +} diff --git a/jetlinks-components/script-component/src/test/java/org/jetlinks/community/script/nashorn/NashornScriptFactoryTest.java b/jetlinks-components/script-component/src/test/java/org/jetlinks/community/script/nashorn/NashornScriptFactoryTest.java new file mode 100644 index 00000000..0a99cc38 --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/jetlinks-components/script-component/src/test/resources/javascript-utils.html b/jetlinks-components/script-component/src/test/resources/javascript-utils.html new file mode 100644 index 00000000..c4b96375 --- /dev/null +++ b/jetlinks-components/script-component/src/test/resources/javascript-utils.html @@ -0,0 +1,92 @@ + + + + + Title + + + + +
+ +
+ + + + + \ No newline at end of file diff --git a/jetlinks-components/script-component/src/test/resources/javascript-utils.js b/jetlinks-components/script-component/src/test/resources/javascript-utils.js new file mode 100644 index 00000000..28cc4da2 --- /dev/null +++ b/jetlinks-components/script-component/src/test/resources/javascript-utils.js @@ -0,0 +1,9 @@ + + + + +function parse(json){ + + var jsonObject = JSON.parse(json); + +} \ No newline at end of file