Commit b397dfb518e101558790127b5e7ad9a7e7aef34f

Authored by Sergey Matvienko
1 parent f3757ad1

js-script-engine-api: js sync calls replaced with completely async calls (CE API only)

@@ -390,13 +390,13 @@ public class RuleChainController extends BaseController { @@ -390,13 +390,13 @@ public class RuleChainController extends BaseController {
390 TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); 390 TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
391 switch (scriptType) { 391 switch (scriptType) {
392 case "update": 392 case "update":
393 - output = msgToOutput(engine.executeUpdate(inMsg)); 393 + output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
394 break; 394 break;
395 case "generate": 395 case "generate":
396 output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); 396 output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
397 break; 397 break;
398 case "filter": 398 case "filter":
399 - boolean result = engine.executeFilter(inMsg); 399 + boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
400 output = Boolean.toString(result); 400 output = Boolean.toString(result);
401 break; 401 break;
402 case "switch": 402 case "switch":
@@ -404,11 +404,11 @@ public class RuleChainController extends BaseController { @@ -404,11 +404,11 @@ public class RuleChainController extends BaseController {
404 output = objectMapper.writeValueAsString(states); 404 output = objectMapper.writeValueAsString(states);
405 break; 405 break;
406 case "json": 406 case "json":
407 - JsonNode json = engine.executeJson(inMsg); 407 + JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
408 output = objectMapper.writeValueAsString(json); 408 output = objectMapper.writeValueAsString(json);
409 break; 409 break;
410 case "string": 410 case "string":
411 - output = engine.executeToString(inMsg); 411 + output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
412 break; 412 break;
413 default: 413 default:
414 throw new IllegalArgumentException("Unsupported script type: " + scriptType); 414 throw new IllegalArgumentException("Unsupported script type: " + scriptType);
@@ -107,95 +107,76 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S @@ -107,95 +107,76 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
107 } 107 }
108 108
109 @Override 109 @Override
110 - public List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException {  
111 - JsonNode result = executeScript(msg);  
112 - if (result.isObject()) {  
113 - return Collections.singletonList(unbindMsg(result, msg));  
114 - } else if (result.isArray()){  
115 - List<TbMsg> res = new ArrayList<>(result.size());  
116 - result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));  
117 - return res;  
118 - } else {  
119 - log.warn("Wrong result type: {}", result.getNodeType());  
120 - throw new ScriptException("Wrong result type: " + result.getNodeType());  
121 - }  
122 - }  
123 -  
124 - @Override  
125 public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { 110 public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
126 ListenableFuture<JsonNode> result = executeScriptAsync(msg); 111 ListenableFuture<JsonNode> result = executeScriptAsync(msg);
127 - return Futures.transformAsync(result, json -> {  
128 - if (json.isObject()) {  
129 - return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));  
130 - } else if (json.isArray()){  
131 - List<TbMsg> res = new ArrayList<>(json.size());  
132 - json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));  
133 - return Futures.immediateFuture(res);  
134 - }  
135 - else{  
136 - log.warn("Wrong result type: {}", json.getNodeType());  
137 - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));  
138 - }  
139 - }, MoreExecutors.directExecutor()); 112 + return Futures.transformAsync(result,
  113 + json -> executeUpdateTransform(msg, json),
  114 + MoreExecutors.directExecutor());
  115 + }
  116 +
  117 + ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) {
  118 + if (json.isObject()) {
  119 + return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));
  120 + } else if (json.isArray()) {
  121 + List<TbMsg> res = new ArrayList<>(json.size());
  122 + json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
  123 + return Futures.immediateFuture(res);
  124 + }
  125 + log.warn("Wrong result type: {}", json.getNodeType());
  126 + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
140 } 127 }
141 128
142 @Override 129 @Override
143 public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) { 130 public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) {
144 - log.trace("execute generate async, prevMsg {}", prevMsg);  
145 - return Futures.transformAsync(executeScriptAsync(prevMsg), result -> {  
146 - if (!result.isObject()) {  
147 - log.warn("Wrong result type: {}", result.getNodeType());  
148 - Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));  
149 - }  
150 - return Futures.immediateFuture(unbindMsg(result, prevMsg));  
151 - }, MoreExecutors.directExecutor());  
152 - 131 + return Futures.transformAsync(executeScriptAsync(prevMsg),
  132 + result -> executeGenerateTransform(prevMsg, result),
  133 + MoreExecutors.directExecutor());
