Commit 8bdde06fa2d9eb39887da00b6afa1728a5c1477e

Authored by Andrew Shvayka
2 parents ffb8a1a5 04432ee7

Merge branch 'develop/2.0' of github.com:thingsboard/thingsboard into develop/2.0

... ... @@ -256,6 +256,10 @@
256 256 <groupId>org.hsqldb</groupId>
257 257 <artifactId>hsqldb</artifactId>
258 258 </dependency>
  259 + <dependency>
  260 + <groupId>org.javadelight</groupId>
  261 + <artifactId>delight-nashorn-sandbox</artifactId>
  262 + </dependency>
259 263 </dependencies>
260 264
261 265 <build>
... ...
... ... @@ -62,6 +62,7 @@ import org.thingsboard.server.service.mail.MailExecutorService;
62 62 import org.thingsboard.server.service.queue.MsgQueueService;
63 63 import org.thingsboard.server.service.rpc.DeviceRpcService;
64 64 import org.thingsboard.server.service.script.JsExecutorService;
  65 +import org.thingsboard.server.service.script.JsSandboxService;
65 66 import org.thingsboard.server.service.state.DeviceStateService;
66 67 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
67 68
... ... @@ -164,6 +165,10 @@ public class ActorSystemContext {
164 165
165 166 @Autowired
166 167 @Getter
  168 + private JsSandboxService jsSandbox;
  169 +
  170 + @Autowired
  171 + @Getter
167 172 private JsExecutorService jsExecutor;
168 173
169 174 @Autowired
... ...
... ... @@ -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.NashornJsEngine;
  47 +import org.thingsboard.server.service.script.JsScriptEngine;
48 48 import scala.concurrent.duration.Duration;
49 49
50 50 import java.util.Collections;
... ... @@ -152,7 +152,7 @@ class DefaultTbContext implements TbContext {
152 152
153 153 @Override
154 154 public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) {
155   - return new NashornJsEngine(script, functionName, argNames);
  155 + return new JsScriptEngine(mainCtx.getJsSandbox(), script, functionName, argNames);
156 156 }
157 157
158 158 @Override
... ...
... ... @@ -50,7 +50,9 @@ 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.NashornJsEngine;
  53 +import org.thingsboard.server.service.script.JsExecutorService;
  54 +import org.thingsboard.server.service.script.JsSandboxService;
  55 +import org.thingsboard.server.service.script.JsScriptEngine;
54 56
55 57 import java.util.List;
56 58 import java.util.Map;
... ... @@ -69,6 +71,9 @@ public class RuleChainController extends BaseController {
69 71 @Autowired
70 72 private EventService eventService;
71 73
  74 + @Autowired
  75 + private JsSandboxService jsSandboxService;
  76 +
72 77 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
73 78 @RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
74 79 @ResponseBody
... ... @@ -273,7 +278,7 @@ public class RuleChainController extends BaseController {
273 278 String errorText = "";
274 279 ScriptEngine engine = null;
275 280 try {
276   - engine = new NashornJsEngine(script, functionName, argNames);
  281 + engine = new JsScriptEngine(jsSandboxService, script, functionName, argNames);
277 282 TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
278 283 switch (scriptType) {
279 284 case "update":
... ...
... ... @@ -54,6 +54,10 @@ public abstract class AbstractListeningExecutor implements ListeningExecutor {
54 54 service.execute(command);
55 55 }
56 56
  57 + public ListeningExecutorService executor() {
  58 + return service;
  59 + }
  60 +
57 61 protected abstract int getThreadPollSize();
58 62
59 63 }
... ...
  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 javax.script.ScriptException;
  20 +
  21 +public interface JsSandboxService {
  22 +
  23 + Object eval(String js) throws ScriptException;
  24 +
  25 + Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException;
  26 +
  27 +}
... ...
application/src/main/java/org/thingsboard/server/service/script/JsScriptEngine.java renamed from application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java
... ... @@ -19,14 +19,11 @@ import com.fasterxml.jackson.core.type.TypeReference;
19 19 import com.fasterxml.jackson.databind.JsonNode;
20 20 import com.fasterxml.jackson.databind.ObjectMapper;
21 21 import com.google.common.collect.Sets;
22   -import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
23 22 import lombok.extern.slf4j.Slf4j;
24 23 import org.apache.commons.lang3.StringUtils;
25 24 import org.thingsboard.server.common.msg.TbMsg;
26 25 import org.thingsboard.server.common.msg.TbMsgMetaData;
27 26
28   -import javax.script.Invocable;
29   -import javax.script.ScriptEngine;
30 27 import javax.script.ScriptException;
31 28 import java.util.Collections;
32 29 import java.util.Map;
... ... @@ -34,7 +31,7 @@ import java.util.Set;
34 31
35 32
36 33 @Slf4j
37   -public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEngine {
  34 +public class JsScriptEngine implements org.thingsboard.rule.engine.api.ScriptEngine {
38 35
39 36 public static final String MSG = "msg";
40 37 public static final String METADATA = "metadata";
... ... @@ -49,12 +46,14 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
49 46 "\n}";
50 47
51 48 private static final ObjectMapper mapper = new ObjectMapper();
52   - private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
53   - private ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"});
  49 +// private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
  50 +// private ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"});
  51 + private final JsSandboxService sandboxService;
54 52
55 53 private final String invokeFunctionName;
56 54
57   - public NashornJsEngine(String script, String functionName, String... argNames) {
  55 + public JsScriptEngine(JsSandboxService sandboxService, String script, String functionName, String... argNames) {
  56 + this.sandboxService = sandboxService;
58 57 this.invokeFunctionName = "invokeInternal" + this.hashCode();
59 58 String msgArg;
60 59 String metadataArg;
... ... @@ -75,7 +74,8 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
75 74
76 75 private void compileScript(String script) {
77 76 try {
78   - engine.eval(script);
  77 + //engine.eval(script);
  78 + sandboxService.eval(script);
79 79 } catch (ScriptException e) {
80 80 log.warn("Failed to compile JS script: {}", e.getMessage(), e);
81 81 throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
... ... @@ -195,7 +195,8 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
195 195 private JsonNode executeScript(TbMsg msg) throws ScriptException {
196 196 try {
197 197 String[] inArgs = prepareArgs(msg);
198   - String eval = ((Invocable)engine).invokeFunction(this.invokeFunctionName, inArgs[0], inArgs[1], inArgs[2]).toString();
  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();
199 200 return mapper.readTree(eval);
200 201 } catch (ScriptException | IllegalArgumentException th) {
201 202 throw th;
... ... @@ -206,6 +207,6 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
206 207 }
207 208
208 209 public void destroy() {
209   - engine = null;
  210 + //engine = null;
210 211 }
211 212 }
... ...
  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 delight.nashornsandbox.NashornSandbox;
  20 +import delight.nashornsandbox.NashornSandboxes;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Value;
  23 +import org.springframework.stereotype.Service;
  24 +
  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 +@Slf4j
  32 +@Service
  33 +public class NashornJsSandboxService implements JsSandboxService {
  34 +
  35 + @Value("${actors.rule.js_sandbox.monitor_thread_pool_size}")
  36 + private int monitorThreadPoolSize;
  37 +
  38 + @Value("${actors.rule.js_sandbox.max_cpu_time}")
  39 + private long maxCpuTime;
  40 +
  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 + }
  52 +
  53 + @PreDestroy
  54 + public void stop() {
  55 + if (monitorExecutorService != null) {
  56 + monitorExecutorService.shutdownNow();
  57 + }
  58 + }
  59 +
  60 + @Override
  61 + public Object eval(String js) throws ScriptException {
  62 + return sandbox.eval(js);
  63 + }
  64 +
  65 + @Override
  66 + public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
  67 + return sandbox.getSandboxedInvocable().invokeFunction(name, args);
  68 + }
  69 +
  70 +}
... ...
... ... @@ -238,6 +238,11 @@ actors:
238 238 mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:10}"
239 239 # Specify thread pool size for external call service
240 240 external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:10}"
  241 + js_sandbox:
  242 + # Specify thread pool size for JavaScript sandbox resource monitor
  243 + monitor_thread_pool_size: "${ACTORS_RULE_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
  244 + # Maximum CPU time in milliseconds allowed for script execution
  245 + max_cpu_time: "${ACTORS_RULE_JS_SANDBOX_MAX_CPU_TIME:100}"
241 246 chain:
242 247 # Errors for particular actor are persisted once per specified amount of milliseconds
243 248 error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}"
... ...
application/src/test/java/org/thingsboard/server/service/script/JsScriptEngineTest.java renamed from application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java
... ... @@ -17,6 +17,8 @@ package org.thingsboard.server.service.script;
17 17
18 18 import com.datastax.driver.core.utils.UUIDs;
19 19 import com.google.common.collect.Sets;
  20 +import org.junit.After;
  21 +import org.junit.Before;
20 22 import org.junit.Test;
21 23 import org.thingsboard.rule.engine.api.ScriptEngine;
22 24 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -25,17 +27,30 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
25 27 import javax.script.ScriptException;
26 28
27 29 import java.util.Set;
  30 +import java.util.concurrent.ExecutorService;
  31 +import java.util.concurrent.Executors;
28 32
29 33 import static org.junit.Assert.*;
30 34
31   -public class NashornJsEngineTest {
  35 +public class JsScriptEngineTest {
32 36
33 37 private ScriptEngine scriptEngine;
  38 + private TestNashornJsSandboxService jsSandboxService;
  39 +
  40 + @Before
  41 + public void beforeTest() throws Exception {
  42 + jsSandboxService = new TestNashornJsSandboxService(1, 100);
  43 + }
  44 +
  45 + @After
  46 + public void afterTest() throws Exception {
  47 + jsSandboxService.destroy();
  48 + }
34 49
35 50 @Test
36 51 public void msgCanBeUpdated() throws ScriptException {
37 52 String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
38   - scriptEngine = new NashornJsEngine(function, "Transform");
  53 + scriptEngine = new JsScriptEngine(jsSandboxService, function, "Transform");
39 54
40 55 TbMsgMetaData metaData = new TbMsgMetaData();
41 56 metaData.putValue("temp", "7");
... ... @@ -51,7 +66,7 @@ public class NashornJsEngineTest {
51 66 @Test
52 67 public void newAttributesCanBeAddedInMsg() throws ScriptException {
53 68 String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};";
54   - scriptEngine = new NashornJsEngine(function, "Transform");
  69 + scriptEngine = new JsScriptEngine(jsSandboxService, function, "Transform");
55 70 TbMsgMetaData metaData = new TbMsgMetaData();
56 71 metaData.putValue("temp", "7");
57 72 metaData.putValue("humidity", "99");
... ... @@ -66,7 +81,7 @@ public class NashornJsEngineTest {
66 81 @Test
67 82 public void payloadCanBeUpdated() throws ScriptException {
68 83 String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};";
69   - scriptEngine = new NashornJsEngine(function, "Transform");
  84 + scriptEngine = new JsScriptEngine(jsSandboxService, function, "Transform");
70 85 TbMsgMetaData metaData = new TbMsgMetaData();
71 86 metaData.putValue("temp", "7");
72 87 metaData.putValue("humidity", "99");
... ... @@ -83,7 +98,7 @@ public class NashornJsEngineTest {
83 98 @Test
84 99 public void metadataAccessibleForFilter() throws ScriptException {
85 100 String function = "return metadata.humidity < 15;";
86   - scriptEngine = new NashornJsEngine(function, "Filter");
  101 + scriptEngine = new JsScriptEngine(jsSandboxService, function, "Filter");
87 102 TbMsgMetaData metaData = new TbMsgMetaData();
88 103 metaData.putValue("temp", "7");
89 104 metaData.putValue("humidity", "99");
... ... @@ -96,7 +111,7 @@ public class NashornJsEngineTest {
96 111 @Test
97 112 public void dataAccessibleForFilter() throws ScriptException {
98 113 String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;";
99   - scriptEngine = new NashornJsEngine(function, "Filter");
  114 + scriptEngine = new JsScriptEngine(jsSandboxService, function, "Filter");
100 115 TbMsgMetaData metaData = new TbMsgMetaData();
101 116 metaData.putValue("temp", "7");
102 117 metaData.putValue("humidity", "99");
... ... @@ -116,7 +131,7 @@ public class NashornJsEngineTest {
116 131 "};\n" +
117 132 "\n" +
118 133 "return nextRelation(metadata, msg);";
119   - scriptEngine = new NashornJsEngine(jsCode, "Switch");
  134 + scriptEngine = new JsScriptEngine(jsSandboxService, jsCode, "Switch");
120 135 TbMsgMetaData metaData = new TbMsgMetaData();
121 136 metaData.putValue("temp", "10");
122 137 metaData.putValue("humidity", "99");
... ... @@ -137,7 +152,7 @@ public class NashornJsEngineTest {
137 152 "};\n" +
138 153 "\n" +
139 154 "return nextRelation(metadata, msg);";
140   - scriptEngine = new NashornJsEngine(jsCode, "Switch");
  155 + scriptEngine = new JsScriptEngine(jsSandboxService, jsCode, "Switch");
141 156 TbMsgMetaData metaData = new TbMsgMetaData();
142 157 metaData.putValue("temp", "10");
143 158 metaData.putValue("humidity", "99");
... ...
  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 delight.nashornsandbox.NashornSandbox;
  20 +import delight.nashornsandbox.NashornSandboxes;
  21 +
  22 +import javax.script.ScriptException;
  23 +import java.util.concurrent.ExecutorService;
  24 +import java.util.concurrent.Executors;
  25 +
  26 +public class TestNashornJsSandboxService implements JsSandboxService {
  27 +
  28 + private NashornSandbox sandbox = NashornSandboxes.create();
  29 + private ExecutorService monitorExecutorService;
  30 +
  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);
  37 + }
  38 +
  39 + @Override
  40 + public Object eval(String js) throws ScriptException {
  41 + return sandbox.eval(js);
  42 + }
  43 +
  44 + @Override
  45 + public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException {
  46 + return sandbox.getSandboxedInvocable().invokeFunction(name, args);
  47 + }
  48 +
  49 + public void destroy() {
  50 + if (monitorExecutorService != null) {
  51 + monitorExecutorService.shutdownNow();
  52 + }
  53 + }
  54 +}
... ...
... ... @@ -81,6 +81,7 @@
81 81 org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/*
82 82 </sonar.exclusions>
83 83 <elasticsearch.version>5.0.2</elasticsearch.version>
  84 + <delight-nashorn-sandbox.version>0.1.14</delight-nashorn-sandbox.version>
84 85 </properties>
85 86
86 87 <modules>
... ... @@ -814,6 +815,11 @@
814 815 <artifactId>rest</artifactId>
815 816 <version>${elasticsearch.version}</version>
816 817 </dependency>
  818 + <dependency>
  819 + <groupId>org.javadelight</groupId>
  820 + <artifactId>delight-nashorn-sandbox</artifactId>
  821 + <version>${delight-nashorn-sandbox.version}</version>
  822 + </dependency>
817 823 </dependencies>
818 824 </dependencyManagement>
819 825
... ...