js-script-engine-api: js sync calls replaced with completely async calls (CE API only)
This commit is contained in:
parent
f3757ad127
commit
b397dfb518
@ -390,13 +390,13 @@ public class RuleChainController extends BaseController {
|
|||||||
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
|
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
|
||||||
switch (scriptType) {
|
switch (scriptType) {
|
||||||
case "update":
|
case "update":
|
||||||
output = msgToOutput(engine.executeUpdate(inMsg));
|
output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
|
||||||
break;
|
break;
|
||||||
case "generate":
|
case "generate":
|
||||||
output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
|
output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS));
|
||||||
break;
|
break;
|
||||||
case "filter":
|
case "filter":
|
||||||
boolean result = engine.executeFilter(inMsg);
|
boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
|
||||||
output = Boolean.toString(result);
|
output = Boolean.toString(result);
|
||||||
break;
|
break;
|
||||||
case "switch":
|
case "switch":
|
||||||
@ -404,11 +404,11 @@ public class RuleChainController extends BaseController {
|
|||||||
output = objectMapper.writeValueAsString(states);
|
output = objectMapper.writeValueAsString(states);
|
||||||
break;
|
break;
|
||||||
case "json":
|
case "json":
|
||||||
JsonNode json = engine.executeJson(inMsg);
|
JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
|
||||||
output = objectMapper.writeValueAsString(json);
|
output = objectMapper.writeValueAsString(json);
|
||||||
break;
|
break;
|
||||||
case "string":
|
case "string":
|
||||||
output = engine.executeToString(inMsg);
|
output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported script type: " + scriptType);
|
throw new IllegalArgumentException("Unsupported script type: " + scriptType);
|
||||||
|
|||||||
@ -106,25 +106,15 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException {
|
|
||||||
JsonNode result = executeScript(msg);
|
|
||||||
if (result.isObject()) {
|
|
||||||
return Collections.singletonList(unbindMsg(result, msg));
|
|
||||||
} else if (result.isArray()){
|
|
||||||
List<TbMsg> res = new ArrayList<>(result.size());
|
|
||||||
result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
|
|
||||||
return res;
|
|
||||||
} else {
|
|
||||||
log.warn("Wrong result type: {}", result.getNodeType());
|
|
||||||
throw new ScriptException("Wrong result type: " + result.getNodeType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
|
public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
|
||||||
ListenableFuture<JsonNode> result = executeScriptAsync(msg);
|
ListenableFuture<JsonNode> result = executeScriptAsync(msg);
|
||||||
return Futures.transformAsync(result, json -> {
|
return Futures.transformAsync(result,
|
||||||
|
json -> executeUpdateTransform(msg, json),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) {
|
||||||
if (json.isObject()) {
|
if (json.isObject()) {
|
||||||
return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));
|
return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));
|
||||||
} else if (json.isArray()) {
|
} else if (json.isArray()) {
|
||||||
@ -132,70 +122,61 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
|
|||||||
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
|
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
|
||||||
return Futures.immediateFuture(res);
|
return Futures.immediateFuture(res);
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
log.warn("Wrong result type: {}", json.getNodeType());
|
log.warn("Wrong result type: {}", json.getNodeType());
|
||||||
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
|
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
|
||||||
}
|
}
|
||||||
}, MoreExecutors.directExecutor());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) {
|
public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) {
|
||||||
log.trace("execute generate async, prevMsg {}", prevMsg);
|
return Futures.transformAsync(executeScriptAsync(prevMsg),
|
||||||
return Futures.transformAsync(executeScriptAsync(prevMsg), result -> {
|
result -> executeGenerateTransform(prevMsg, result),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) {
|
||||||
if (!result.isObject()) {
|
if (!result.isObject()) {
|
||||||
log.warn("Wrong result type: {}", result.getNodeType());
|
log.warn("Wrong result type: {}", result.getNodeType());
|
||||||
Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
|
Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(unbindMsg(result, prevMsg));
|
return Futures.immediateFuture(unbindMsg(result, prevMsg));
|
||||||
}, MoreExecutors.directExecutor());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonNode executeJson(TbMsg msg) throws ScriptException {
|
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
|
||||||
return executeScript(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException {
|
|
||||||
return executeScriptAsync(msg);
|
return executeScriptAsync(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String executeToString(TbMsg msg) throws ScriptException {
|
public ListenableFuture<String> executeToStringAsync(TbMsg msg) {
|
||||||
JsonNode result = executeScript(msg);
|
return Futures.transformAsync(executeScriptAsync(msg),
|
||||||
if (!result.isTextual()) {
|
this::executeToStringTransform,
|
||||||
log.warn("Wrong result type: {}", result.getNodeType());
|
MoreExecutors.directExecutor());
|
||||||
throw new ScriptException("Wrong result type: " + result.getNodeType());
|
|
||||||
}
|
|
||||||
return result.asText();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
ListenableFuture<String> executeToStringTransform(JsonNode result) {
|
||||||
public boolean executeFilter(TbMsg msg) throws ScriptException {
|
if (result.isTextual()) {
|
||||||
JsonNode result = executeScript(msg);
|
return Futures.immediateFuture(result.asText());
|
||||||
if (!result.isBoolean()) {
|
|
||||||
log.warn("Wrong result type: {}", result.getNodeType());
|
|
||||||
throw new ScriptException("Wrong result type: " + result.getNodeType());
|
|
||||||
}
|
}
|
||||||
return result.asBoolean();
|
log.warn("Wrong result type: {}", result.getNodeType());
|
||||||
|
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
|
public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
|
||||||
ListenableFuture<JsonNode> result = executeScriptAsync(msg);
|
return Futures.transformAsync(executeScriptAsync(msg),
|
||||||
return Futures.transformAsync(result, json -> {
|
this::executeFilterTransform,
|
||||||
if (!json.isBoolean()) {
|
MoreExecutors.directExecutor());
|
||||||
log.warn("Wrong result type: {}", json.getNodeType());
|
|
||||||
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
|
|
||||||
} else {
|
|
||||||
return Futures.immediateFuture(json.asBoolean());
|
|
||||||
}
|
|
||||||
}, MoreExecutors.directExecutor());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListenableFuture<Set<String>> executeSwitchPostProcessAsyncFunction(JsonNode result) {
|
ListenableFuture<Boolean> executeFilterTransform(JsonNode json) {
|
||||||
|
if (json.isBoolean()) {
|
||||||
|
return Futures.immediateFuture(json.asBoolean());
|
||||||
|
}
|
||||||
|
log.warn("Wrong result type: {}", json.getNodeType());
|
||||||
|
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
|
||||||
|
}
|
||||||
|
|
||||||
|
ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) {
|
||||||
if (result.isTextual()) {
|
if (result.isTextual()) {
|
||||||
return Futures.immediateFuture(Collections.singleton(result.asText()));
|
return Futures.immediateFuture(Collections.singleton(result.asText()));
|
||||||
}
|
}
|
||||||
@ -217,34 +198,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) {
|
public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) {
|
||||||
log.trace("execute switch async, msg {}", msg);
|
|
||||||
return Futures.transformAsync(executeScriptAsync(msg),
|
return Futures.transformAsync(executeScriptAsync(msg),
|
||||||
this::executeSwitchPostProcessAsyncFunction,
|
this::executeSwitchTransform,
|
||||||
MoreExecutors.directExecutor()); //usually runs in a callbackExecutor
|
MoreExecutors.directExecutor()); //usually runs in a callbackExecutor
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonNode executeScript(TbMsg msg) throws ScriptException {
|
ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) {
|
||||||
try {
|
|
||||||
String[] inArgs = prepareArgs(msg);
|
|
||||||
String eval = sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
|
|
||||||
return mapper.readTree(eval);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
if (e.getCause() instanceof ScriptException) {
|
|
||||||
throw (ScriptException) e.getCause();
|
|
||||||
} else if (e.getCause() instanceof RuntimeException) {
|
|
||||||
throw new ScriptException(e.getCause().getMessage());
|
|
||||||
} else {
|
|
||||||
throw new ScriptException(e);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new ScriptException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ListenableFuture<JsonNode> executeScriptAsync(TbMsg msg) {
|
|
||||||
log.trace("execute script async, msg {}", msg);
|
log.trace("execute script async, msg {}", msg);
|
||||||
String[] inArgs = prepareArgs(msg);
|
String[] inArgs = prepareArgs(msg);
|
||||||
return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]),
|
return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListenableFuture<JsonNode> executeScriptAsync(CustomerId customerId, Object... args) {
|
||||||
|
return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args),
|
||||||
o -> {
|
o -> {
|
||||||
try {
|
try {
|
||||||
return Futures.immediateFuture(mapper.readTree(o.toString()));
|
return Futures.immediateFuture(mapper.readTree(o.toString()));
|
||||||
|
|||||||
@ -19,14 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode;
|
|||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import org.thingsboard.server.common.msg.TbMsg;
|
import org.thingsboard.server.common.msg.TbMsg;
|
||||||
|
|
||||||
import javax.script.ScriptException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface ScriptEngine {
|
public interface ScriptEngine {
|
||||||
|
|
||||||
List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException;
|
|
||||||
|
|
||||||
ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg);
|
ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg);
|
||||||
|
|
||||||
ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg);
|
ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg);
|
||||||
@ -37,11 +34,9 @@ public interface ScriptEngine {
|
|||||||
|
|
||||||
ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg);
|
ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg);
|
||||||
|
|
||||||
JsonNode executeJson(TbMsg msg) throws ScriptException;
|
ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg);
|
||||||
|
|
||||||
ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException;
|
ListenableFuture<String> executeToStringAsync(TbMsg msg);
|
||||||
|
|
||||||
String executeToString(TbMsg msg) throws ScriptException;
|
|
||||||
|
|
||||||
void destroy();
|
void destroy();
|
||||||
|
|
||||||
|
|||||||
@ -214,6 +214,10 @@ public interface TbContext {
|
|||||||
|
|
||||||
EdgeEventService getEdgeEventService();
|
EdgeEventService getEdgeEventService();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Js script executors call are completely asynchronous
|
||||||
|
* */
|
||||||
|
@Deprecated
|
||||||
ListeningExecutor getJsExecutor();
|
ListeningExecutor getJsExecutor();
|
||||||
|
|
||||||
ListeningExecutor getMailExecutor();
|
ListeningExecutor getMailExecutor();
|
||||||
|
|||||||
@ -15,8 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package org.thingsboard.rule.engine.action;
|
package org.thingsboard.rule.engine.action;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.thingsboard.common.util.ListeningExecutor;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.thingsboard.rule.engine.api.RuleNode;
|
import org.thingsboard.rule.engine.api.RuleNode;
|
||||||
import org.thingsboard.rule.engine.api.ScriptEngine;
|
import org.thingsboard.rule.engine.api.ScriptEngine;
|
||||||
import org.thingsboard.rule.engine.api.TbContext;
|
import org.thingsboard.rule.engine.api.TbContext;
|
||||||
@ -55,18 +58,21 @@ public class TbLogNode implements TbNode {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMsg(TbContext ctx, TbMsg msg) {
|
public void onMsg(TbContext ctx, TbMsg msg) {
|
||||||
ListeningExecutor jsExecutor = ctx.getJsExecutor();
|
|
||||||
ctx.logJsEvalRequest();
|
ctx.logJsEvalRequest();
|
||||||
withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)),
|
Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback<String>() {
|
||||||
toString -> {
|
@Override
|
||||||
|
public void onSuccess(@Nullable String result) {
|
||||||
ctx.logJsEvalResponse();
|
ctx.logJsEvalResponse();
|
||||||
log.info(toString);
|
log.info(result);
|
||||||
ctx.tellSuccess(msg);
|
ctx.tellSuccess(msg);
|
||||||
},
|
}
|
||||||
t -> {
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
ctx.logJsEvalResponse();
|
ctx.logJsEvalResponse();
|
||||||
ctx.tellFailure(msg, t);
|
ctx.tellFailure(msg, t);
|
||||||
});
|
}
|
||||||
|
}, MoreExecutors.directExecutor()); //usually js responses runs on js callback executor
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -33,8 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg;
|
|||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.thingsboard.common.util.DonAsynchron.withCallback;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RuleNode(
|
@RuleNode(
|
||||||
type = ComponentType.FILTER,
|
type = ComponentType.FILTER,
|
||||||
|
|||||||
@ -64,7 +64,7 @@ public class TbJsSwitchNodeTest {
|
|||||||
private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
|
private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void multipleRoutesAreAllowed() throws TbNodeException, ScriptException {
|
public void multipleRoutesAreAllowed() throws TbNodeException {
|
||||||
initWithScript();
|
initWithScript();
|
||||||
TbMsgMetaData metaData = new TbMsgMetaData();
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
metaData.putValue("temp", "10");
|
metaData.putValue("temp", "10");
|
||||||
@ -72,11 +72,9 @@ public class TbJsSwitchNodeTest {
|
|||||||
String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
|
String rawJson = "{\"name\": \"Vit\", \"passed\": 5}";
|
||||||
|
|
||||||
TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
|
TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId);
|
||||||
mockJsExecutor();
|
|
||||||
when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three")));
|
when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three")));
|
||||||
|
|
||||||
node.onMsg(ctx, msg);
|
node.onMsg(ctx, msg);
|
||||||
verify(ctx).getJsExecutor();
|
|
||||||
verify(ctx).tellNext(msg, Sets.newHashSet("one", "three"));
|
verify(ctx).tellNext(msg, Sets.newHashSet("one", "three"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,19 +90,6 @@ public class TbJsSwitchNodeTest {
|
|||||||
node.init(ctx, nodeConfiguration);
|
node.init(ctx, nodeConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void mockJsExecutor() {
|
|
||||||
when(ctx.getJsExecutor()).thenReturn(executor);
|
|
||||||
doAnswer((Answer<ListenableFuture<Set<String>>>) invocationOnMock -> {
|
|
||||||
try {
|
|
||||||
Callable task = (Callable) (invocationOnMock.getArguments())[0];
|
|
||||||
return Futures.immediateFuture((Set<String>) task.call());
|
|
||||||
} catch (Throwable th) {
|
|
||||||
return Futures.immediateFailedFuture(th);
|
|
||||||
}
|
|
||||||
}).when(executor).executeAsync(ArgumentMatchers.any(Callable.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyError(TbMsg msg, String message, Class expectedClass) {
|
private void verifyError(TbMsg msg, String message, Class expectedClass) {
|
||||||
ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
|
ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
|
||||||
verify(ctx).tellFailure(same(msg), captor.capture());
|
verify(ctx).tellFailure(same(msg), captor.capture());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user