153 } 134 }
154 135
155 - @Override  
156 - public JsonNode executeJson(TbMsg msg) throws ScriptException {  
157 - return executeScript(msg); 136 + ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) {
  137 + if (!result.isObject()) {
  138 + log.warn("Wrong result type: {}", result.getNodeType());
  139 + Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
  140 + }
  141 + return Futures.immediateFuture(unbindMsg(result, prevMsg));
158 } 142 }
159 143
160 @Override 144 @Override
161 - public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException { 145 + public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
162 return executeScriptAsync(msg); 146 return executeScriptAsync(msg);
163 } 147 }
164 148
165 @Override 149 @Override
166 - public String executeToString(TbMsg msg) throws ScriptException {  
167 - JsonNode result = executeScript(msg);  
168 - if (!result.isTextual()) {  
169 - log.warn("Wrong result type: {}", result.getNodeType());  
170 - throw new ScriptException("Wrong result type: " + result.getNodeType());  
171 - }  
172 - return result.asText(); 150 + public ListenableFuture<String> executeToStringAsync(TbMsg msg) {
  151 + return Futures.transformAsync(executeScriptAsync(msg),
  152 + this::executeToStringTransform,
  153 + MoreExecutors.directExecutor());
173 } 154 }
174 155
175 - @Override  
176 - public boolean executeFilter(TbMsg msg) throws ScriptException {  
177 - JsonNode result = executeScript(msg);  
178 - if (!result.isBoolean()) {  
179 - log.warn("Wrong result type: {}", result.getNodeType());  
180 - throw new ScriptException("Wrong result type: " + result.getNodeType()); 156 + ListenableFuture<String> executeToStringTransform(JsonNode result) {
  157 + if (result.isTextual()) {
  158 + return Futures.immediateFuture(result.asText());
181 } 159 }
182 - return result.asBoolean(); 160 + log.warn("Wrong result type: {}", result.getNodeType());
  161 + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
183 } 162 }
184 163
185 @Override 164 @Override
186 public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { 165 public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
187 - ListenableFuture<JsonNode> result = executeScriptAsync(msg);  
188 - return Futures.transformAsync(result, json -> {  
189 - if (!json.isBoolean()) {  
190 - log.warn("Wrong result type: {}", json.getNodeType());  
191 - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));  
192 - } else {  
193 - return Futures.immediateFuture(json.asBoolean());  
194 - }  
195 - }, MoreExecutors.directExecutor()); 166 + return Futures.transformAsync(executeScriptAsync(msg),
  167 + this::executeFilterTransform,
  168 + MoreExecutors.directExecutor());
  169 + }
  170 +
  171 + ListenableFuture<Boolean> executeFilterTransform(JsonNode json) {
  172 + if (json.isBoolean()) {
  173 + return Futures.immediateFuture(json.asBoolean());
  174 + }
  175 + log.warn("Wrong result type: {}", json.getNodeType());
  176 + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
196 } 177 }
197 178
198 - ListenableFuture<Set<String>> executeSwitchPostProcessAsyncFunction(JsonNode result) { 179 + ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) {
199 if (result.isTextual()) { 180 if (result.isTextual()) {
200 return Futures.immediateFuture(Collections.singleton(result.asText())); 181 return Futures.immediateFuture(Collections.singleton(result.asText()));
201 } 182 }
@@ -217,34 +198,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S @@ -217,34 +198,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
217 198
218 @Override 199 @Override
219 public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) { 200 public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) {
220 - log.trace("execute switch async, msg {}", msg);  
221 return Futures.transformAsync(executeScriptAsync(msg), 201 return Futures.transformAsync(executeScriptAsync(msg),
222 - this::executeSwitchPostProcessAsyncFunction, 202 + this::executeSwitchTransform,
223 MoreExecutors.directExecutor()); //usually runs in a callbackExecutor 203 MoreExecutors.directExecutor()); //usually runs in a callbackExecutor
224 } 204 }
225 205
226 - private JsonNode executeScript(TbMsg msg) throws ScriptException {  
227 - try {  
228 - String[] inArgs = prepareArgs(msg);  
229 - String eval = sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString();  
230 - return mapper.readTree(eval);  
231 - } catch (ExecutionException e) {  
232 - if (e.getCause() instanceof ScriptException) {  
233 - throw (ScriptException) e.getCause();  
234 - } else if (e.getCause() instanceof RuntimeException) {  
235 - throw new ScriptException(e.getCause().getMessage());  
236 - } else {  
237 - throw new ScriptException(e);  
238 - }  
239 - } catch (Exception e) {  
240 - throw new ScriptException(e);  
241 - }  
242 - }  
243 -  
244 - private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) { 206 + ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) {
245 log.trace("execute script async, msg {}", msg); 207 log.trace("execute script async, msg {}", msg);
246 String[] inArgs = prepareArgs(msg); 208 String[] inArgs = prepareArgs(msg);
247 - return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]), 209 + return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]);
  210 + }
  211 +
  212 + ListenableFuture<JsonNode> executeScriptAsync(CustomerId customerId, Object... args) {
  213 + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args),
