Commit f73fbc5ee94b64874f5b329c85a28b686204797e

Authored by Igor Kulikov
1 parent 04432ee7

JavaScript Sandbox Service improvements.

Showing 22 changed files with 302 additions and 136 deletions
... ... @@ -44,7 +44,7 @@ import org.thingsboard.server.dao.relation.RelationService;
44 44 import org.thingsboard.server.dao.rule.RuleChainService;
45 45 import org.thingsboard.server.dao.timeseries.TimeseriesService;
46 46 import org.thingsboard.server.dao.user.UserService;
47   -import org.thingsboard.server.service.script.JsScriptEngine;
  47 +import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
48 48 import scala.concurrent.duration.Duration;
49 49
50 50 import java.util.Collections;
... ... @@ -151,8 +151,8 @@ class DefaultTbContext implements TbContext {
151 151 }
152 152
153 153 @Override
154   - public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) {
155   - return new JsScriptEngine(mainCtx.getJsSandbox(), script, functionName, argNames);
  154 + public ScriptEngine createJsScriptEngine(String script, String... argNames) {
  155 + return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), script, argNames);
156 156 }
157 157
158 158 @Override
... ...
... ... @@ -50,9 +50,8 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
50 50 import org.thingsboard.server.common.msg.TbMsg;
51 51 import org.thingsboard.server.common.msg.TbMsgMetaData;
52 52 import org.thingsboard.server.dao.event.EventService;
53   -import org.thingsboard.server.service.script.JsExecutorService;
54 53 import org.thingsboard.server.service.script.JsSandboxService;
55   -import org.thingsboard.server.service.script.JsScriptEngine;
  54 +import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
