Browse Source

Merge branch 'rc' into feature/add_show_total_legend_setting_to_latest-chart-widgets

pull/13350/head
Paolo Cristiani 1 year ago
committed by GitHub
parent
commit
a61261b99b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 100
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  2. 70
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java
  3. 112
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java
  4. 22
      application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
  5. 45
      application/src/test/java/org/thingsboard/server/service/script/RemoteJsInvokeServiceTest.java
  6. 22
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
  7. 5
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java
  8. 21
      common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java
  9. 7
      common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java
  10. 16
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java
  11. 49
      common/script/script-api/src/test/java/org/thingsboard/script/api/TbScriptExceptionTest.java
  12. 4
      common/util/src/main/java/org/thingsboard/common/util/RecoveryAware.java
  13. 4
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
  14. 7
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java

100
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -17,18 +17,16 @@ package org.thingsboard.server.service.script;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.RuleNodeScriptFactory; import org.thingsboard.script.api.RuleNodeScriptFactory;
import org.thingsboard.script.api.TbScriptException;
import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -36,8 +34,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@Slf4j
public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService, JsonNode> { public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService, JsonNode> {
public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService scriptInvokeService, String script, String... argNames) { public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService scriptInvokeService, String script, String... argNames) {
@ -45,87 +41,81 @@ public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService
} }
@Override @Override
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) { protected Object[] prepareArgs(TbMsg msg) {
return executeScriptAsync(msg); String[] args = new String[3];
if (msg.getData() != null) {
args[0] = msg.getData();
} else {
args[0] = "";
}
args[1] = JacksonUtil.toString(msg.getMetaData().getData());
args[2] = msg.getType();
return args;
} }
@Override @Override
protected ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) { protected List<TbMsg> executeUpdateTransform(TbMsg msg, JsonNode json) {
if (json.isObject()) { if (json.isObject()) {
return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); return Collections.singletonList(unbindMsg(json, msg));
} else if (json.isArray()) { } else if (json.isArray()) {
List<TbMsg> res = new ArrayList<>(json.size()); List<TbMsg> res = new ArrayList<>(json.size());
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
return Futures.immediateFuture(res); return res;
} }
log.warn("Wrong result type: {}", json.getNodeType()); throw wrongResultType(json);
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
} }
@Override @Override
protected ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) { protected TbMsg executeGenerateTransform(TbMsg prevMsg, JsonNode result) {
if (!result.isObject()) { if (!result.isObject()) {
log.warn("Wrong result type: {}", result.getNodeType()); throw wrongResultType(result);
Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
}
return Futures.immediateFuture(unbindMsg(result, prevMsg));
}
@Override
protected JsonNode convertResult(Object result) {
return JacksonUtil.toJsonNode(result != null ? result.toString() : null);
}
@Override
protected ListenableFuture<String> executeToStringTransform(JsonNode result) {
if (result.isTextual()) {
return Futures.immediateFuture(result.asText());
} }
log.warn("Wrong result type: {}", result.getNodeType()); return unbindMsg(result, prevMsg);
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
} }
@Override @Override
protected ListenableFuture<Boolean> executeFilterTransform(JsonNode json) { protected boolean executeFilterTransform(JsonNode json) {
if (json.isBoolean()) { if (json.isBoolean()) {
return Futures.immediateFuture(json.asBoolean()); return json.asBoolean();
} }
log.warn("Wrong result type: {}", json.getNodeType()); throw wrongResultType(json);
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
} }
@Override @Override
protected ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) { protected Set<String> executeSwitchTransform(JsonNode result) {
if (result.isTextual()) { if (result.isTextual()) {
return Futures.immediateFuture(Collections.singleton(result.asText())); return Collections.singleton(result.asText());
} }
if (result.isArray()) { if (result.isArray()) {
Set<String> nextStates = new HashSet<>(); Set<String> nextStates = new HashSet<>();
for (JsonNode val : result) { for (JsonNode val : result) {
if (!val.isTextual()) { if (!val.isTextual()) {
log.warn("Wrong result type: {}", val.getNodeType()); throw wrongResultType(val);
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + val.getNodeType()));
} else { } else {
nextStates.add(val.asText()); nextStates.add(val.asText());
} }
} }
return Futures.immediateFuture(nextStates); return nextStates;
} }
log.warn("Wrong result type: {}", result.getNodeType()); throw wrongResultType(result);
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
} }
@Override @Override
protected Object[] prepareArgs(TbMsg msg) { public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
String[] args = new String[3]; return executeScriptAsync(msg);
if (msg.getData() != null) { }
args[0] = msg.getData();
} else { @Override
args[0] = ""; protected String executeToStringTransform(JsonNode result) {
if (result.isTextual()) {
return result.asText();
} }
args[1] = JacksonUtil.toString(msg.getMetaData().getData()); throw wrongResultType(result);
args[2] = msg.getType(); }
return args;
@Override
protected JsonNode convertResult(Object result) {
return JacksonUtil.toJsonNode(result != null ? result.toString() : null);
} }
private static TbMsg unbindMsg(JsonNode msgData, TbMsg msg) { private static TbMsg unbindMsg(JsonNode msgData, TbMsg msg) {
@ -138,19 +128,23 @@ public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService
} }
if (msgData.has(RuleNodeScriptFactory.METADATA)) { if (msgData.has(RuleNodeScriptFactory.METADATA)) {
JsonNode msgMetadata = msgData.get(RuleNodeScriptFactory.METADATA); JsonNode msgMetadata = msgData.get(RuleNodeScriptFactory.METADATA);
metadata = JacksonUtil.convertValue(msgMetadata, new TypeReference<>() { metadata = JacksonUtil.convertValue(msgMetadata, new TypeReference<>() {});
});
} }
if (msgData.has(RuleNodeScriptFactory.MSG_TYPE)) { if (msgData.has(RuleNodeScriptFactory.MSG_TYPE)) {
messageType = msgData.get(RuleNodeScriptFactory.MSG_TYPE).asText(); messageType = msgData.get(RuleNodeScriptFactory.MSG_TYPE).asText();
} }
String newData = data != null ? data : msg.getData(); String newData = data != null ? data : msg.getData();
TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy(); TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy();
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); String newMessageType = StringUtils.isNotEmpty(messageType) ? messageType : msg.getType();
return msg.transform() return msg.transform()
.type(newMessageType) .type(newMessageType)
.metaData(newMetadata) .metaData(newMetadata)
.data(newData) .data(newData)
.build(); .build();
} }
private TbScriptException wrongResultType(JsonNode result) {
return new TbScriptException(scriptId, TbScriptException.ErrorCode.RUNTIME, null, new ClassCastException("Wrong result type: " + result.getNodeType()));
}
} }