248 o -> { 214 o -> {
249 try { 215 try {
250 return Futures.immediateFuture(mapper.readTree(o.toString())); 216 return Futures.immediateFuture(mapper.readTree(o.toString()));
@@ -19,14 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode; @@ -19,14 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode;
19 import com.google.common.util.concurrent.ListenableFuture; 19 import com.google.common.util.concurrent.ListenableFuture;
20 import org.thingsboard.server.common.msg.TbMsg; 20 import org.thingsboard.server.common.msg.TbMsg;
21 21
22 -import javax.script.ScriptException;  
23 import java.util.List; 22 import java.util.List;
24 import java.util.Set; 23 import java.util.Set;
25 24
26 public interface ScriptEngine { 25 public interface ScriptEngine {
27 26
28 - List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException;  
29 -  
30 ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg); 27 ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg);
31 28
32 ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg); 29 ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg);
@@ -37,11 +34,9 @@ public interface ScriptEngine { @@ -37,11 +34,9 @@ public interface ScriptEngine {
37 34
38 ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg); 35 ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg);
39 36
40 - JsonNode executeJson(TbMsg msg) throws ScriptException;  
41 -  
42 - ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException; 37 + ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg);
43 38
44 - String executeToString(TbMsg msg) throws ScriptException; 39 + ListenableFuture<String> executeToStringAsync(TbMsg msg);
45 40
46 void destroy(); 41 void destroy();
47 42
@@ -214,6 +214,10 @@ public interface TbContext { @@ -214,6 +214,10 @@ public interface TbContext {
214 214
215 EdgeEventService getEdgeEventService(); 215 EdgeEventService getEdgeEventService();
216 216
  217 + /**
  218 + * Js script executors call are completely asynchronous
  219 + * */
  220 + @Deprecated
217 ListeningExecutor getJsExecutor(); 221 ListeningExecutor getJsExecutor();
218 222
219 ListeningExecutor getMailExecutor(); 223 ListeningExecutor getMailExecutor();
@@ -15,8 +15,11 @@ @@ -15,8 +15,11 @@
15 */ 15 */
16 package org.thingsboard.rule.engine.action; 16 package org.thingsboard.rule.engine.action;
17 17
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.MoreExecutors;
18 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
19 -import org.thingsboard.common.util.ListeningExecutor; 22 +import org.checkerframework.checker.nullness.qual.Nullable;
20 import org.thingsboard.rule.engine.api.RuleNode; 23 import org.thingsboard.rule.engine.api.RuleNode;
21 import org.thingsboard.rule.engine.api.ScriptEngine; 24 import org.thingsboard.rule.engine.api.ScriptEngine;
22 import org.thingsboard.rule.engine.api.TbContext; 25 import org.thingsboard.rule.engine.api.TbContext;
@@ -55,18 +58,21 @@ public class TbLogNode implements TbNode { @@ -55,18 +58,21 @@ public class TbLogNode implements TbNode {
55 58
56 @Override 59 @Override
57 public void onMsg(TbContext ctx, TbMsg msg) { 60 public void onMsg(TbContext ctx, TbMsg msg) {
58 - ListeningExecutor jsExecutor = ctx.getJsExecutor();  
59 ctx.logJsEvalRequest(); 61 ctx.logJsEvalRequest();
60 - withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)),  
61 - toString -> {  
62 - ctx.logJsEvalResponse();  
63 - log.info(toString);  
64 - ctx.tellSuccess(msg);  
65 - },  
66 - t -> {  
67 - ctx.logJsEvalResponse();  
68 - ctx.tellFailure(msg, t);  
69 - }); 62 + Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback<String>() {
  63 + @Override
  64 + public void onSuccess(@Nullable String result) {
  65 + ctx.logJsEvalResponse();
  66 + log.info(result);
  67 + ctx.tellSuccess(msg);
  68 + }
  69 +
  70 + @Override
  71 + public void onFailure(Throwable t) {
  72 + ctx.logJsEvalResponse();
  73 + ctx.tellFailure(msg, t);
  74 + }
  75 + }, MoreExecutors.directExecutor()); //usually js responses runs on js callback executor
70 } 76 }
71 77
72 @Override 78 @Override
@@ -33,8 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg; @@ -33,8 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg;
33 33
34 import java.util.Set; 34 import java.util.Set;
35 35
36 -import static org.thingsboard.common.util.DonAsynchron.withCallback;  
37 -  
38 @Slf4j 36 @Slf4j
39 @RuleNode( 37 @RuleNode(
40 type = ComponentType.FILTER, 38 type = ComponentType.FILTER,
@@ -64,7 +64,7 @@ public class TbJsSwitchNodeTest { @@ -64,7 +64,7 @@ public class TbJsSwitchNodeTest {
64 private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); 64 private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
65 65
66 @Test 66 @Test
67 - public void multipleRoutesAreAllowed() throws TbNodeException, ScriptException { 67 + public void multipleRoutesAreAllowed() throws TbNodeException {
68 initWithScript(); 68 initWithScript();
69 TbMsgMetaData metaData = new TbMsgMetaData(); 69 TbMsgMetaData metaData = new TbMsgMetaData();
70 metaData.putValue("temp", "10"); 70 metaData.putValue("temp", "10");
@@ -72,11 +72,9 @@ public class TbJsSwitchNodeTest { @@ -72,11 +72,9 @@ public class TbJsSwitchNodeTest {
72 String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; 72 String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
73 73
74 TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); 74 TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
75 - mockJsExecutor();  
76 when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three"))); 75 when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three")));
77 76
78 node.onMsg(ctx, msg); 77 node.onMsg(ctx, msg);
79 - verify(ctx).getJsExecutor();  
80 verify(ctx).tellNext(msg, Sets.newHashSet("one", "three")); 78 verify(ctx).tellNext(msg, Sets.newHashSet("one", "three"));
81 } 79 }
82 80
@@ -92,19 +90,6 @@ public class TbJsSwitchNodeTest { @@ -92,19 +90,6 @@ public class TbJsSwitchNodeTest {
92 node.init(ctx, nodeConfiguration); 90 node.init(ctx, nodeConfiguration);
93 } 91 }
94 92
95 - @SuppressWarnings("unchecked")  
96 - private void mockJsExecutor() {  
97 - when(ctx.getJsExecutor()).thenReturn(executor);  
98 - doAnswer((Answer<ListenableFuture<Set<String>>>) invocationOnMock -> {  
99 - try {  
100 - Callable task = (Callable) (invocationOnMock.getArguments())[0];  
101 - return Futures.immediateFuture((Set<String>) task.call());  
102 - } catch (Throwable th) {  
103 - return Futures.immediateFailedFuture(th);  
104 - }  
105 - }).when(executor).executeAsync(ArgumentMatchers.any(Callable.class));  
106 - }  
107 -  
108 private void verifyError(TbMsg msg, String message, Class expectedClass) { 93 private void verifyError(TbMsg msg, String message, Class expectedClass) {
109 ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); 94 ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
110 verify(ctx).tellFailure(same(msg), captor.capture()); 95 verify(ctx).tellFailure(same(msg), captor.capture());