diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index f8199caedd..85b8e21095 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.script.api.js.JsInvokeService;
+import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.EventInfo;
@@ -60,6 +61,7 @@ import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType;
+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;
@@ -67,6 +69,7 @@ import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rule.TbRuleChainService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
+import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@@ -117,10 +120,10 @@ public class RuleChainController extends BaseController {
private static final String RULE_CHAIN_DESCRIPTION = "The rule chain object is lightweight and contains general information about the rule chain. " +
"List of rule nodes and their connection is stored in a separate 'metadata' object.";
private static final String RULE_CHAIN_METADATA_DESCRIPTION = "The metadata object contains information about the rule nodes and their connections.";
- private static final String TEST_JS_FUNCTION = "Execute the JavaScript function and return the result. The format of request: \n\n"
+ private static final String TEST_SCRIPT_FUNCTION = "Execute the Script function and return the result. The format of request: \n\n"
+ MARKDOWN_CODE_BLOCK_START
+ "{\n" +
- " \"script\": \"Your JS Function as String\",\n" +
+ " \"script\": \"Your Function as String\",\n" +
" \"scriptType\": \"One of: update, generate, filter, switch, json, string\",\n" +
" \"argNames\": [\"msg\", \"metadata\", \"type\"],\n" +
" \"msg\": \"{\\\"temperature\\\": 42}\", \n" +
@@ -140,7 +143,10 @@ public class RuleChainController extends BaseController {
private EventService eventService;
@Autowired
- private JsInvokeService scriptInvokeService;
+ private JsInvokeService jsInvokeService;
+
+ @Autowired(required = false)
+ private MvelInvokeService mvelInvokeService;
@Autowired(required = false)
private ActorSystemContext actorContext;
@@ -148,6 +154,9 @@ public class RuleChainController extends BaseController {
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}")
private boolean debugPerTenantEnabled;
+ @Value("${mvel.enabled:true}")
+ private boolean mvelEnabled;
+
@ApiOperation(value = "Get Rule Chain (getRuleChainById)",
notes = "Fetch the Rule Chain object based on the provided Rule Chain Id. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@@ -369,13 +378,23 @@ public class RuleChainController extends BaseController {
}
}
+ @ApiOperation(value = "Is MVEL script executor enabled",
+ notes = "Returns 'True' if the MVEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH)
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/ruleChain/mvelEnabled", method = RequestMethod.GET)
+ @ResponseBody
+ public Boolean isMvelEnabled() {
+ return mvelEnabled;
+ }
- @ApiOperation(value = "Test JavaScript function",
- notes = TEST_JS_FUNCTION + TENANT_AUTHORITY_PARAGRAPH)
+ @ApiOperation(value = "Test Script function",
+ notes = TEST_SCRIPT_FUNCTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
@ResponseBody
public JsonNode testScript(
+ @ApiParam(value = "Script language: JS or MVEL")
+ @RequestParam(required = false) ScriptLanguage scriptLang,
@ApiParam(value = "Test JS request. See API call description above.")
@RequestBody JsonNode inputParams) throws ThingsboardException {
try {
@@ -393,7 +412,17 @@ public class RuleChainController extends BaseController {
String errorText = "";
ScriptEngine engine = null;
try {
- engine = new RuleNodeJsScriptEngine(getTenantId(), scriptInvokeService, script, argNames);
+ if (scriptLang == null) {
+ scriptLang = ScriptLanguage.JS;
+ }
+ if (ScriptLanguage.JS.equals(scriptLang)) {
+ engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames);
+ } else {
+ if (mvelInvokeService == null) {
+ throw new IllegalArgumentException("MVEL script engine is disabled!");
+ }
+ engine = new RuleNodeMvelScriptEngine(getTenantId(), mvelInvokeService, script, argNames);
+ }
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
switch (scriptType) {
case "update":
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
index 373c685cc1..c7d964e017 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
@@ -40,9 +40,9 @@ import static org.thingsboard.common.util.DonAsynchron.withCallback;
"If True - send Message via True chain, otherwise False chain is used." +
"Message payload can be accessed via msg property. For example msg.temperature < 10;
" +
"Message metadata can be accessed via metadata property. For example metadata.customerName === 'John';
" +
- "Message type can be accessed via msgType property."
-// uiResources = {"static/rulenode/rulenode-core-config.js"},
-// configDirective = "tbFilterNodeScriptConfig")
+ "Message type can be accessed via msgType property.",
+ uiResources = {"static/rulenode/rulenode-core-config.js"},
+ configDirective = "tbFilterNodeScriptConfig"
)
public class TbJsFilterNode implements TbNode {
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
index 3ef6f787d4..fd33bb4554 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
@@ -40,9 +40,9 @@ import java.util.List;
"msgType - is a Message type.
" +
"Should return the following structure:
" +
"{ msg: new payload,
   metadata: new metadata,
   msgType: new msgType }
" +
- "All fields in resulting object are optional and will be taken from original message if not specified."
-// uiResources = {"static/rulenode/rulenode-core-config.js"},
-// configDirective = "tbTransformationNodeScriptConfig"
+ "All fields in resulting object are optional and will be taken from original message if not specified.",
+ uiResources = {"static/rulenode/rulenode-core-config.js"},
+ configDirective = "tbTransformationNodeScriptConfig"
)
public class TbTransformMsgNode extends TbAbstractTransformNode {
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
index d9706f6b96..b4b4563b4d 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
@@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
+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;
@@ -180,7 +181,7 @@ public class TbAlarmNodeTest {
verifyError(msg, "message", NotImplementedException.class);
- verify(ctx).createJsScriptEngine("DETAILS");
+ verify(ctx).createScriptEngine(ScriptLanguage.JS, "DETAILS");
verify(ctx).getAlarmService();
verify(ctx, times(3)).getDbCallbackExecutor();
verify(ctx).logJsEvalRequest();
@@ -395,12 +396,13 @@ public class TbAlarmNodeTest {
config.setPropagate(true);
config.setSeverity("$[alarmSeverity]");
config.setAlarmType("SomeType");
+ config.setScriptLang(ScriptLanguage.JS);
config.setAlarmDetailsBuildJs("DETAILS");
config.setDynamicSeverity(true);
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
- when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+ when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
when(ctx.getTenantId()).thenReturn(tenantId);
when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -456,6 +458,7 @@ public class TbAlarmNodeTest {
public void testCreateAlarmWithDynamicSeverityFromMetadata() throws Exception {
TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration();
config.setPropagate(true);
+ config.setScriptLang(ScriptLanguage.JS);
config.setSeverity("${alarmSeverity}");
config.setAlarmType("SomeType");
config.setAlarmDetailsBuildJs("DETAILS");
@@ -463,7 +466,7 @@ public class TbAlarmNodeTest {
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
- when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+ when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
when(ctx.getTenantId()).thenReturn(tenantId);
when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -521,12 +524,13 @@ public class TbAlarmNodeTest {
config.setPropagateToTenant(true);
config.setSeverity(CRITICAL.name());
config.setAlarmType("SomeType" + i);
+ config.setScriptLang(ScriptLanguage.JS);
config.setAlarmDetailsBuildJs("DETAILS");
config.setDynamicSeverity(true);
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
- when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+ when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
when(ctx.getTenantId()).thenReturn(tenantId);
when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -584,11 +588,12 @@ public class TbAlarmNodeTest {
config.setPropagate(true);
config.setSeverity(CRITICAL.name());
config.setAlarmType("SomeType");
+ config.setScriptLang(ScriptLanguage.JS);
config.setAlarmDetailsBuildJs("DETAILS");
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
- when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+ when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
when(ctx.getTenantId()).thenReturn(tenantId);
when(ctx.getAlarmService()).thenReturn(alarmService);
@@ -605,11 +610,12 @@ public class TbAlarmNodeTest {
try {
TbClearAlarmNodeConfiguration config = new TbClearAlarmNodeConfiguration();
config.setAlarmType("SomeType");
+ config.setScriptLang(ScriptLanguage.JS);
config.setAlarmDetailsBuildJs("DETAILS");
ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));
- when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
+ when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs);
when(ctx.getTenantId()).thenReturn(tenantId);
when(ctx.getAlarmService()).thenReturn(alarmService);
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
index 63c94ea9a5..f40c6f0072 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java
@@ -33,6 +33,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;
@@ -54,8 +55,6 @@ public class TbJsFilterNodeTest {
@Mock
private TbContext ctx;
@Mock
- private ListeningExecutor executor;
- @Mock
private ScriptEngine scriptEngine;
private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
@@ -73,7 +72,7 @@ public class TbJsFilterNodeTest {
}
@Test
- public void exceptionInJsThrowsException() throws TbNodeException, ScriptException {
+ public void exceptionInJsThrowsException() throws TbNodeException {
initWithScript();
TbMsgMetaData metaData = new TbMsgMetaData();
TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
@@ -85,7 +84,7 @@ public class TbJsFilterNodeTest {
}
@Test
- public void metadataConditionCanBeTrue() throws TbNodeException, ScriptException {
+ public void metadataConditionCanBeTrue() throws TbNodeException {
initWithScript();
TbMsgMetaData metaData = new TbMsgMetaData();
TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId);
@@ -98,11 +97,12 @@ public class TbJsFilterNodeTest {
private void initWithScript() throws TbNodeException {
TbJsFilterNodeConfiguration config = new TbJsFilterNodeConfiguration();
+ 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 TbJsFilterNode();
node.init(ctx, nodeConfiguration);
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
index 411db21c7f..89480b404e 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
@@ -33,6 +33,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;
@@ -55,12 +56,10 @@ public class TbTransformMsgNodeTest {
@Mock
private TbContext ctx;
@Mock
- private ListeningExecutor executor;
- @Mock
private ScriptEngine scriptEngine;
@Test
- public void metadataCanBeUpdated() throws TbNodeException, ScriptException {
+ public void metadataCanBeUpdated() throws TbNodeException {
initWithScript();
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
@@ -80,7 +79,7 @@ public class TbTransformMsgNodeTest {
}
@Test
- public void exceptionHandledCorrectly() throws TbNodeException, ScriptException {
+ public void exceptionHandledCorrectly() throws TbNodeException {
initWithScript();
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
@@ -97,11 +96,12 @@ public class TbTransformMsgNodeTest {
private void initWithScript() throws TbNodeException {
TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration();
+ 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 TbTransformMsgNode();
node.init(ctx, nodeConfiguration);