56 55
57 56 import java.util.List;
58 57 import java.util.Map;
... ... @@ -266,7 +265,6 @@ public class RuleChainController extends BaseController {
266 265 try {
267 266 String script = inputParams.get("script").asText();
268 267 String scriptType = inputParams.get("scriptType").asText();
269   - String functionName = inputParams.get("functionName").asText();
270 268 JsonNode argNamesJson = inputParams.get("argNames");
271 269 String[] argNames = objectMapper.treeToValue(argNamesJson, String[].class);
272 270
... ... @@ -278,7 +276,7 @@ public class RuleChainController extends BaseController {
278 276 String errorText = "";
279 277 ScriptEngine engine = null;
280 278 try {
281   - engine = new JsScriptEngine(jsSandboxService, script, functionName, argNames);
  279 + engine = new RuleNodeJsScriptEngine(jsSandboxService, script, argNames);
282 280 TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
283 281 switch (scriptType) {
284 282 case "update":
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.service.script;
  18 +
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
  21 +import delight.nashornsandbox.NashornSandbox;
  22 +import delight.nashornsandbox.NashornSandboxes;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +
  25 +import javax.annotation.PostConstruct;
  26 +import javax.annotation.PreDestroy;
  27 +import javax.script.ScriptException;
  28 +import java.util.Map;
  29 +import java.util.UUID;
  30 +import java.util.concurrent.ConcurrentHashMap;
  31 +import java.util.concurrent.ExecutorService;
  32 +import java.util.concurrent.Executors;
  33 +import java.util.concurrent.atomic.AtomicInteger;
  34 +
  35 +@Slf4j
  36 +public abstract class AbstractNashornJsSandboxService implements JsSandboxService {
  37 +
  38 + private NashornSandbox sandbox = NashornSandboxes.create();
  39 + private ExecutorService monitorExecutorService;
  40 +
  41 + private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
  42 +
  43 + private Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
  44 +
  45 + @PostConstruct
  46 + public void init() {
  47 + monitorExecutorService = Executors.newFixedThreadPool(getMonitorThreadPoolSize());
  48 + sandbox.setExecutor(monitorExecutorService);
  49 + sandbox.setMaxCPUTime(getMaxCpuTime());
  50 + sandbox.allowNoBraces(false);
  51 + sandbox.setMaxPreparedStatements(30);
  52 + }
  53 +
  54 + @PreDestroy
  55 + public void stop() {
  56 + if (monitorExecutorService != null) {
  57 + monitorExecutorService.shutdownNow();
  58 + }
  59 + }
  60 +
  61 + protected abstract int getMonitorThreadPoolSize();
  62 +
  63 + protected abstract long getMaxCpuTime();
  64 +
  65 + protected abstract int getMaxErrors();
  66 +
  67 + @Override
  68 + public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
  69 + UUID scriptId = UUID.randomUUID();
  70 + String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
  71 + String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
  72 + try {
  73 + sandbox.eval(jsScript);
  74 + functionsMap.put(scriptId, functionName);
  75 + } catch (Exception e) {
  76 + log.warn("Failed to compile JS script: {}", e.getMessage(), e);
  77 + return Futures.immediateFailedFuture(e);
  78 + }
  79 + return Futures.immediateFuture(scriptId);
  80 + }
  81 +
  82 + @Override
  83 + public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) {
  84 + String functionName = functionsMap.get(scriptId);
  85 + if (functionName == null) {
  86 + return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
  87 + }
  88 + if (!isBlackListed(scriptId)) {
  89 + try {
  90 + return Futures.immediateFuture(sandbox.getSandboxedInvocable().invokeFunction(functionName, args));
  91 + } catch (Exception e) {
  92 + blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
  93 + return Futures.immediateFailedFuture(e);
  94 + }
  95 + } else {
  96 + return Futures.immediateFailedFuture(
  97 + new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!"));
  98 + }
  99 + }
  100 +
  101 + @Override
  102 + public ListenableFuture<Void> release(UUID scriptId) {
  103 + String functionName = functionsMap.get(scriptId);
  104 + if (functionName != null) {
  105 + try {
  106 + sandbox.eval(functionName + " = undefined;");
  107 + functionsMap.remove(scriptId);
  108 + blackListedFunctions.remove(scriptId);
  109 + } catch (ScriptException e) {
  110 + return Futures.immediateFailedFuture(e);
  111 + }
  112 + }
  113 + return Futures.immediateFuture(null);
  114 + }
  115 +
  116 + private boolean isBlackListed(UUID scriptId) {
  117 + if (blackListedFunctions.containsKey(scriptId)) {
  118 + AtomicInteger errorCount = blackListedFunctions.get(scriptId);
  119 + return errorCount.get() >= getMaxErrors();
  120 + } else {
  121 + return false;
  122 + }
  123 + }
  124 +
  125 + private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {
  126 + switch (scriptType) {
  127 + case RULE_NODE_SCRIPT:
  128 + return RuleNodeScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames);
  129 + default:
  130 + throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
  131 + }
  132 + }
  133 +}
... ...
... ... @@ -16,12 +16,16 @@
16 16
17 17 package org.thingsboard.server.service.script;
18 18
19   -import javax.script.ScriptException;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +
  21 +import java.util.UUID;
20 22
21 23 public interface JsSandboxService {
22 24
23   - Object eval(String js) throws ScriptException;
  25 + ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames);
  26 +
  27 + ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args);
24 28
25   - Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException;
  29 + ListenableFuture<Void> release(UUID scriptId);
26 30
27 31 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.service.script;
  18 +
  19 +public enum JsScriptType {
  20 + RULE_NODE_SCRIPT
  21 +}
... ...
... ... @@ -16,21 +16,13 @@
16 16
17 17 package org.thingsboard.server.service.script;
18 18
19   -import delight.nashornsandbox.NashornSandbox;
20   -import delight.nashornsandbox.NashornSandboxes;
21 19 import lombok.extern.slf4j.Slf4j;
22 20 import org.springframework.beans.factory.annotation.Value;
23 21 import org.springframework.stereotype.Service;
24 22
25   -import javax.annotation.PostConstruct;
26   -import javax.annotation.PreDestroy;
27   -import javax.script.ScriptException;
28   -import java.util.concurrent.ExecutorService;
29   -import java.util.concurrent.Executors;
30   -
31 23 @Slf4j
32 24 @Service
33   -public class NashornJsSandboxService implements JsSandboxService {
  25 +public class NashornJsSandboxService extends AbstractNashornJsSandboxService {
34 26
35 27 @Value("${actors.rule.js_sandbox.monitor_thread_pool_size}")
36 28 private int monitorThreadPoolSize;
... ... @@ -38,33 +30,21 @@ public class NashornJsSandboxService implements JsSandboxService {
38 30 @Value("${actors.rule.js_sandbox.max_cpu_time}")
39 31 private long maxCpuTime;
40 32
41   - private NashornSandbox sandbox = NashornSandboxes.create();
42   - private ExecutorService monitorExecutorService;
43   -
44   - @PostConstruct
45   - public void init() {
46   - monitorExecutorService = Executors.newFixedThreadPool(monitorThreadPoolSize);
47   - sandbox.setExecutor(monitorExecutorService);
48   - sandbox.setMaxCPUTime(maxCpuTime);
49   - sandbox.allowNoBraces(false);
50   - sandbox.setMaxPreparedStatements(30);
51   - }
  33 + @Value("${actors.rule.js_sandbox.max_errors}")
  34 + private int maxErrors;
52 35
53   - @PreDestroy
54   - public void stop() {
55   - if (monitorExecutorService != null) {
56   - monitorExecutorService.shutdownNow();
57   - }
  36 + @Override
  37 + protected int getMonitorThreadPoolSize() {
  38 + return monitorThreadPoolSize;
58 39 }
59 40
60 41 @Override
61   - public Object eval(String js) throws ScriptException {
62   - return sandbox.eval(js);
  42 + protected long getMaxCpuTime() {
  43 + return maxCpuTime;
63 44 }
64 45
65 46 @Override
66   - public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
67   - return sandbox.getSandboxedInvocable().invokeFunction(name, args);
  47 + protected int getMaxErrors() {
  48 + return maxErrors;
68 49 }
69   -
70 50 }
... ...
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java renamed from application/src/main/java/org/thingsboard/server/service/script/JsScriptEngine.java
... ... @@ -28,56 +28,23 @@ import javax.script.ScriptException;
28 28 import java.util.Collections;
29 29 import java.util.Map;
30 30 import java.util.Set;
  31 +import java.util.UUID;
  32 +import java.util.concurrent.ExecutionException;
31 33
32 34
33 35 @Slf4j
34   -public class JsScriptEngine implements org.thingsboard.rule.engine.api.ScriptEngine {
35   -
36   - public static final String MSG = "msg";
37   - public static final String METADATA = "metadata";
38   - public static final String MSG_TYPE = "msgType";
39   -
40   - private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msgStr, metadataStr, msgType) { " +
41   - " var msg = JSON.parse(msgStr); " +
42   - " var metadata = JSON.parse(metadataStr); " +
43   - " return JSON.stringify(%s(msg, metadata, msgType));" +
44   - " function %s(%s, %s, %s) {";
45   - private static final String JS_WRAPPER_SUFFIX = "}" +
46   - "\n}";
  36 +public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.ScriptEngine {
47 37
48 38 private static final ObjectMapper mapper = new ObjectMapper();
49   -// private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
50   -// private ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"});
51 39 private final JsSandboxService sandboxService;
52 40
53   - private final String invokeFunctionName;
  41 + private final UUID scriptId;
54 42
55   - public JsScriptEngine(JsSandboxService sandboxService, String script, String functionName, String... argNames) {
  43 + public RuleNodeJsScriptEngine(JsSandboxService sandboxService, String script, String... argNames) {
56 44 this.sandboxService = sandboxService;
57   - this.invokeFunctionName = "invokeInternal" + this.hashCode();
58   - String msgArg;
59   - String metadataArg;
60   - String msgTypeArg;
61   - if (argNames != null && argNames.length == 3) {
62   - msgArg = argNames[0];
63   - metadataArg = argNames[1];
64   - msgTypeArg = argNames[2];
65   - } else {
66   - msgArg = MSG;
67   - metadataArg = METADATA;
68   - msgTypeArg = MSG_TYPE;
69   - }
70   - String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, this.invokeFunctionName,
71   - functionName, functionName, msgArg, metadataArg, msgTypeArg);
72   - compileScript(jsWrapperPrefix + script + JS_WRAPPER_SUFFIX);
73   - }
74   -
75   - private void compileScript(String script) {
76 45 try {
77   - //engine.eval(script);
78   - sandboxService.eval(script);
79   - } catch (ScriptException e) {
80   - log.warn("Failed to compile JS script: {}", e.getMessage(), e);
  46 + this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
  47 + } catch (Exception e) {
81 48 throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
82 49 }
83 50 }
... ... @@ -103,17 +70,17 @@ public class JsScriptEngine implements org.thingsboard.rule.engine.api.ScriptEng
103 70 String data = null;
104 71 Map<String, String> metadata = null;
105 72 String messageType = null;
106   - if (msgData.has(MSG)) {
107   - JsonNode msgPayload = msgData.get(MSG);
  73 + if (msgData.has(RuleNodeScriptFactory.MSG)) {
  74 + JsonNode msgPayload = msgData.get(RuleNodeScriptFactory.MSG);
108 75 data = mapper.writeValueAsString(msgPayload);
109 76 }
110   - if (msgData.has(METADATA)) {
111   - JsonNode msgMetadata = msgData.get(METADATA);
  77 + if (msgData.has(RuleNodeScriptFactory.METADATA)) {
  78 + JsonNode msgMetadata = msgData.get(RuleNodeScriptFactory.METADATA);
112 79 metadata = mapper.convertValue(msgMetadata, new TypeReference<Map<String, String>>() {
113 80 });
114 81 }
115   - if (msgData.has(MSG_TYPE)) {
116   - messageType = msgData.get(MSG_TYPE).asText();
  82 + if (msgData.has(RuleNodeScriptFactory.MSG_TYPE)) {
  83 + messageType = msgData.get(RuleNodeScriptFactory.MSG_TYPE).asText();
117 84 }
118 85 String newData = data != null ? data : msg.getData();
119 86 TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy();
... ... @@ -195,18 +162,20 @@ public class JsScriptEngine implements org.thingsboard.rule.engine.api.ScriptEng
195 162 private JsonNode executeScript(TbMsg msg) throws ScriptException {
196 163 try {
197 164 String[] inArgs = prepareArgs(msg);
198   - //String eval = ((Invocable)engine).invokeFunction(this.invokeFunctionName, inArgs[0], inArgs[1], inArgs[2]).toString();
199   - String eval = sandboxService.invokeFunction(this.invokeFunctionName, inArgs[0], inArgs[1], inArgs[2]).toString();
  165 + String eval = sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
200 166 return mapper.readTree(eval);
201   - } catch (ScriptException | IllegalArgumentException th) {
202   - throw th;
203   - } catch (Throwable th) {
204   - th.printStackTrace();
205   - throw new RuntimeException("Failed to execute js script", th);
  167 + } catch (ExecutionException e) {
  168 + if (e.getCause() instanceof ScriptException) {
  169 + throw (ScriptException)e.getCause();
  170 + } else {
  171 + throw new ScriptException("Failed to execute js script: " + e.getMessage());
  172 + }
  173 + } catch (Exception e) {
  174 + throw new ScriptException("Failed to execute js script: " + e.getMessage());
206 175 }
207 176 }
208 177
209 178 public void destroy() {
210   - //engine = null;
  179 + sandboxService.release(this.scriptId);
211 180 }
212 181 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.service.script;
  18 +
  19 +public class RuleNodeScriptFactory {
  20 +
  21 + public static final String MSG = "msg";
  22 + public static final String METADATA = "metadata";
  23 + public static final String MSG_TYPE = "msgType";
  24 + public static final String RULE_NODE_FUNCTION_NAME = "ruleNodeFunc";
  25 +
  26 + private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msgStr, metadataStr, msgType) { " +
  27 + " var msg = JSON.parse(msgStr); " +
  28 + " var metadata = JSON.parse(metadataStr); " +
  29 + " return JSON.stringify(%s(msg, metadata, msgType));" +
  30 + " function %s(%s, %s, %s) {";
  31 + private static final String JS_WRAPPER_SUFFIX = "}" +
  32 + "\n}";
  33 +
  34 +
  35 + public static String generateRuleNodeScript(String functionName, String scriptBody, String... argNames) {
  36 + String msgArg;
  37 + String metadataArg;
  38 + String msgTypeArg;
  39 + if (argNames != null && argNames.length == 3) {
  40 + msgArg = argNames[0];
  41 + metadataArg = argNames[1];
  42 + msgTypeArg = argNames[2];
  43 + } else {
  44 + msgArg = MSG;
  45 + metadataArg = METADATA;
  46 + msgTypeArg = MSG_TYPE;
  47 + }
  48 + String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, functionName,
  49 + RULE_NODE_FUNCTION_NAME, RULE_NODE_FUNCTION_NAME, msgArg, metadataArg, msgTypeArg);
  50 + return jsWrapperPrefix + scriptBody + JS_WRAPPER_SUFFIX;
  51 + }
  52 +
  53 +}
... ...
... ... @@ -243,6 +243,8 @@ actors:
243 243 monitor_thread_pool_size: "${ACTORS_RULE_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
244 244 # Maximum CPU time in milliseconds allowed for script execution
245 245 max_cpu_time: "${ACTORS_RULE_JS_SANDBOX_MAX_CPU_TIME:100}"
  246 + # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
  247 + max_errors: "${ACTORS_RULE_JS_SANDBOX_MAX_ERRORS:3}"
246 248 chain:
247 249 # Errors for particular actor are persisted once per specified amount of milliseconds
248 250 error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}"
... ...
application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java renamed from application/src/test/java/org/thingsboard/server/service/script/JsScriptEngineTest.java
... ... @@ -27,30 +27,28 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
27 27 import javax.script.ScriptException;
28 28
29 29 import java.util.Set;
30   -import java.util.concurrent.ExecutorService;
31   -import java.util.concurrent.Executors;
32 30
33 31 import static org.junit.Assert.*;
34 32
35   -public class JsScriptEngineTest {
  33 +public class RuleNodeJsScriptEngineTest {
36 34
37 35 private ScriptEngine scriptEngine;
38 36 private TestNashornJsSandboxService jsSandboxService;
39 37
40 38 @Before
41 39 public void beforeTest() throws Exception {
42   - jsSandboxService = new TestNashornJsSandboxService(1, 100);
  40 + jsSandboxService = new TestNashornJsSandboxService(1, 100, 3);
43 41 }
44 42
45 43 @After
46 44 public void afterTest() throws Exception {
47   - jsSandboxService.destroy();
  45 + jsSandboxService.stop();
48 46 }
49 47
50 48 @Test
51 49 public void msgCanBeUpdated() throws ScriptException {
52 50 String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
53   - scriptEngine = new JsScriptEngine(jsSandboxService, function, "Transform");
  51 + scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
54 52
55 53 TbMsgMetaData metaData = new TbMsgMetaData();
56 54 metaData.putValue("temp", "7");
... ... @@ -61,12 +59,13 @@ public class JsScriptEngineTest {
61 59
62 60 TbMsg actual = scriptEngine.executeUpdate(msg);
63 61 assertEquals("70", actual.getMetaData().getValue("temp"));
  62 + scriptEngine.destroy();
64 63 }
65 64
66 65 @Test
67 66 public void newAttributesCanBeAddedInMsg() throws ScriptException {
68 67 String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};";
69   - scriptEngine = new JsScriptEngine(jsSandboxService, function, "Transform");
  68 + scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
70 69 TbMsgMetaData metaData = new TbMsgMetaData();
71 70 metaData.putValue("temp", "7");
72 71 metaData.putValue("humidity", "99");
... ... @@ -76,12 +75,13 @@ public class JsScriptEngineTest {
76 75
77 76 TbMsg actual = scriptEngine.executeUpdate(msg);
78 77 assertEquals("94", actual.getMetaData().getValue("newAttr"));
  78 + scriptEngine.destroy();
79 79 }
80 80
81 81 @Test
82 82 public void payloadCanBeUpdated() throws ScriptException {
83 83 String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};";
84   - scriptEngine = new JsScriptEngine(jsSandboxService, function, "Transform");
  84 + scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
85 85 TbMsgMetaData metaData = new TbMsgMetaData();
86 86 metaData.putValue("temp", "7");
87 87 metaData.putValue("humidity", "99");
... ... @@ -93,12 +93,13 @@ public class JsScriptEngineTest {
93 93
94 94 String expectedJson = "{\"name\":\"Vit\",\"passed\":35,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}";
95 95 assertEquals(expectedJson, actual.getData());
  96 + scriptEngine.destroy();
96 97 }
97 98
98 99 @Test
99 100 public void metadataAccessibleForFilter() throws ScriptException {
100 101 String function = "return metadata.humidity < 15;";
101   - scriptEngine = new JsScriptEngine(jsSandboxService, function, "Filter");
  102 + scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
102 103 TbMsgMetaData metaData = new TbMsgMetaData();
103 104 metaData.putValue("temp", "7");
104 105 metaData.putValue("humidity", "99");
... ... @@ -106,12 +107,13 @@ public class JsScriptEngineTest {
106 107
107 108 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
108 109 assertFalse(scriptEngine.executeFilter(msg));
  110 + scriptEngine.destroy();
109 111 }
110 112
111 113 @Test
112 114 public void dataAccessibleForFilter() throws ScriptException {
113 115 String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;";
114   - scriptEngine = new JsScriptEngine(jsSandboxService, function, "Filter");
  116 + scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
115 117 TbMsgMetaData metaData = new TbMsgMetaData();
116 118 metaData.putValue("temp", "7");
117 119 metaData.putValue("humidity", "99");
... ... @@ -119,6 +121,7 @@ public class JsScriptEngineTest {
119 121
120 122 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
121 123 assertTrue(scriptEngine.executeFilter(msg));
  124 + scriptEngine.destroy();
122 125 }
123 126
124 127 @Test
... ... @@ -131,7 +134,7 @@ public class JsScriptEngineTest {
131 134 "};\n" +
132 135 "\n" +
133 136 "return nextRelation(metadata, msg);";
134   - scriptEngine = new JsScriptEngine(jsSandboxService, jsCode, "Switch");
  137 + scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, jsCode);
135 138 TbMsgMetaData metaData = new TbMsgMetaData();
136 139 metaData.putValue("temp", "10");
137 140 metaData.putValue("humidity", "99");
... ... @@ -140,6 +143,7 @@ public class JsScriptEngineTest {
140 143 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
141 144 Set<String> actual = scriptEngine.executeSwitch(msg);
142 145 assertEquals(Sets.newHashSet("one"), actual);
  146 + scriptEngine.destroy();
143 147 }
144 148
145 149 @Test
... ... @@ -152,7 +156,7 @@ public class JsScriptEngineTest {
152 156 "};\n" +
153 157 "\n" +
154 158 "return nextRelation(metadata, msg);";
155   - scriptEngine = new JsScriptEngine(jsSandboxService, jsCode, "Switch");
  159 + scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, jsCode);
156 160 TbMsgMetaData metaData = new TbMsgMetaData();
157 161 metaData.putValue("temp", "10");
158 162 metaData.putValue("humidity", "99");
... ... @@ -161,6 +165,7 @@ public class JsScriptEngineTest {
161 165 TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L);
162 166 Set<String> actual = scriptEngine.executeSwitch(msg);
163 167 assertEquals(Sets.newHashSet("one", "three"), actual);
  168 + scriptEngine.destroy();
164 169 }
165 170
166 171 }
\ No newline at end of file
... ...
... ... @@ -16,39 +16,40 @@
16 16
17 17 package org.thingsboard.server.service.script;
18 18
  19 +import com.google.common.util.concurrent.ListenableFuture;
19 20 import delight.nashornsandbox.NashornSandbox;
20 21 import delight.nashornsandbox.NashornSandboxes;
21 22
22 23 import javax.script.ScriptException;
  24 +import java.util.UUID;
23 25 import java.util.concurrent.ExecutorService;
24 26 import java.util.concurrent.Executors;
25 27
26   -public class TestNashornJsSandboxService implements JsSandboxService {
  28 +public class TestNashornJsSandboxService extends AbstractNashornJsSandboxService {
27 29
28   - private NashornSandbox sandbox = NashornSandboxes.create();
29   - private ExecutorService monitorExecutorService;
  30 + private final int monitorThreadPoolSize;
  31 + private final long maxCpuTime;
  32 + private final int maxErrors;
30 33
31   - public TestNashornJsSandboxService(int monitorThreadPoolSize, long maxCpuTime) {
32   - monitorExecutorService = Executors.newFixedThreadPool(monitorThreadPoolSize);
33   - sandbox.setExecutor(monitorExecutorService);
34   - sandbox.setMaxCPUTime(maxCpuTime);
35   - sandbox.allowNoBraces(false);
36   - sandbox.setMaxPreparedStatements(30);
  34 + public TestNashornJsSandboxService(int monitorThreadPoolSize, long maxCpuTime, int maxErrors) {
  35 + this.monitorThreadPoolSize = monitorThreadPoolSize;
  36 + this.maxCpuTime = maxCpuTime;
  37 + this.maxErrors = maxErrors;
  38 + init();
37 39 }
38 40
39 41 @Override
40   - public Object eval(String js) throws ScriptException {
41   - return sandbox.eval(js);
  42 + protected int getMonitorThreadPoolSize() {
  43 + return monitorThreadPoolSize;
42 44 }
43 45
44 46 @Override
45   - public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
46   - return sandbox.getSandboxedInvocable().invokeFunction(name, args);
  47 + protected long getMaxCpuTime() {
  48 + return maxCpuTime;
47 49 }
48 50
49   - public void destroy() {
50   - if (monitorExecutorService != null) {
51   - monitorExecutorService.shutdownNow();
52   - }
  51 + @Override
  52 + protected int getMaxErrors() {
  53 + return maxErrors;
53 54 }
54 55 }
... ...
... ... @@ -90,6 +90,6 @@ public interface TbContext {
90 90
91 91 MailService getMailService();
92 92
93   - ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames);
  93 + ScriptEngine createJsScriptEngine(String script, String... argNames);
94 94
95 95 }
... ...
... ... @@ -43,7 +43,7 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
43 43 @Override
44 44 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
45 45 this.config = loadAlarmNodeConfig(configuration);
46   - this.buildDetailsJsEngine = ctx.createJsScriptEngine(config.getAlarmDetailsBuildJs(), "Details");
  46 + this.buildDetailsJsEngine = ctx.createJsScriptEngine(config.getAlarmDetailsBuildJs());
47 47 }
48 48
49 49 protected abstract C loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException;
... ...
... ... @@ -46,7 +46,7 @@ public class TbLogNode implements TbNode {
46 46 @Override
47 47 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
48 48 this.config = TbNodeUtils.convert(configuration, TbLogNodeConfiguration.class);
49   - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "ToString");
  49 + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript());
50 50 }
51 51
52 52 @Override
... ...
... ... @@ -65,7 +65,7 @@ public class TbMsgGeneratorNode implements TbNode {
65 65 } else {
66 66 originatorId = ctx.getSelfId();
67 67 }
68   - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Generate", "prevMsg", "prevMetadata", "prevMsgType");
  68 + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType");
69 69 sentTickMsg(ctx);
70 70 }
71 71
... ...
... ... @@ -45,7 +45,7 @@ public class TbJsFilterNode implements TbNode {
45 45 @Override
46 46 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
47 47 this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
48   - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Filter");
  48 + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript());
49 49 }
50 50
51 51 @Override
... ...
... ... @@ -47,7 +47,7 @@ public class TbJsSwitchNode implements TbNode {
47 47 @Override
48 48 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
49 49 this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
50   - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Switch");
  50 + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript());
51 51 }
52 52
53 53 @Override
... ...
... ... @@ -43,7 +43,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode {
43 43 @Override
44 44 public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
45 45 this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class);
46   - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "Transform");
  46 + this.jsEngine = ctx.createJsScriptEngine(config.getJsScript());
47 47 setConfig(config);
48 48 }
49 49
... ...
... ... @@ -152,7 +152,7 @@ public class TbAlarmNodeTest {
152 152
153 153 verifyError(msg, "message", NotImplementedException.class);
154 154
155   - verify(ctx).createJsScriptEngine("DETAILS", "Details");
  155 + verify(ctx).createJsScriptEngine("DETAILS");
156 156 verify(ctx, times(1)).getJsExecutor();
157 157 verify(ctx).getAlarmService();
158 158 verify(ctx, times(2)).getDbCallbackExecutor();
... ... @@ -314,7 +314,7 @@ public class TbAlarmNodeTest {
314 314 ObjectMapper mapper = new ObjectMapper();
315 315 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
316 316
317   - when(ctx.createJsScriptEngine("DETAILS", "Details")).thenReturn(detailsJs);
  317 + when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
318 318
319 319 when(ctx.getTenantId()).thenReturn(tenantId);
320 320 when(ctx.getJsExecutor()).thenReturn(executor);
... ... @@ -338,7 +338,7 @@ public class TbAlarmNodeTest {
338 338 ObjectMapper mapper = new ObjectMapper();
339 339 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
340 340
341   - when(ctx.createJsScriptEngine("DETAILS", "Details")).thenReturn(detailsJs);
  341 + when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
342 342
343 343 when(ctx.getTenantId()).thenReturn(tenantId);
344 344 when(ctx.getJsExecutor()).thenReturn(executor);
... ...
... ... @@ -97,7 +97,7 @@ public class TbJsFilterNodeTest {
97 97 ObjectMapper mapper = new ObjectMapper();
98 98 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
99 99
100   - when(ctx.createJsScriptEngine("scr", "Filter")).thenReturn(scriptEngine);
  100 + when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine);
101 101
102 102 node = new TbJsFilterNode();
103 103 node.init(ctx, nodeConfiguration);
... ...
... ... @@ -79,7 +79,7 @@ public class TbJsSwitchNodeTest {
79 79 ObjectMapper mapper = new ObjectMapper();
80 80 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
81 81
82   - when(ctx.createJsScriptEngine("scr", "Switch")).thenReturn(scriptEngine);
  82 + when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine);
83 83
84 84 node = new TbJsSwitchNode();
85 85 node.init(ctx, nodeConfiguration);
... ...
... ... @@ -97,7 +97,7 @@ public class TbTransformMsgNodeTest {
97 97 ObjectMapper mapper = new ObjectMapper();
98 98 TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
99 99
100   - when(ctx.createJsScriptEngine("scr", "Transform")).thenReturn(scriptEngine);
  100 + when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine);
101 101
102 102 node = new TbTransformMsgNode();
103 103 node.init(ctx, nodeConfiguration);
... ...