commit
d494a8883a
@ -17,18 +17,16 @@ package org.thingsboard.server.service.script;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.script.api.RuleNodeScriptFactory;
|
||||
import org.thingsboard.script.api.TbScriptException;
|
||||
import org.thingsboard.script.api.js.JsInvokeService;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@ -36,85 +34,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService, JsonNode> {
|
||||
|
||||
public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService scriptInvokeService, String script, String... argNames) {
|
||||
super(tenantId, scriptInvokeService, script, argNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
|
||||
return executeScriptAsync(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, JsonNode json) {
|
||||
if (json.isObject()) {
|
||||
return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg)));
|
||||
} else if (json.isArray()) {
|
||||
List<TbMsg> res = new ArrayList<>(json.size());
|
||||
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
|
||||
return Futures.immediateFuture(res);
|
||||
}
|
||||
log.warn("Wrong result type: {}", json.getNodeType());
|
||||
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, JsonNode result) {
|
||||
if (!result.isObject()) {
|
||||
log.warn("Wrong result type: {}", result.getNodeType());
|
||||
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 Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected 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()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<Set<String>> executeSwitchTransform(JsonNode result) {
|
||||
if (result.isTextual()) {
|
||||
return Futures.immediateFuture(Collections.singleton(result.asText()));
|
||||
}
|
||||
if (result.isArray()) {
|
||||
Set<String> nextStates = new HashSet<>();
|
||||
for (JsonNode val : result) {
|
||||
if (!val.isTextual()) {
|
||||
log.warn("Wrong result type: {}", val.getNodeType());
|
||||
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + val.getNodeType()));
|
||||
} else {
|
||||
nextStates.add(val.asText());
|
||||
}
|
||||
}
|
||||
return Futures.immediateFuture(nextStates);
|
||||
}
|
||||
log.warn("Wrong result type: {}", result.getNodeType());
|
||||
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object[] prepareArgs(TbMsg msg) {
|
||||
String[] args = new String[3];
|
||||
@ -128,6 +53,71 @@ public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TbMsg> executeUpdateTransform(TbMsg msg, JsonNode json) {
|
||||
if (json.isObject()) {
|
||||
return Collections.singletonList(unbindMsg(json, msg));
|
||||
} else if (json.isArray()) {
|
||||
List<TbMsg> res = new ArrayList<>(json.size());
|
||||
json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg)));
|
||||
return res;
|
||||
}
|
||||
throw wrongResultType(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TbMsg executeGenerateTransform(TbMsg prevMsg, JsonNode result) {
|
||||
if (!result.isObject()) {
|
||||
throw wrongResultType(result);
|
||||
}
|
||||
return unbindMsg(result, prevMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean executeFilterTransform(JsonNode json) {
|
||||
if (json.isBoolean()) {
|
||||
return json.asBoolean();
|
||||
}
|
||||
throw wrongResultType(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> executeSwitchTransform(JsonNode result) {
|
||||
if (result.isTextual()) {
|
||||
return Collections.singleton(result.asText());
|
||||
}
|
||||
if (result.isArray()) {
|
||||
Set<String> nextStates = new HashSet<>();
|
||||
for (JsonNode val : result) {
|
||||
if (!val.isTextual()) {
|
||||
throw wrongResultType(val);
|
||||
} else {
|
||||
nextStates.add(val.asText());
|
||||
}
|
||||
}
|
||||
return nextStates;
|
||||
}
|
||||
throw wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
|
||||
return executeScriptAsync(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String executeToStringTransform(JsonNode result) {
|
||||
if (result.isTextual()) {
|
||||
return result.asText();
|
||||
}
|
||||
throw wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonNode convertResult(Object result) {
|
||||
return JacksonUtil.toJsonNode(result != null ? result.toString() : null);
|
||||
}
|
||||
|
||||
private static TbMsg unbindMsg(JsonNode msgData, TbMsg msg) {
|
||||
String data = null;
|
||||
Map<String, String> metadata = null;
|
||||
@ -138,19 +128,23 @@ public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine<JsInvokeService
|
||||
}
|
||||
if (msgData.has(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)) {
|
||||
messageType = msgData.get(RuleNodeScriptFactory.MSG_TYPE).asText();
|
||||
}
|
||||
String newData = data != null ? data : msg.getData();
|
||||
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()
|
||||
.type(newMessageType)
|
||||
.metaData(newMetadata)
|
||||
.data(newData)
|
||||
.build();
|
||||
}
|
||||
|
||||
private TbScriptException wrongResultType(JsonNode result) {
|
||||
return new TbScriptException(scriptId, TbScriptException.ErrorCode.RUNTIME, null, new ClassCastException("Wrong result type: " + result.getNodeType()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -17,41 +17,44 @@ package org.thingsboard.server.service.script;
|
||||
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.rule.engine.api.ScriptEngine;
|
||||
import org.thingsboard.script.api.ScriptInvokeService;
|
||||
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.TenantId;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||
|
||||
@Slf4j
|
||||
public abstract class RuleNodeScriptEngine<T extends ScriptInvokeService, R> implements ScriptEngine {
|
||||
|
||||
private final T scriptInvokeService;
|
||||
|
||||
private final UUID scriptId;
|
||||
protected final UUID scriptId;
|
||||
private final TenantId tenantId;
|
||||
|
||||
public RuleNodeScriptEngine(TenantId tenantId, T scriptInvokeService, String script, String... argNames) {
|
||||
this.tenantId = tenantId;
|
||||
this.scriptInvokeService = scriptInvokeService;
|
||||
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) {
|
||||
Throwable t = e;
|
||||
if (e instanceof ExecutionException) {
|
||||
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
|
||||
public ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg) {
|
||||
ListenableFuture<R> result = executeScriptAsync(msg);
|
||||
return Futures.transformAsync(result,
|
||||
json -> executeUpdateTransform(msg, json),
|
||||
MoreExecutors.directExecutor());
|
||||
return Futures.transform(result, json -> executeUpdateTransform(msg, json), directExecutor());
|
||||
}
|
||||
|
||||
protected abstract ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, R result);
|
||||
protected abstract List<TbMsg> executeUpdateTransform(TbMsg msg, R result);
|
||||
|
||||
@Override
|
||||
public ListenableFuture<TbMsg> executeGenerateAsync(TbMsg prevMsg) {
|
||||
return Futures.transformAsync(executeScriptAsync(prevMsg),
|
||||
result -> executeGenerateTransform(prevMsg, result),
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
protected abstract ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, R result);
|
||||
|
||||
@Override
|
||||
public ListenableFuture<String> executeToStringAsync(TbMsg msg) {
|
||||
return Futures.transformAsync(executeScriptAsync(msg), this::executeToStringTransform, MoreExecutors.directExecutor());
|
||||
return Futures.transform(executeScriptAsync(prevMsg), result -> executeGenerateTransform(prevMsg, result), directExecutor());
|
||||
}
|
||||
|
||||
protected abstract TbMsg executeGenerateTransform(TbMsg prevMsg, R result);
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Boolean> executeFilterAsync(TbMsg msg) {
|
||||
return Futures.transformAsync(executeScriptAsync(msg),
|
||||
this::executeFilterTransform,
|
||||
MoreExecutors.directExecutor());
|
||||
return Futures.transform(executeScriptAsync(msg), this::executeFilterTransform, directExecutor());
|
||||
}
|
||||
|
||||
protected abstract ListenableFuture<String> executeToStringTransform(R result);
|
||||
|
||||
protected abstract ListenableFuture<Boolean> executeFilterTransform(R result);
|
||||
|
||||
protected abstract ListenableFuture<Set<String>> executeSwitchTransform(R result);
|
||||
protected abstract boolean executeFilterTransform(R result);
|
||||
|
||||
@Override
|
||||
public ListenableFuture<Set<String>> executeSwitchAsync(TbMsg msg) {
|
||||
return Futures.transformAsync(executeScriptAsync(msg),
|
||||
this::executeSwitchTransform,
|
||||
MoreExecutors.directExecutor()); //usually runs in a callbackExecutor
|
||||
return Futures.transform(executeScriptAsync(msg), this::executeSwitchTransform, directExecutor()); // usually runs on a callbackExecutor
|
||||
}
|
||||
|
||||
protected abstract Set<String> executeSwitchTransform(R result);
|
||||
|
||||
@Override
|
||||
public ListenableFuture<String> executeToStringAsync(TbMsg msg) {
|
||||
return Futures.transform(executeScriptAsync(msg), this::executeToStringTransform, directExecutor());
|
||||
}
|
||||
|
||||
protected abstract String executeToStringTransform(R result);
|
||||
|
||||
ListenableFuture<R> executeScriptAsync(TbMsg msg) {
|
||||
log.trace("execute script async, msg {}", msg);
|
||||
Object[] inArgs = prepareArgs(msg);
|
||||
return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]);
|
||||
}
|
||||
|
||||
ListenableFuture<R> executeScriptAsync(CustomerId customerId, Object... args) {
|
||||
return Futures.transformAsync(scriptInvokeService.invokeScript(tenantId, customerId, this.scriptId, args),
|
||||
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());
|
||||
private ListenableFuture<R> executeScriptAsync(CustomerId customerId, Object... args) {
|
||||
return Futures.transform(scriptInvokeService.invokeScript(tenantId, customerId, scriptId, args), this::convertResult, directExecutor());
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
scriptInvokeService.release(this.scriptId);
|
||||
scriptInvokeService.release(scriptId);
|
||||
}
|
||||
|
||||
protected abstract R convertResult(Object result);
|
||||
|
||||
}
|
||||
|
||||
@ -19,17 +19,15 @@ import com.fasterxml.jackson.core.type.TypeReference;
|
||||
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.MoreExecutors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.script.api.RuleNodeScriptFactory;
|
||||
import org.thingsboard.script.api.TbScriptException;
|
||||
import org.thingsboard.script.api.tbel.TbelInvokeService;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@ -40,86 +38,14 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||
|
||||
@Slf4j
|
||||
public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeService, Object> {
|
||||
|
||||
public RuleNodeTbelScriptEngine(TenantId tenantId, TbelInvokeService scriptInvokeService, String script, String... argNames) {
|
||||
super(tenantId, scriptInvokeService, script, argNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<Boolean> executeFilterTransform(Object result) {
|
||||
if (result instanceof Boolean) {
|
||||
return Futures.immediateFuture((Boolean) result);
|
||||
}
|
||||
return wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<List<TbMsg>> executeUpdateTransform(TbMsg msg, Object result) {
|
||||
if (result instanceof Map) {
|
||||
return Futures.immediateFuture(Collections.singletonList(unbindMsg((Map) result, msg)));
|
||||
} else if (result instanceof Collection) {
|
||||
List<TbMsg> res = new ArrayList<>();
|
||||
for (Object resObject : (Collection) result) {
|
||||
if (resObject instanceof Map) {
|
||||
res.add(unbindMsg((Map) resObject, msg));
|
||||
} else {
|
||||
return wrongResultType(resObject);
|
||||
}
|
||||
}
|
||||
return Futures.immediateFuture(res);
|
||||
}
|
||||
return wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<TbMsg> executeGenerateTransform(TbMsg prevMsg, Object result) {
|
||||
if (result instanceof Map) {
|
||||
return Futures.immediateFuture(unbindMsg((Map) result, prevMsg));
|
||||
}
|
||||
return wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<String> executeToStringTransform(Object result) {
|
||||
if (result instanceof String) {
|
||||
return Futures.immediateFuture((String) result);
|
||||
} else {
|
||||
return Futures.immediateFuture(JacksonUtil.toString(result));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListenableFuture<Set<String>> executeSwitchTransform(Object result) {
|
||||
if (result instanceof String) {
|
||||
return Futures.immediateFuture(Collections.singleton((String) result));
|
||||
} else if (result instanceof Collection) {
|
||||
Set<String> res = new HashSet<>();
|
||||
for (Object resObject : (Collection) result) {
|
||||
if (resObject instanceof String) {
|
||||
res.add((String) resObject);
|
||||
} else {
|
||||
return wrongResultType(resObject);
|
||||
}
|
||||
}
|
||||
return Futures.immediateFuture(res);
|
||||
}
|
||||
return wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
|
||||
return Futures.transform(executeScriptAsync(msg), JacksonUtil::valueToTree, MoreExecutors.directExecutor());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object convertResult(Object result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object[] prepareArgs(TbMsg msg) {
|
||||
Object[] args = new Object[3];
|
||||
@ -133,6 +59,74 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
|
||||
return args;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<TbMsg> executeUpdateTransform(TbMsg msg, Object result) {
|
||||
if (result instanceof Map msgData) {
|
||||
return Collections.singletonList(unbindMsg(msgData, msg));
|
||||
} else if (result instanceof Collection resultCollection) {
|
||||
List<TbMsg> res = new ArrayList<>(resultCollection.size());
|
||||
for (Object resObject : resultCollection) {
|
||||
if (resObject instanceof Map msgData) {
|
||||
res.add(unbindMsg(msgData, msg));
|
||||
} else {
|
||||
throw wrongResultType(resObject);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
throw wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TbMsg executeGenerateTransform(TbMsg prevMsg, Object result) {
|
||||
if (result instanceof Map msgData) {
|
||||
return unbindMsg(msgData, prevMsg);
|
||||
}
|
||||
throw wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean executeFilterTransform(Object result) {
|
||||
if (result instanceof Boolean b) {
|
||||
return b;
|
||||
}
|
||||
throw wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> executeSwitchTransform(Object result) {
|
||||
if (result instanceof String str) {
|
||||
return Collections.singleton(str);
|
||||
}
|
||||
if (result instanceof Collection<?> resultCollection) {
|
||||
Set<String> res = new HashSet<>(resultCollection.size());
|
||||
for (Object resObject : resultCollection) {
|
||||
if (resObject instanceof String str) {
|
||||
res.add(str);
|
||||
} else {
|
||||
throw wrongResultType(resObject);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
throw wrongResultType(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) {
|
||||
return Futures.transform(executeScriptAsync(msg), JacksonUtil::valueToTree, directExecutor());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object convertResult(Object result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String executeToStringTransform(Object result) {
|
||||
return result instanceof String str ? str : JacksonUtil.toString(result);
|
||||
}
|
||||
|
||||
private static TbMsg unbindMsg(Map msgData, TbMsg msg) {
|
||||
String data = null;
|
||||
Map<String, String> metadata = null;
|
||||
@ -142,12 +136,12 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
|
||||
}
|
||||
if (msgData.containsKey(RuleNodeScriptFactory.METADATA)) {
|
||||
Object msgMetadataObj = msgData.get(RuleNodeScriptFactory.METADATA);
|
||||
if (msgMetadataObj instanceof Map) {
|
||||
metadata = ((Map<?, ?>) msgMetadataObj).entrySet().stream().filter(e -> e.getValue() != null)
|
||||
if (msgMetadataObj instanceof Map<?, ?> msgMetadataObjAsMap) {
|
||||
metadata = msgMetadataObjAsMap.entrySet().stream()
|
||||
.filter(e -> e.getValue() != null)
|
||||
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString()));
|
||||
} else {
|
||||
metadata = JacksonUtil.convertValue(msgMetadataObj, new TypeReference<>() {
|
||||
});
|
||||
metadata = JacksonUtil.convertValue(msgMetadataObj, new TypeReference<>() {});
|
||||
}
|
||||
}
|
||||
if (msgData.containsKey(RuleNodeScriptFactory.MSG_TYPE)) {
|
||||
@ -155,7 +149,7 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
|
||||
}
|
||||
String newData = data != null ? data : msg.getData();
|
||||
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()
|
||||
.type(newMessageType)
|
||||
.metaData(newMetadata)
|
||||
@ -163,13 +157,13 @@ public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeSer
|
||||
.build();
|
||||
}
|
||||
|
||||
private static <T> ListenableFuture<T> wrongResultType(Object result) {
|
||||
private TbScriptException wrongResultType(Object result) {
|
||||
String className = toClassName(result);
|
||||
log.warn("Wrong result type: {}", className);
|
||||
return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + className));
|
||||
return new TbScriptException(scriptId, TbScriptException.ErrorCode.RUNTIME, null, new ClassCastException("Wrong result type: " + className));
|
||||
}
|
||||
|
||||
private static String toClassName(Object result) {
|
||||
return result != null ? result.getClass().getSimpleName() : "null";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,11 +25,13 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.thingsboard.common.util.TbStopWatch;
|
||||
import org.thingsboard.script.api.ScriptType;
|
||||
import org.thingsboard.script.api.TbScriptException;
|
||||
import org.thingsboard.script.api.js.NashornJsInvokeService;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.controller.AbstractControllerTest;
|
||||
import org.thingsboard.server.dao.service.DaoSqlTest;
|
||||
|
||||
import javax.script.ScriptException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
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.assertThatThrownBy;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
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}")
|
||||
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
|
||||
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
||||
int iterations = 1000;
|
||||
|
||||
@ -23,9 +23,9 @@ import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
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.id.TenantId;
|
||||
import org.thingsboard.server.common.stats.DefaultStatsFactory;
|
||||
import org.thingsboard.server.common.stats.StatsCounter;
|
||||
import org.thingsboard.server.common.stats.StatsFactory;
|
||||
import org.thingsboard.server.common.stats.TbApiUsageReportClient;
|
||||
@ -42,8 +42,11 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
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.argThat;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
@ -60,7 +63,6 @@ class RemoteJsInvokeServiceTest {
|
||||
private RemoteJsInvokeService remoteJsInvokeService;
|
||||
private TbQueueRequestTemplate<TbProtoJsQueueMsg<RemoteJsRequest>, TbProtoQueueMsg<RemoteJsResponse>> jsRequestTemplate;
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
TbApiUsageStateClient apiUsageStateClient = mock(TbApiUsageStateClient.class);
|
||||
@ -74,7 +76,7 @@ class RemoteJsInvokeServiceTest {
|
||||
remoteJsInvokeService.requestTemplate = jsRequestTemplate;
|
||||
StatsFactory statsFactory = mock(StatsFactory.class);
|
||||
when(statsFactory.createStatsCounter(any(), any())).thenReturn(mock(StatsCounter.class));
|
||||
ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory",statsFactory);
|
||||
ReflectionTestUtils.setField(remoteJsInvokeService, "statsFactory", statsFactory);
|
||||
remoteJsInvokeService.init();
|
||||
}
|
||||
|
||||
@ -84,7 +86,36 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@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();
|
||||
String scriptBody = "return { a: 'b'};";
|
||||
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||
@ -110,7 +141,7 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception {
|
||||
void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception {
|
||||
mockJsEvalResponse();
|
||||
String scriptBody = "return { a: 'b'};";
|
||||
UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||
@ -156,7 +187,7 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception {
|
||||
void whenDoingEval_thenSaveScriptByHashOfTenantIdAndScriptBody() throws Exception {
|
||||
mockJsEvalResponse();
|
||||
|
||||
TenantId tenantId1 = TenantId.fromUUID(UUID.randomUUID());
|
||||
@ -187,7 +218,7 @@ class RemoteJsInvokeServiceTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenReleasingScript_thenCheckForHashUsages() throws Exception {
|
||||
void whenReleasingScript_thenCheckForHashUsages() throws Exception {
|
||||
mockJsEvalResponse();
|
||||
String scriptBody = "return { a: 'b'};";
|
||||
UUID scriptId1 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get();
|
||||
|
||||
@ -20,10 +20,12 @@ import com.github.benmanes.caffeine.cache.Cache;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mvel2.CompileException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.script.api.TbScriptException;
|
||||
import org.thingsboard.script.api.tbel.TbelScript;
|
||||
|
||||
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.assertThatThrownBy;
|
||||
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||
|
||||
@TestPropertySource(properties = {
|
||||
"tbel.max_script_body_size=100",
|
||||
@ -50,6 +53,25 @@ class TbelInvokeServiceTest extends AbstractTbelInvokeTest {
|
||||
@Value("${tbel.max_errors}")
|
||||
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
|
||||
void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException {
|
||||
int iterations = 100000;
|
||||
|
||||
@ -19,8 +19,8 @@ import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.TbActorError;
|
||||
import org.thingsboard.server.common.msg.TbActorMsg;
|
||||
import org.thingsboard.server.common.msg.TbActorStopReason;
|
||||
|
||||
@ -35,6 +35,7 @@ import java.util.function.Supplier;
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public final class TbActorMailbox implements TbActorCtx {
|
||||
|
||||
private static final boolean HIGH_PRIORITY = true;
|
||||
private static final boolean NORMAL_PRIORITY = false;
|
||||
|
||||
@ -100,7 +101,7 @@ public final class TbActorMailbox implements TbActorCtx {
|
||||
if (t instanceof TbActorException && t.getCause() != null) {
|
||||
t = t.getCause();
|
||||
}
|
||||
return t instanceof TbActorError && ((TbActorError) t).isUnrecoverable();
|
||||
return t instanceof RecoveryAware recoveryAware && recoveryAware.isUnrecoverable();
|
||||
}
|
||||
|
||||
private void enqueue(TbActorMsg msg, boolean highPriority) {
|
||||
|
||||
@ -16,13 +16,24 @@
|
||||
package org.thingsboard.script.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.thingsboard.common.util.RecoveryAware;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.util.UUID;
|
||||
|
||||
public class TbScriptException extends RuntimeException {
|
||||
public class TbScriptException extends RuntimeException implements RecoveryAware {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -1958193538782818284L;
|
||||
|
||||
public static enum ErrorCode {COMPILATION, TIMEOUT, RUNTIME, OTHER}
|
||||
public enum ErrorCode {
|
||||
|
||||
COMPILATION,
|
||||
TIMEOUT,
|
||||
RUNTIME,
|
||||
OTHER
|
||||
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final UUID scriptId;
|
||||
@ -37,4 +48,10 @@ public class TbScriptException extends RuntimeException {
|
||||
this.errorCode = errorCode;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnrecoverable() {
|
||||
return errorCode == ErrorCode.COMPILATION;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import delight.nashornsandbox.NashornSandbox;
|
||||
import delight.nashornsandbox.NashornSandboxes;
|
||||
import delight.nashornsandbox.exceptions.ScriptCPUAbuseException;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import lombok.Getter;
|
||||
@ -153,8 +154,12 @@ public class NashornJsInvokeService extends AbstractJsInvokeService {
|
||||
}
|
||||
scriptInfoMap.put(scriptId, scriptInfo);
|
||||
return scriptId;
|
||||
} catch (Exception e) {
|
||||
} catch (ScriptException 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import jakarta.annotation.PreDestroy;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.mvel2.CompileException;
|
||||
import org.mvel2.ExecutionContext;
|
||||
import org.mvel2.MVEL;
|
||||
import org.mvel2.ParserContext;
|
||||
@ -52,11 +53,11 @@ import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@ -66,9 +67,9 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
@Service
|
||||
public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService {
|
||||
|
||||
protected final Map<UUID, String> scriptIdToHash = new ConcurrentHashMap<>();
|
||||
protected final Map<String, TbelScript> scriptMap = new ConcurrentHashMap<>();
|
||||
protected Cache<String, Serializable> compiledScriptsCache;
|
||||
private final ConcurrentMap<UUID, String> scriptIdToHash = new ConcurrentHashMap<>();
|
||||
private final ConcurrentMap<String, TbelScript> scriptMap = new ConcurrentHashMap<>();
|
||||
private Cache<String, Serializable> compiledScriptsCache;
|
||||
|
||||
private SandboxedParserConfiguration parserConfig;
|
||||
private final Optional<TbApiUsageStateClient> apiUsageStateClient;
|
||||
@ -204,8 +205,10 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem
|
||||
lock.unlock();
|
||||
}
|
||||
return scriptId;
|
||||
} catch (Exception e) {
|
||||
} catch (CompileException 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());
|
||||
}
|
||||
|
||||
@ -269,4 +272,5 @@ public class DefaultTbelInvokeService extends AbstractScriptInvokeService implem
|
||||
protected StatsType getStatsType() {
|
||||
return StatsType.TBEL_INVOKE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.common.msg;
|
||||
package org.thingsboard.common.util;
|
||||
|
||||
public interface TbActorError {
|
||||
public interface RecoveryAware {
|
||||
|
||||
boolean isUnrecoverable();
|
||||
|
||||
@ -207,14 +207,14 @@ final class MqttClientImpl implements MqttClient {
|
||||
}
|
||||
|
||||
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 (reconnect) {
|
||||
this.reconnect = true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,12 +16,9 @@
|
||||
package org.thingsboard.rule.engine.api;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.thingsboard.server.common.msg.TbActorError;
|
||||
import org.thingsboard.common.util.RecoveryAware;
|
||||
|
||||
/**
|
||||
* Created by ashvayka on 19.01.18.
|
||||
*/
|
||||
public class TbNodeException extends Exception implements TbActorError {
|
||||
public class TbNodeException extends Exception implements RecoveryAware {
|
||||
|
||||
@Getter
|
||||
private final boolean unrecoverable;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user