Merge branch 'feature/mvel-executor' of github.com:thingsboard/thingsboard into feature/mvel-executor

This commit is contained in:
Igor Kulikov 2022-10-19 19:10:42 +03:00
commit e21e37469b
9 changed files with 86 additions and 62 deletions

View File

@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.EventLoopGroup;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.util.Arrays;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
@ -443,19 +444,41 @@ class DefaultTbContext implements TbContext {
return mainCtx.getExternalCallExecutorService();
}
@Deprecated
@Override
public ScriptEngine createJsScriptEngine(String script, String... argNames) {
return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsInvokeService(), script, argNames);
}
@Override
public ScriptEngine createMvelScriptEngine(String script, String... argNames) {
private ScriptEngine createMvelScriptEngine(String script, String... argNames) {
if (mainCtx.getMvelInvokeService() == null) {
throw new RuntimeException("MVEL execution is disabled!");
}
return new RuleNodeMvelScriptEngine(getTenantId(), mainCtx.getMvelInvokeService(), script, argNames);
}
@Override
public ScriptEngine createScriptEngine(ScriptLanguage scriptLang, String script, String... argNames) {
if (scriptLang == null) {
scriptLang = ScriptLanguage.JS;
}
if (StringUtils.isBlank(script)) {
throw new RuntimeException(scriptLang.name() + " script is blank!");
}
switch (scriptLang) {
case JS:
return createJsScriptEngine(script, argNames);
case MVEL:
if (Arrays.isNullOrEmpty(argNames)) {
return createMvelScriptEngine(script, "msg", "metadata", "msgType");
} else {
return createMvelScriptEngine(script, argNames);
}
default:
throw new RuntimeException("Unsupported script language: " + scriptLang.name());
}
}
@Override
public void logJsEvalRequest() {
if (mainCtx.isStatisticsEnabled()) {
@ -708,24 +731,6 @@ class DefaultTbContext implements TbContext {
return mainCtx.getTenantProfileCache().get(getTenantId());
}
@Override
public ScriptEngine createScriptEngine(ScriptLanguage scriptLang, String script) {
if (scriptLang == null) {
scriptLang = ScriptLanguage.JS;
}
if (StringUtils.isBlank(script)) {
throw new RuntimeException(scriptLang.name() + " script is blank!");
}
switch (scriptLang) {
case JS:
return createJsScriptEngine(script);
case MVEL:
return createMvelScriptEngine(script, "msg", "metadata", "msgType");
default:
throw new RuntimeException("Unsupported script language: " + scriptLang.name());
}
}
private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("ruleNodeId", ruleNodeId.toString());

View File

@ -262,9 +262,15 @@ public interface TbContext {
SmsSenderFactory getSmsSenderFactory();
/**
* Creates JS Script Engine
* @deprecated
* <p> Use {@link #createScriptEngine} instead.
*
*/
ScriptEngine createJsScriptEngine(String script, String... argNames);
ScriptEngine createMvelScriptEngine(String script, String... argNames);
ScriptEngine createScriptEngine(ScriptLanguage scriptLang, String script, String... argNames);
void logJsEvalRequest();
@ -302,5 +308,4 @@ public interface TbContext {
TenantProfile getTenantProfile();
ScriptEngine createScriptEngine(ScriptLanguage scriptLang, String s);
}

View File

@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
@Slf4j
@ -47,14 +48,15 @@ import org.thingsboard.server.common.msg.TbMsg;
public class TbLogNode implements TbNode {
private TbLogNodeConfiguration config;
private ScriptEngine jsEngine;
private ScriptEngine scriptEngine;
private boolean standard;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbLogNodeConfiguration.class);
this.standard = new TbLogNodeConfiguration().defaultConfiguration().getJsScript().equals(config.getJsScript());
this.jsEngine = this.standard ? null : ctx.createJsScriptEngine(config.getJsScript());
this.scriptEngine = this.standard ? null : ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript());
}
@Override
@ -65,7 +67,7 @@ public class TbLogNode implements TbNode {
}
ctx.logJsEvalRequest();
Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback<String>() {
Futures.addCallback(scriptEngine.executeToStringAsync(msg), new FutureCallback<String>() {
@Override
public void onSuccess(@Nullable String result) {
ctx.logJsEvalResponse();
@ -94,8 +96,8 @@ public class TbLogNode implements TbNode {
@Override
public void destroy() {
if (jsEngine != null) {
jsEngine.destroy();
if (scriptEngine != null) {
scriptEngine.destroy();
}
}
}

View File

@ -17,16 +17,21 @@ package org.thingsboard.rule.engine.action;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.script.ScriptLanguage;
@Data
public class TbLogNodeConfiguration implements NodeConfiguration {
private ScriptLanguage scriptLang;
private String jsScript;
private String mvelScript;
@Override
public TbLogNodeConfiguration defaultConfiguration() {
TbLogNodeConfiguration configuration = new TbLogNodeConfiguration();
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setJsScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);");
configuration.setMvelScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);");
return configuration;
}
}

View File

@ -31,6 +31,7 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
@ -60,7 +61,7 @@ public class TbMsgGeneratorNode implements TbNode {
private static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg";
private TbMsgGeneratorNodeConfiguration config;
private ScriptEngine jsEngine;
private ScriptEngine scriptEngine;
private long delay;
private long lastScheduledTs;
private int currentMsgCount;
@ -93,7 +94,8 @@ public class TbMsgGeneratorNode implements TbNode {
log.trace("updateGeneratorState, config {}", config);
if (ctx.isLocalEntity(originatorId)) {
if (initialized.compareAndSet(false, true)) {
this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType");
this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType");
scheduleTickMsg(ctx);
}
} else if (initialized.compareAndSet(true, false)) {
@ -146,7 +148,7 @@ public class TbMsgGeneratorNode implements TbNode {
}
if (initialized.get()) {
ctx.logJsEvalRequest();
return Futures.transformAsync(jsEngine.executeGenerateAsync(prevMsg), generated -> {
return Futures.transformAsync(scriptEngine.executeGenerateAsync(prevMsg), generated -> {
log.trace("generate process response, generated {}, config {}", generated, config);
ctx.logJsEvalResponse();
prevMsg = ctx.newMsg(null, generated.getType(), originatorId, msg.getCustomerId(), generated.getMetaData(), generated.getData());
@ -161,9 +163,9 @@ public class TbMsgGeneratorNode implements TbNode {
public void destroy() {
log.trace("destroy, config {}", config);
prevMsg = null;
if (jsEngine != null) {
jsEngine.destroy();
jsEngine = null;
if (scriptEngine != null) {
scriptEngine.destroy();
scriptEngine = null;
}
}
}

View File

@ -18,27 +18,33 @@ package org.thingsboard.rule.engine.debug;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.script.ScriptLanguage;
@Data
public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration<TbMsgGeneratorNodeConfiguration> {
public static final int UNLIMITED_MSG_COUNT = 0;
public static final String DEFAULT_SCRIPT = "var msg = { temp: 42, humidity: 77 };\n" +
"var metadata = { data: 40 };\n" +
"var msgType = \"POST_TELEMETRY_REQUEST\";\n\n" +
"return { msg: msg, metadata: metadata, msgType: msgType };";
private int msgCount;
private int periodInSeconds;
private String originatorId;
private EntityType originatorType;
private ScriptLanguage scriptLang;
private String jsScript;
private String mvelScript;
@Override
public TbMsgGeneratorNodeConfiguration defaultConfiguration() {
TbMsgGeneratorNodeConfiguration configuration = new TbMsgGeneratorNodeConfiguration();
configuration.setMsgCount(UNLIMITED_MSG_COUNT);
configuration.setPeriodInSeconds(1);
configuration.setJsScript("var msg = { temp: 42, humidity: 77 };\n" +
"var metadata = { data: 40 };\n" +
"var msgType = \"POST_TELEMETRY_REQUEST\";\n\n" +
"return { msg: msg, metadata: metadata, msgType: msgType };");
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setJsScript(DEFAULT_SCRIPT);
configuration.setMvelScript(DEFAULT_SCRIPT);
return configuration;
}
}

View File

@ -20,7 +20,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.thingsboard.common.util.ListeningExecutor;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.TbContext;
@ -29,6 +28,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.Set;
@ -50,18 +50,19 @@ import java.util.Set;
public class TbJsSwitchNode implements TbNode {
private TbJsSwitchNodeConfiguration config;
private ScriptEngine jsEngine;
private ScriptEngine scriptEngine;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
this.jsEngine = ctx.createJsScriptEngine(config.getJsScript());
this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript());
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
ctx.logJsEvalRequest();
Futures.addCallback(jsEngine.executeSwitchAsync(msg), new FutureCallback<Set<String>>() {
Futures.addCallback(scriptEngine.executeSwitchAsync(msg), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Set<String> result) {
ctx.logJsEvalResponse();
@ -82,8 +83,8 @@ public class TbJsSwitchNode implements TbNode {
@Override
public void destroy() {
if (jsEngine != null) {
jsEngine.destroy();
if (scriptEngine != null) {
scriptEngine.destroy();
}
}
}

View File

@ -18,24 +18,31 @@ package org.thingsboard.rule.engine.filter;
import com.google.common.collect.Sets;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import java.util.Set;
@Data
public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitchNodeConfiguration> {
private static final String DEFAULT_SCRIPT = "function nextRelation(metadata, msg) {\n" +
" return ['one','nine'];\n" +
"}\n" +
"if(msgType === 'POST_TELEMETRY_REQUEST') {\n" +
" return ['two'];\n" +
"}\n" +
"return nextRelation(metadata, msg);";
private ScriptLanguage scriptLang;
private String jsScript;
private String mvelScript;
@Override
public TbJsSwitchNodeConfiguration defaultConfiguration() {
TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
" return ['one','nine'];\n" +
"}\n" +
"if(msgType === 'POST_TELEMETRY_REQUEST') {\n" +
" return ['two'];\n" +
"}\n" +
"return nextRelation(metadata, msg);");
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setJsScript(DEFAULT_SCRIPT);
configuration.setMvelScript(DEFAULT_SCRIPT);
return configuration;
}
}

View File

@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -56,8 +57,6 @@ public class TbJsSwitchNodeTest {
@Mock
private TbContext ctx;
@Mock
private ListeningExecutor executor;
@Mock
private ScriptEngine scriptEngine;
private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
@ -80,22 +79,14 @@ public class TbJsSwitchNodeTest {
private void initWithScript() throws TbNodeException {
TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration();
config.setScriptLang(ScriptLanguage.JS);
config.setJsScript("scr");
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine);
when(ctx.createScriptEngine(ScriptLanguage.JS, "scr")).thenReturn(scriptEngine);
node = new TbJsSwitchNode();
node.init(ctx, nodeConfiguration);
}
private void verifyError(TbMsg msg, String message, Class expectedClass) {
ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
verify(ctx).tellFailure(same(msg), captor.capture());
Throwable value = captor.getValue();
assertEquals(expectedClass, value.getClass());
assertEquals(message, value.getMessage());
}
}