70
application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java

@ -17,41 +17,44 @@ package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.script.api.ScriptInvokeService; import org.thingsboard.script.api.ScriptInvokeService;
import org.thingsboard.script.api.ScriptType; import org.thingsboard.script.api.ScriptType;
import org.thingsboard.script.api.TbScriptException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
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;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
@Slf4j @Slf4j
public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> implements ScriptEngine { public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> implements ScriptEngine {
private final T scriptInvokeService; private final T scriptInvokeService;
private final UUID scriptId; protected final UUID scriptId;
private final TenantId tenantId; private final TenantId tenantId;
public RuleNodeScriptEngine(TenantId tenantId, T scriptInvokeService, String script, String... argNames) { public RuleNodeScriptEngine(TenantId tenantId, T scriptInvokeService, String script, String... argNames) {
this.tenantId = tenantId; this.tenantId = tenantId;
this.scriptInvokeService = scriptInvokeService; this.scriptInvokeService = scriptInvokeService;
try { try {
this.scriptId = this.scriptInvokeService.eval(tenantId, ScriptType.RULE_NODE_SCRIPT, script, argNames).get(); scriptId = this.scriptInvokeService.eval(tenantId, ScriptType.RULE_NODE_SCRIPT, script, argNames).get();
} catch (Exception e) { } catch (Exception e) {
Throwable t = e; Throwable t = e;
if (e instanceof ExecutionException) { if (e instanceof ExecutionException) {
t = e.getCause(); t = e.getCause();
} }
throw new IllegalArgumentException("Can't compile script: " + t.getMessage(), t); if (t instanceof TbScriptException scriptException) {
throw scriptException;
}
throw new RuntimeException("Unexpected error when creating script engine: " + t.getMessage(), t);
} }
} }
@ -60,74 +63,53 @@ public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> imp
@Override @Override
public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) { public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
ListenableFuture<R> result = executeScriptAsync(msg); ListenableFuture<R> result = executeScriptAsync(msg);
return Futures.transformAsync(result, return Futures.transform(result, json -> executeUpdateTransform(msg, json), directExecutor());
json -> executeUpdateTransform(msg, json),
MoreExecutors.directExecutor());
} }
protected abstract ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, R result); protected abstract List<TbMsg> executeUpdateTransform(TbMsg msg, R result);
@Override @Override
public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) { public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) {
return Futures.transformAsync(executeScriptAsync(prevMsg), return Futures.transform(executeScriptAsync(prevMsg), result -> executeGenerateTransform(prevMsg, result), directExecutor());
result -> executeGenerateTransform(prevMsg, result),
MoreExecutors.directExecutor());
} }
protected abstract ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, R result); protected abstract TbMsg executeGenerateTransform(TbMsg prevMsg, R result);
@Override @Override
public ListenableFuture<String> executeToStringAsync(TbMsg msg) { public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
return Futures.transformAsync(executeScriptAsync(msg), this::executeToStringTransform, MoreExecutors.directExecutor()); return Futures.transform(executeScriptAsync(msg), this::executeFilterTransform, directExecutor());
} }
protected abstract boolean executeFilterTransform(R result);
@Override @Override
public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) { public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) {
return Futures.transformAsync(executeScriptAsync(msg), return Futures.transform(executeScriptAsync(msg), this::executeSwitchTransform, directExecutor()); // usually runs on a callbackExecutor
this::executeFilterTransform,
MoreExecutors.directExecutor());
} }
protected abstract ListenableFuture<String> executeToStringTransform(R result); protected abstract Set<String> executeSwitchTransform(R result);
protected abstract ListenableFuture<Boolean> executeFilterTransform(R result);
protected abstract ListenableFuture<Set<String>> executeSwitchTransform(R result);
@Override @Override
public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) { public ListenableFuture<String> executeToStringAsync(TbMsg msg) {
return Futures.transformAsync(executeScriptAsync(msg), return Futures.transform(executeScriptAsync(msg), this::executeToStringTransform, directExecutor());
this::executeSwitchTransform,
MoreExecutors.directExecutor()); //usually runs in a callbackExecutor
} }
protected abstract String executeToStringTransform(R result);
ListenableFuture<R> executeScriptAsync(TbMsg msg) { ListenableFuture<R> executeScriptAsync(TbMsg msg) {
log.trace("execute script async, msg {}", msg); log.trace("execute script async, msg {}", msg);
Object[] inArgs = prepareArgs(msg); Object[] inArgs = prepareArgs(msg);
return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]); return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]);
} }
ListenableFuture<R> executeScriptAsync(CustomerId customerId, Object... args) { private ListenableFuture<R> executeScriptAsync(CustomerId customerId, Object... args) {
return Futures.transformAsync(scriptInvokeService.invokeScript(tenantId, customerId, this.scriptId, args), return Futures.transform(scriptInvokeService.invokeScript(tenantId, customerId, scriptId, args), this::convertResult, directExecutor());
o -> {
try {
return Futures.immediateFuture(convertResult(o));
} catch (Exception e) {
if (e.getCause() instanceof ScriptException) {
return Futures.immediateFailedFuture(e.getCause());
} else if (e.getCause() instanceof RuntimeException) {
return Futures.immediateFailedFuture(new ScriptException(e.getCause().getMessage()));
} else {
return Futures.immediateFailedFuture(new ScriptException(e));
}
}
}, MoreExecutors.directExecutor());
} }
public void destroy() { public void destroy() {
scriptInvokeService.release(this.scriptId); scriptInvokeService.release(scriptId);
} }
protected abstract R convertResult(Object result); protected abstract R convertResult(Object result);
} }

112
application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java

@ -19,17 +19,15 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.RuleNodeScriptFactory; import org.thingsboard.script.api.RuleNodeScriptFactory;
import org.thingsboard.script.api.TbScriptException;
import org.thingsboard.script.api.tbel.TbelInvokeService; import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -40,8 +38,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
@Slf4j
public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeService, Object> { public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeService, Object> {
public RuleNodeTbelScriptEngine(TenantId tenantId, TbelInvokeService scriptInvokeService, String script, String... argNames) { public RuleNodeTbelScriptEngine(TenantId tenantId, TbelInvokeService scriptInvokeService, String script, String... argNames) {
@ -49,70 +47,74 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
} }
@Override @Override
protected ListenableFuture<Boolean> executeFilterTransform(Object result) { protected Object[] prepareArgs(TbMsg msg) {
if (result instanceof Boolean) { Object[] args = new Object[3];
return Futures.immediateFuture((Boolean) result); if (msg.getData() != null) {
args[0] = JacksonUtil.fromString(msg.getData(), Object.class);
} else {
args[0] = new HashMap<>();
} }
return wrongResultType(result); args[1] = new HashMap<>(msg.getMetaData().getData());
args[2] = msg.getType();
return args;
} }
@Override @Override
protected ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, Object result) { protected List<TbMsg> executeUpdateTransform(TbMsg msg, Object result) {
if (result instanceof Map) { if (result instanceof Map msgData) {
return Futures.immediateFuture(Collections.singletonList(unbindMsg((Map) result, msg))); return Collections.singletonList(unbindMsg(msgData, msg));
} else if (result instanceof Collection) { } else if (result instanceof Collection resultCollection) {
List<TbMsg> res = new ArrayList<>(); List<TbMsg> res = new ArrayList<>(resultCollection.size());
for (Object resObject : (Collection) result) { for (Object resObject : resultCollection) {
if (resObject instanceof Map) { if (resObject instanceof Map msgData) {
res.add(unbindMsg((Map) resObject, msg)); res.add(unbindMsg(msgData, msg));
} else { } else {
return wrongResultType(resObject); throw wrongResultType(resObject);
} }
} }
return Futures.immediateFuture(res); return res;
} }
return wrongResultType(result); throw wrongResultType(result);
} }
@Override @Override
protected ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, Object result) { protected TbMsg executeGenerateTransform(TbMsg prevMsg, Object result) {
if (result instanceof Map) { if (result instanceof Map msgData) {
return Futures.immediateFuture(unbindMsg((Map) result, prevMsg)); return unbindMsg(msgData, prevMsg);
} }
return wrongResultType(result); throw wrongResultType(result);
} }
@Override @Override
protected ListenableFuture<String> executeToStringTransform(Object result) { protected boolean executeFilterTransform(Object result) {
if (result instanceof String) { if (result instanceof Boolean b) {
return Futures.immediateFuture((String) result); return b;
} else {
return Futures.immediateFuture(JacksonUtil.toString(result));
} }
throw wrongResultType(result);
} }
@Override @Override
protected ListenableFuture<Set<String>> executeSwitchTransform(Object result) { protected Set<String> executeSwitchTransform(Object result) {
if (result instanceof String) { if (result instanceof String str) {
return Futures.immediateFuture(Collections.singleton((String) result)); return Collections.singleton(str);
} else if (result instanceof Collection) { }
Set<String> res = new HashSet<>(); if (result instanceof Collection<?> resultCollection) {
for (Object resObject : (Collection) result) { Set<String> res = new HashSet<>(resultCollection.size());
if (resObject instanceof String) { for (Object resObject : resultCollection) {
res.add((String) resObject); if (resObject instanceof String str) {
res.add(str);
} else { } else {
return wrongResultType(resObject); throw wrongResultType(resObject);
} }
} }
return Futures.immediateFuture(res); return res;
} }
return wrongResultType(result); throw wrongResultType(result);
} }
@Override @Override
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) { public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
return Futures.transform(executeScriptAsync(msg), JacksonUtil::valueToTree, MoreExecutors.directExecutor()); return Futures.transform(executeScriptAsync(msg), JacksonUtil::valueToTree, directExecutor());
} }
@Override @Override
@ -121,16 +123,8 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
} }
@Override @Override
protected Object[] prepareArgs(TbMsg msg) { protected String executeToStringTransform(Object result) {
Object[] args = new Object[3]; return result instanceof String str ? str : JacksonUtil.toString(result);
if (msg.getData() != null) {
args[0] = JacksonUtil.fromString(msg.getData(), Object.class);
} else {
args[0] = new HashMap<>();
}
args[1] = new HashMap<>(msg.getMetaData().getData());
args[2] = msg.getType();
return args;
} }
private static TbMsg unbindMsg(Map msgData, TbMsg msg) { private static TbMsg unbindMsg(Map msgData, TbMsg msg) {
@ -142,12 +136,12 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
} }
if (msgData.containsKey(RuleNodeScriptFactory.METADATA)) { if (msgData.containsKey(RuleNodeScriptFactory.METADATA)) {
Object msgMetadataObj = msgData.get(RuleNodeScriptFactory.METADATA); Object msgMetadataObj = msgData.get(RuleNodeScriptFactory.METADATA);
if (msgMetadataObj instanceof Map) { if (msgMetadataObj instanceof Map<?, ?> msgMetadataObjAsMap) {
metadata = ((Map<?, ?>) msgMetadataObj).entrySet().stream().filter(e -> e.getValue() != null) metadata = msgMetadataObjAsMap.entrySet().stream()
.filter(e -> e.getValue() != null)
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString())); .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString()));
} else { } else {
metadata = JacksonUtil.convertValue(msgMetadataObj, new TypeReference<>() { metadata = JacksonUtil.convertValue(msgMetadataObj, new TypeReference<>() {});
});
} }
} }
if (msgData.containsKey(RuleNodeScriptFactory.MSG_TYPE)) { if (msgData.containsKey(RuleNodeScriptFactory.MSG_TYPE)) {
@ -155,7 +149,7 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
} }
String newData = data != null ? data : msg.getData(); String newData = data != null ? data : msg.getData();
TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy(); TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy();
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); String newMessageType = StringUtils.isNotEmpty(messageType) ? messageType : msg.getType();
return msg.transform() return msg.transform()
.type(newMessageType) .type(newMessageType)
.metaData(newMetadata) .metaData(newMetadata)
@ -163,13 +157,13 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
.build(); .build();
} }
private static <T> ListenableFuture<T> wrongResultType(Object result) { private TbScriptException wrongResultType(Object result) {
String className = toClassName(result); String className = toClassName(result);
log.warn("Wrong result type: {}", className); return new TbScriptException(scriptId, TbScriptException.ErrorCode.RUNTIME, null, new ClassCastException("Wrong result type: " + className));
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + className));
} }
private static String toClassName(Object result) { private static String toClassName(Object result) {
return result != null ? result.getClass().getSimpleName() : "null"; return result != null ? result.getClass().getSimpleName() : "null";
} }
} }

22
application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java

@ -25,11 +25,13 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.TbStopWatch; import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.script.api.ScriptType; import org.thingsboard.script.api.ScriptType;
import org.thingsboard.script.api.TbScriptException;
import org.thingsboard.script.api.js.NashornJsInvokeService; import org.thingsboard.script.api.js.NashornJsInvokeService;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import javax.script.ScriptException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -39,6 +41,7 @@ import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST;
@ -59,6 +62,25 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
@Value("${js.local.max_errors}") @Value("${js.local.max_errors}")
private int maxJsErrors; private int maxJsErrors;
@Test
void givenUncompilableScript_whenEvaluating_thenThrowsErrorWithCompilationErrorCode() {
// GIVEN
var uncompilableScript = "return msg.temperature?.value;";
// WHEN-THEN
assertThatThrownBy(() -> evalScript(uncompilableScript))
.isInstanceOf(ExecutionException.class)
.cause()
.isInstanceOf(TbScriptException.class)
.asInstanceOf(type(TbScriptException.class))
.satisfies(ex -> {
assertThat(ex.getScriptId()).isNotNull();
assertThat(ex.getErrorCode()).isEqualTo(TbScriptException.ErrorCode.COMPILATION);
assertThat(ex.getBody()).contains(uncompilableScript);
assertThat(ex.getCause()).isInstanceOf(ScriptException.class);
});
}
@Test @Test
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException { void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
int iterations = 1000; int iterations = 1000;

45
application/src/test/java/org/thingsboard/server/service/script/RemoteJsInvokeServiceTest.java

@ -23,9 +23,9 @@ import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.script.api.ScriptType; import org.thingsboard.script.api.ScriptType;
import org.thingsboard.script.api.TbScriptException;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.stats.DefaultStatsFactory;
import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.stats.TbApiUsageReportClient;
@ -42,8 +42,11 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
@ -60,7 +63,6 @@ class RemoteJsInvokeServiceTest {
private RemoteJsInvokeService remoteJsInvokeService; private RemoteJsInvokeService remoteJsInvokeService;
private TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> jsRequestTemplate; private TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> jsRequestTemplate;
@BeforeEach @BeforeEach
public void beforeEach() { public void beforeEach() {
TbApiUsageStateClient apiUsageStateClient = mock(TbApiUsageStateClient.class); TbApiUsageStateClient apiUsageStateClient = mock(TbApiUsageStateClient.class);
@ -74,7 +76,7 @@ class RemoteJsInvokeServiceTest {
remoteJsInvokeService.requestTemplate = jsRequestTemplate; remoteJsInvokeService.requestTemplate = jsRequestTemplate;
StatsFactory statsFactory = mock(StatsFactory.class); StatsFactory statsFactory = mock(StatsFactory.class);
when(statsFactory.createStatsCounter(any(), any())).thenReturn(mock(StatsCounter.class)); when(statsFactory.createStatsCounter(any(), any())).thenReturn(mock(StatsCounter.class));
ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory",statsFactory); ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory", statsFactory);
remoteJsInvokeService.init(); remoteJsInvokeService.init();
} }
@ -84,7 +86,36 @@ class RemoteJsInvokeServiceTest {
} }
@Test @Test
public void whenInvokingFunction_thenDoNotSendScriptBody() throws Exception { void givenUncompilableScript_whenEvaluating_thenThrowsErrorWithCompilationErrorCode() {
// GIVEN
doAnswer(methodCall -> Futures.immediateFuture(new TbProtoJsQueueMsg<>(UUID.randomUUID(), RemoteJsResponse.newBuilder()
.setCompileResponse(JsInvokeProtos.JsCompileResponse.newBuilder()
.setSuccess(false)
.setErrorCode(JsInvokeProtos.JsInvokeErrorCode.COMPILATION_ERROR)
.setErrorDetails("SyntaxError: Unexpected token 'const'")
.setScriptHash(methodCall.<TbProtoQueueMsg<RemoteJsRequest>>getArgument(0).getValue().getCompileRequest().getScriptHash())
.build())
.build())))
.when(jsRequestTemplate).send(argThat(jsQueueMsg -> jsQueueMsg.getValue().hasCompileRequest()));
var uncompilableScript = "let const = 'this is not allowed';";
// WHEN-THEN
assertThatThrownBy(() -> remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, uncompilableScript).get())
.isInstanceOf(ExecutionException.class)
.cause()
.isInstanceOf(TbScriptException.class)
.asInstanceOf(type(TbScriptException.class))
.satisfies(ex -> {
assertThat(ex.getScriptId()).isNotNull();
assertThat(ex.getErrorCode()).isEqualTo(TbScriptException.ErrorCode.COMPILATION);
assertThat(ex.getBody()).contains(uncompilableScript);
assertThat(ex.getCause()).isInstanceOf(RuntimeException.class).hasMessage("SyntaxError: Unexpected token 'const'");
});
}
@Test
void whenInvokingFunction_thenDoNotSendScriptBody() throws Exception {
mockJsEvalResponse(); mockJsEvalResponse();
String scriptBody = "return { a: 'b'};"; String scriptBody = "return { a: 'b'};";
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get(); UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
@ -110,7 +141,7 @@ class RemoteJsInvokeServiceTest {
} }
@Test @Test
public void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception { void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception {
mockJsEvalResponse(); mockJsEvalResponse();
String scriptBody = "return { a: 'b'};"; String scriptBody = "return { a: 'b'};";
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get(); UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
@ -156,7 +187,7 @@ class RemoteJsInvokeServiceTest {
} }
@Test @Test
public void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception { void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception {
mockJsEvalResponse(); mockJsEvalResponse();
TenantId tenantId1 = TenantId.fromUUID(UUID.randomUUID()); TenantId tenantId1 = TenantId.fromUUID(UUID.randomUUID());
@ -187,7 +218,7 @@ class RemoteJsInvokeServiceTest {
} }
@Test @Test
public void whenReleasingScript_thenCheckForHashUsages() throws Exception { void whenReleasingScript_thenCheckForHashUsages() throws Exception {
mockJsEvalResponse(); mockJsEvalResponse();
String scriptBody = "return { a: 'b'};"; String scriptBody = "return { a: 'b'};";
UUID scriptId1 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get(); UUID scriptId1 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();

22
application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java

@ -20,10 +20,12 @@ import com.github.benmanes.caffeine.cache.Cache;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mvel2.CompileException;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.TbScriptException;
import org.thingsboard.script.api.tbel.TbelScript; import org.thingsboard.script.api.tbel.TbelScript;
import java.io.Serializable; import java.io.Serializable;
@ -37,6 +39,7 @@ import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.InstanceOfAssertFactories.type;
@TestPropertySource(properties = { @TestPropertySource(properties = {
"tbel.max_script_body_size=100", "tbel.max_script_body_size=100",
@ -50,6 +53,25 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
@Value("${tbel.max_errors}") @Value("${tbel.max_errors}")
private int maxJsErrors; private int maxJsErrors;
@Test
void givenUncompilableScript_whenEvaluating_thenThrowsErrorWithCompilationErrorCode() {
// GIVEN
var uncompilableScript = "return msg.property !== undefined;";
// WHEN-THEN
assertThatThrownBy(() -> evalScript(uncompilableScript))
.isInstanceOf(ExecutionException.class)
.cause()
.isInstanceOf(TbScriptException.class)
.asInstanceOf(type(TbScriptException.class))
.satisfies(ex -> {
assertThat(ex.getScriptId()).isNotNull();
assertThat(ex.getErrorCode()).isEqualTo(TbScriptException.ErrorCode.COMPILATION);
assertThat(ex.getBody()).isEqualTo(uncompilableScript);
assertThat(ex.getCause()).isInstanceOf(CompileException.class);
});
}
@Test @Test
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException { void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
int iterations = 100000; int iterations = 100000;

5
common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java

@ -19,8 +19,8 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.common.util.RecoveryAware;
import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorError;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbActorStopReason;
@ -35,6 +35,7 @@ import java.util.function.Supplier;
@Getter @Getter
@RequiredArgsConstructor @RequiredArgsConstructor
public final class TbActorMailbox implements TbActorCtx { public final class TbActorMailbox implements TbActorCtx {
private static final boolean HIGH_PRIORITY = true; private static final boolean HIGH_PRIORITY = true;
private static final boolean NORMAL_PRIORITY = false; private static final boolean NORMAL_PRIORITY = false;
@ -100,7 +101,7 @@ public final class TbActorMailbox implements TbActorCtx {
if (t instanceof TbActorException && t.getCause() != null) { if (t instanceof TbActorException && t.getCause() != null) {
t = t.getCause(); t = t.getCause();
} }
return t instanceof TbActorError && ((TbActorError) t).isUnrecoverable(); return t instanceof RecoveryAware recoveryAware && recoveryAware.isUnrecoverable();
} }
private void enqueue(TbActorMsg msg, boolean highPriority) { private void enqueue(TbActorMsg msg, boolean highPriority) {

21
common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java

@ -16,13 +16,24 @@
package org.thingsboard.script.api; package org.thingsboard.script.api;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.common.util.RecoveryAware;
import java.io.Serial;
import java.util.UUID; import java.util.UUID;
public class TbScriptException extends RuntimeException { public class TbScriptException extends RuntimeException implements RecoveryAware {
@Serial
private static final long serialVersionUID = -1958193538782818284L; private static final long serialVersionUID = -1958193538782818284L;
public static enum ErrorCode {COMPILATION, TIMEOUT, RUNTIME, OTHER} public enum ErrorCode {
COMPILATION,
TIMEOUT,
RUNTIME,
OTHER
}
@Getter @Getter
private final UUID scriptId; private final UUID scriptId;
@ -37,4 +48,10 @@ public class TbScriptException extends RuntimeException {
this.errorCode = errorCode; this.errorCode = errorCode;
this.body = body; this.body = body;
} }
@Override
public boolean isUnrecoverable() {
return errorCode == ErrorCode.COMPILATION;
}
} }

7
common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes; import delight.nashornsandbox.NashornSandboxes;
import delight.nashornsandbox.exceptions.ScriptCPUAbuseException;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.Getter; import lombok.Getter;
@ -153,8 +154,12 @@ public class NashornJsInvokeService extends AbstractJsInvokeService {
} }
scriptInfoMap.put(scriptId, scriptInfo); scriptInfoMap.put(scriptId, scriptInfo);
return scriptId; return scriptId;
} catch (Exception e) { } catch (ScriptException e) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, jsScript, e); throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, jsScript, e);
} catch (ScriptCPUAbuseException e) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.TIMEOUT, jsScript, e);
} catch (Exception e) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, jsScript, e);
} }
}); });
} }

16
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java

@ -27,6 +27,7 @@ import jakarta.annotation.PreDestroy;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mvel2.CompileException;
import org.mvel2.ExecutionContext; import org.mvel2.ExecutionContext;
import org.mvel2.MVEL; import org.mvel2.MVEL;
import org.mvel2.ParserContext; import org.mvel2.ParserContext;
@ -52,11 +53,11 @@ import java.io.Serializable;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -66,9 +67,9 @@ import java.util.concurrent.locks.ReentrantLock;
@Service @Service
public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService { public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService {
protected final Map<UUID, String> scriptIdToHash = new ConcurrentHashMap<>(); private final ConcurrentMap<UUID, String> scriptIdToHash = new ConcurrentHashMap<>();
protected final Map<String, TbelScript> scriptMap = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TbelScript> scriptMap = new ConcurrentHashMap<>();
protected Cache<String, Serializable> compiledScriptsCache; private Cache<String, Serializable> compiledScriptsCache;
private SandboxedParserConfiguration parserConfig; private SandboxedParserConfiguration parserConfig;
private final Optional<TbApiUsageStateClient> apiUsageStateClient; private final Optional<TbApiUsageStateClient> apiUsageStateClient;
@ -204,8 +205,10 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem
lock.unlock(); lock.unlock();
} }
return scriptId; return scriptId;
} catch (Exception e) { } catch (CompileException e) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e); throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e);
} catch (Exception e) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, scriptBody, e);
} }
}); });
} }
@ -246,7 +249,7 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem
} }
} }
private Serializable compileScript(String scriptBody) { private static Serializable compileScript(String scriptBody) throws CompileException {
return MVEL.compileExpression(scriptBody, new ParserContext()); return MVEL.compileExpression(scriptBody, new ParserContext());
} }
@ -269,4 +272,5 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem
protected StatsType getStatsType() { protected StatsType getStatsType() {
return StatsType.TBEL_INVOKE; return StatsType.TBEL_INVOKE;
} }
} }

49
common/script/script-api/src/test/java/org/thingsboard/script/api/TbScriptExceptionTest.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import static org.assertj.core.api.Assertions.assertThat;
class TbScriptExceptionTest {
@Test
void givenCompilationError_whenCheckingIsUnrecoverable_thenReturnsTrue() {
// GIVEN
var exception = new TbScriptException(null, TbScriptException.ErrorCode.COMPILATION, null, null);
// WHEN-THEN
assertThat(exception.isUnrecoverable()).isTrue();
}
@ParameterizedTest
@EnumSource(
value = TbScriptException.ErrorCode.class,
mode = EnumSource.Mode.EXCLUDE,
names = "COMPILATION"
)
void givenRecoverableErrorCodes_whenCheckingIsUnrecoverable_thenReturnsFalse(TbScriptException.ErrorCode errorCode) {
// GIVEN
var exception = new TbScriptException(null, errorCode, null, null);
// WHEN-THEN
assertThat(exception.isUnrecoverable()).isFalse();
}
}

4
common/message/src/main/java/org/thingsboard/server/common/msg/TbActorError.java → common/util/src/main/java/org/thingsboard/common/util/RecoveryAware.java

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.msg; package org.thingsboard.common.util;
public interface TbActorError { public interface RecoveryAware {
boolean isUnrecoverable(); boolean isUnrecoverable();

4
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java

@ -192,14 +192,14 @@ final class MqttClientImpl implements MqttClient {
} }
private void scheduleConnectIfRequired(String host, int port, boolean reconnect) { private void scheduleConnectIfRequired(String host, int port, boolean reconnect) {
log.trace("[{}] Scheduling connect to server, isReconnect - {}", channel != null ? channel.id() : "UNKNOWN", reconnect); log.trace("[{}][{}][{}] Scheduling connect to server, isReconnect - {}", host, port, channel != null ? channel.id() : "UNKNOWN", reconnect);
if (clientConfig.isReconnect() && !disconnected) { if (clientConfig.isReconnect() && !disconnected) {
if (reconnect) { if (reconnect) {
this.reconnect = true; this.reconnect = true;
} }
final long nextReconnectDelay = reconnectStrategy.getNextReconnectDelay(); final long nextReconnectDelay = reconnectStrategy.getNextReconnectDelay();
log.info("[{}] Scheduling reconnect in [{}] sec", channel != null ? channel.id() : "UNKNOWN", nextReconnectDelay); log.debug("[{}][{}][{}] Scheduling reconnect in [{}] sec", host, port, channel != null ? channel.id() : "UNKNOWN", nextReconnectDelay);
eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), nextReconnectDelay, TimeUnit.SECONDS); eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), nextReconnectDelay, TimeUnit.SECONDS);
} }
} }

7
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbNodeException.java

@ -16,12 +16,9 @@
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.server.common.msg.TbActorError; import org.thingsboard.common.util.RecoveryAware;
/** public class TbNodeException extends Exception implements RecoveryAware {
* Created by ashvayka on 19.01.18.
*/
public class TbNodeException extends Exception implements TbActorError {
@Getter @Getter
private final boolean unrecoverable; private final boolean unrecoverable;

Loading…
Cancel
Save