Merge pull request #8725 from ShvaykaD/feature/math-node-fields-templatization
Math node fields templatization
This commit is contained in:
commit
80baf9f54d
@ -43,19 +43,18 @@ public class TbMathArgumentValue {
|
|||||||
throw new RuntimeException(error);
|
throw new RuntimeException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TbMathArgumentValue fromMessageBody(TbMathArgument arg, Optional<ObjectNode> jsonNodeOpt) {
|
public static TbMathArgumentValue fromMessageBody(TbMathArgument arg, String argKey, Optional<ObjectNode> jsonNodeOpt) {
|
||||||
String key = arg.getKey();
|
|
||||||
Double defaultValue = arg.getDefaultValue();
|
Double defaultValue = arg.getDefaultValue();
|
||||||
if (jsonNodeOpt.isEmpty()) {
|
if (jsonNodeOpt.isEmpty()) {
|
||||||
return defaultOrThrow(defaultValue, "Message body is empty!");
|
return defaultOrThrow(defaultValue, "Message body is empty!");
|
||||||
}
|
}
|
||||||
var json = jsonNodeOpt.get();
|
var json = jsonNodeOpt.get();
|
||||||
if (!json.has(key)) {
|
if (!json.has(argKey)) {
|
||||||
return defaultOrThrow(defaultValue, "Message body has no '" + key + "'!");
|
return defaultOrThrow(defaultValue, "Message body has no '" + argKey + "'!");
|
||||||
}
|
}
|
||||||
JsonNode valueNode = json.get(key);
|
JsonNode valueNode = json.get(argKey);
|
||||||
if (valueNode.isNull()) {
|
if (valueNode.isNull()) {
|
||||||
return defaultOrThrow(defaultValue, "Message body has null '" + key + "'!");
|
return defaultOrThrow(defaultValue, "Message body has null '" + argKey + "'!");
|
||||||
}
|
}
|
||||||
double value;
|
double value;
|
||||||
if (valueNode.isNumber()) {
|
if (valueNode.isNumber()) {
|
||||||
@ -69,7 +68,7 @@ public class TbMathArgumentValue {
|
|||||||
throw new RuntimeException("Can't convert value '" + valueNode.asText() + "' to double!");
|
throw new RuntimeException("Can't convert value '" + valueNode.asText() + "' to double!");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return defaultOrThrow(defaultValue, "Message value is empty for '" + key + "'!");
|
return defaultOrThrow(defaultValue, "Message value is empty for '" + argKey + "'!");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Can't convert value '" + valueNode.toString() + "' to double!");
|
throw new RuntimeException("Can't convert value '" + valueNode.toString() + "' to double!");
|
||||||
@ -77,15 +76,14 @@ public class TbMathArgumentValue {
|
|||||||
return new TbMathArgumentValue(value);
|
return new TbMathArgumentValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TbMathArgumentValue fromMessageMetadata(TbMathArgument arg, TbMsgMetaData metaData) {
|
public static TbMathArgumentValue fromMessageMetadata(TbMathArgument arg, String argKey, TbMsgMetaData metaData) {
|
||||||
String key = arg.getKey();
|
|
||||||
Double defaultValue = arg.getDefaultValue();
|
Double defaultValue = arg.getDefaultValue();
|
||||||
if (metaData == null) {
|
if (metaData == null) {
|
||||||
return defaultOrThrow(defaultValue, "Message metadata is empty!");
|
return defaultOrThrow(defaultValue, "Message metadata is empty!");
|
||||||
}
|
}
|
||||||
var value = metaData.getValue(key);
|
var value = metaData.getValue(argKey);
|
||||||
if (StringUtils.isEmpty(value)) {
|
if (StringUtils.isEmpty(value)) {
|
||||||
return defaultOrThrow(defaultValue, "Message metadata has no '" + key + "'!");
|
return defaultOrThrow(defaultValue, "Message metadata has no '" + argKey + "'!");
|
||||||
}
|
}
|
||||||
return fromString(value);
|
return fromString(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,6 +51,8 @@ import java.util.function.BiFunction;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.thingsboard.rule.engine.math.TbMathArgumentType.CONSTANT;
|
||||||
|
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RuleNode(
|
@RuleNode(
|
||||||
@ -121,7 +123,7 @@ public class TbMathNode implements TbNode {
|
|||||||
var argumentValues = Futures.allAsList(arguments.stream()
|
var argumentValues = Futures.allAsList(arguments.stream()
|
||||||
.map(arg -> resolveArguments(ctx, msg, msgBodyOpt, arg)).collect(Collectors.toList()));
|
.map(arg -> resolveArguments(ctx, msg, msgBodyOpt, arg)).collect(Collectors.toList()));
|
||||||
ListenableFuture<TbMsg> resultMsgFuture = Futures.transformAsync(argumentValues, args ->
|
ListenableFuture<TbMsg> resultMsgFuture = Futures.transformAsync(argumentValues, args ->
|
||||||
updateMsgAndDb(ctx, msg, msgBodyOpt, calculateResult(ctx, msg, args)), ctx.getDbCallbackExecutor());
|
updateMsgAndDb(ctx, msg, msgBodyOpt, calculateResult(args)), ctx.getDbCallbackExecutor());
|
||||||
DonAsynchron.withCallback(resultMsgFuture, resultMsg -> {
|
DonAsynchron.withCallback(resultMsgFuture, resultMsg -> {
|
||||||
try {
|
try {
|
||||||
ctx.tellSuccess(resultMsg);
|
ctx.tellSuccess(resultMsg);
|
||||||
@ -155,17 +157,18 @@ public class TbMathNode implements TbNode {
|
|||||||
|
|
||||||
private ListenableFuture<TbMsg> updateMsgAndDb(TbContext ctx, TbMsg msg, Optional<ObjectNode> msgBodyOpt, double result) {
|
private ListenableFuture<TbMsg> updateMsgAndDb(TbContext ctx, TbMsg msg, Optional<ObjectNode> msgBodyOpt, double result) {
|
||||||
TbMathResult mathResultDef = config.getResult();
|
TbMathResult mathResultDef = config.getResult();
|
||||||
|
String mathResultKey = getKeyFromTemplate(msg, mathResultDef.getType(), mathResultDef.getKey());
|
||||||
switch (mathResultDef.getType()) {
|
switch (mathResultDef.getType()) {
|
||||||
case MESSAGE_BODY:
|
case MESSAGE_BODY:
|
||||||
return Futures.immediateFuture(addToBody(msg, mathResultDef, msgBodyOpt, result));
|
return Futures.immediateFuture(addToBody(msg, mathResultDef, mathResultKey, msgBodyOpt, result));
|
||||||
case MESSAGE_METADATA:
|
case MESSAGE_METADATA:
|
||||||
return Futures.immediateFuture(addToMeta(msg, mathResultDef, result));
|
return Futures.immediateFuture(addToMeta(msg, mathResultDef, mathResultKey, result));
|
||||||
case ATTRIBUTE:
|
case ATTRIBUTE:
|
||||||
ListenableFuture<Void> attrSave = saveAttribute(ctx, msg, result, mathResultDef);
|
ListenableFuture<Void> attrSave = saveAttribute(ctx, msg, result, mathResultDef);
|
||||||
return Futures.transform(attrSave, attr -> addToBodyAndMeta(msg, msgBodyOpt, result, mathResultDef), ctx.getDbCallbackExecutor());
|
return Futures.transform(attrSave, attr -> addToBodyAndMeta(msg, msgBodyOpt, result, mathResultDef, mathResultKey), ctx.getDbCallbackExecutor());
|
||||||
case TIME_SERIES:
|
case TIME_SERIES:
|
||||||
ListenableFuture<Void> tsSave = saveTimeSeries(ctx, msg, result, mathResultDef);
|
ListenableFuture<Void> tsSave = saveTimeSeries(ctx, msg, result, mathResultDef);
|
||||||
return Futures.transform(tsSave, ts -> addToBodyAndMeta(msg, msgBodyOpt, result, mathResultDef), ctx.getDbCallbackExecutor());
|
return Futures.transform(tsSave, ts -> addToBodyAndMeta(msg, msgBodyOpt, result, mathResultDef, mathResultKey), ctx.getDbCallbackExecutor());
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Result type is not supported: " + mathResultDef.getType() + "!");
|
throw new RuntimeException("Result type is not supported: " + mathResultDef.getType() + "!");
|
||||||
}
|
}
|
||||||
@ -180,7 +183,7 @@ public class TbMathNode implements TbNode {
|
|||||||
private ListenableFuture<Void> saveAttribute(TbContext ctx, TbMsg msg, double result, TbMathResult mathResultDef) {
|
private ListenableFuture<Void> saveAttribute(TbContext ctx, TbMsg msg, double result, TbMathResult mathResultDef) {
|
||||||
String attributeScope = getAttributeScope(mathResultDef.getAttributeScope());
|
String attributeScope = getAttributeScope(mathResultDef.getAttributeScope());
|
||||||
if (isIntegerResult(mathResultDef, config.getOperation())) {
|
if (isIntegerResult(mathResultDef, config.getOperation())) {
|
||||||
var value = toIntValue(mathResultDef, result);
|
var value = toIntValue(result);
|
||||||
return ctx.getTelemetryService().saveAttrAndNotify(
|
return ctx.getTelemetryService().saveAttrAndNotify(
|
||||||
ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value);
|
ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value);
|
||||||
} else {
|
} else {
|
||||||
@ -194,7 +197,7 @@ public class TbMathNode implements TbNode {
|
|||||||
return function.isIntegerResult() || mathResultDef.getResultValuePrecision() == 0;
|
return function.isIntegerResult() || mathResultDef.getResultValuePrecision() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long toIntValue(TbMathResult mathResultDef, double value) {
|
private long toIntValue(double value) {
|
||||||
return (long) value;
|
return (long) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,38 +220,38 @@ public class TbMathNode implements TbNode {
|
|||||||
return msgBodyOpt;
|
return msgBodyOpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TbMsg addToBodyAndMeta(TbMsg msg, Optional<ObjectNode> msgBodyOpt, double result, TbMathResult mathResultDef) {
|
private TbMsg addToBodyAndMeta(TbMsg msg, Optional<ObjectNode> msgBodyOpt, double result, TbMathResult mathResultDef, String mathResultKey) {
|
||||||
TbMsg tmpMsg = msg;
|
TbMsg tmpMsg = msg;
|
||||||
if (mathResultDef.isAddToBody()) {
|
if (mathResultDef.isAddToBody()) {
|
||||||
tmpMsg = addToBody(tmpMsg, mathResultDef, msgBodyOpt, result);
|
tmpMsg = addToBody(tmpMsg, mathResultDef, mathResultKey, msgBodyOpt, result);
|
||||||
}
|
}
|
||||||
if (mathResultDef.isAddToMetadata()) {
|
if (mathResultDef.isAddToMetadata()) {
|
||||||
tmpMsg = addToMeta(tmpMsg, mathResultDef, result);
|
tmpMsg = addToMeta(tmpMsg, mathResultDef, mathResultKey, result);
|
||||||
}
|
}
|
||||||
return tmpMsg;
|
return tmpMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TbMsg addToBody(TbMsg msg, TbMathResult mathResultDef, Optional<ObjectNode> msgBodyOpt, double result) {
|
private TbMsg addToBody(TbMsg msg, TbMathResult mathResultDef, String mathResultKey, Optional<ObjectNode> msgBodyOpt, double result) {
|
||||||
ObjectNode body = msgBodyOpt.get();
|
ObjectNode body = msgBodyOpt.get();
|
||||||
if (isIntegerResult(mathResultDef, config.getOperation())) {
|
if (isIntegerResult(mathResultDef, config.getOperation())) {
|
||||||
body.put(mathResultDef.getKey(), toIntValue(mathResultDef, result));
|
body.put(mathResultKey, toIntValue(result));
|
||||||
} else {
|
} else {
|
||||||
body.put(mathResultDef.getKey(), toDoubleValue(mathResultDef, result));
|
body.put(mathResultKey, toDoubleValue(mathResultDef, result));
|
||||||
}
|
}
|
||||||
return TbMsg.transformMsgData(msg, JacksonUtil.toString(body));
|
return TbMsg.transformMsgData(msg, JacksonUtil.toString(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
private TbMsg addToMeta(TbMsg msg, TbMathResult mathResultDef, double result) {
|
private TbMsg addToMeta(TbMsg msg, TbMathResult mathResultDef, String mathResultKey, double result) {
|
||||||
var md = msg.getMetaData();
|
var md = msg.getMetaData();
|
||||||
if (isIntegerResult(mathResultDef, config.getOperation())) {
|
if (isIntegerResult(mathResultDef, config.getOperation())) {
|
||||||
md.putValue(mathResultDef.getKey(), Long.toString(toIntValue(mathResultDef, result)));
|
md.putValue(mathResultKey, Long.toString(toIntValue(result)));
|
||||||
} else {
|
} else {
|
||||||
md.putValue(mathResultDef.getKey(), Double.toString(toDoubleValue(mathResultDef, result)));
|
md.putValue(mathResultKey, Double.toString(toDoubleValue(mathResultDef, result)));
|
||||||
}
|
}
|
||||||
return TbMsg.transformMsg(msg, md);
|
return TbMsg.transformMsg(msg, md);
|
||||||
}
|
}
|
||||||
|
|
||||||
private double calculateResult(TbContext ctx, TbMsg msg, List<TbMathArgumentValue> args) {
|
private double calculateResult(List<TbMathArgumentValue> args) {
|
||||||
switch (config.getOperation()) {
|
switch (config.getOperation()) {
|
||||||
case ADD:
|
case ADD:
|
||||||
return apply(args.get(0), args.get(1), Double::sum);
|
return apply(args.get(0), args.get(1), Double::sum);
|
||||||
@ -345,21 +348,22 @@ public class TbMathNode implements TbNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<TbMathArgumentValue> resolveArguments(TbContext ctx, TbMsg msg, Optional<ObjectNode> msgBodyOpt, TbMathArgument arg) {
|
private ListenableFuture<TbMathArgumentValue> resolveArguments(TbContext ctx, TbMsg msg, Optional<ObjectNode> msgBodyOpt, TbMathArgument arg) {
|
||||||
|
String argKey = getKeyFromTemplate(msg, arg.getType(), arg.getKey());
|
||||||
switch (arg.getType()) {
|
switch (arg.getType()) {
|
||||||
case CONSTANT:
|
case CONSTANT:
|
||||||
return Futures.immediateFuture(TbMathArgumentValue.constant(arg));
|
return Futures.immediateFuture(TbMathArgumentValue.constant(arg));
|
||||||
case MESSAGE_BODY:
|
case MESSAGE_BODY:
|
||||||
return Futures.immediateFuture(TbMathArgumentValue.fromMessageBody(arg, msgBodyOpt));
|
return Futures.immediateFuture(TbMathArgumentValue.fromMessageBody(arg, argKey, msgBodyOpt));
|
||||||
case MESSAGE_METADATA:
|
case MESSAGE_METADATA:
|
||||||
return Futures.immediateFuture(TbMathArgumentValue.fromMessageMetadata(arg, msg.getMetaData()));
|
return Futures.immediateFuture(TbMathArgumentValue.fromMessageMetadata(arg, argKey, msg.getMetaData()));
|
||||||
case ATTRIBUTE:
|
case ATTRIBUTE:
|
||||||
String scope = getAttributeScope(arg.getAttributeScope());
|
String scope = getAttributeScope(arg.getAttributeScope());
|
||||||
return Futures.transform(ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), scope, arg.getKey()),
|
return Futures.transform(ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), scope, argKey),
|
||||||
opt -> getTbMathArgumentValue(arg, opt, "Attribute: " + arg.getKey() + " with scope: " + scope + " not found for entity: " + msg.getOriginator())
|
opt -> getTbMathArgumentValue(arg, opt, "Attribute: " + argKey + " with scope: " + scope + " not found for entity: " + msg.getOriginator())
|
||||||
, MoreExecutors.directExecutor());
|
, MoreExecutors.directExecutor());
|
||||||
case TIME_SERIES:
|
case TIME_SERIES:
|
||||||
return Futures.transform(ctx.getTimeseriesService().findLatest(ctx.getTenantId(), msg.getOriginator(), arg.getKey()),
|
return Futures.transform(ctx.getTimeseriesService().findLatest(ctx.getTenantId(), msg.getOriginator(), argKey),
|
||||||
opt -> getTbMathArgumentValue(arg, opt, "Time-series: " + arg.getKey() + " not found for entity: " + msg.getOriginator())
|
opt -> getTbMathArgumentValue(arg, opt, "Time-series: " + argKey + " not found for entity: " + msg.getOriginator())
|
||||||
, MoreExecutors.directExecutor());
|
, MoreExecutors.directExecutor());
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Unsupported argument type: " + arg.getType() + "!");
|
throw new RuntimeException("Unsupported argument type: " + arg.getType() + "!");
|
||||||
@ -367,6 +371,10 @@ public class TbMathNode implements TbNode {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getKeyFromTemplate(TbMsg msg, TbMathArgumentType type, String keyPattern) {
|
||||||
|
return CONSTANT.equals(type) ? keyPattern : TbNodeUtils.processPattern(keyPattern, msg);
|
||||||
|
}
|
||||||
|
|
||||||
private String getAttributeScope(String attrScope) {
|
private String getAttributeScope(String attrScope) {
|
||||||
return StringUtils.isEmpty(attrScope) ? DataConstants.SERVER_SCOPE : attrScope;
|
return StringUtils.isEmpty(attrScope) ? DataConstants.SERVER_SCOPE : attrScope;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ public class TbMathArgumentValueTest {
|
|||||||
public void test_fromMessageBody_then_defaultValue() {
|
public void test_fromMessageBody_then_defaultValue() {
|
||||||
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
||||||
tbMathArgument.setDefaultValue(5.0);
|
tbMathArgument.setDefaultValue(5.0);
|
||||||
TbMathArgumentValue result = TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.ofNullable(JacksonUtil.newObjectNode()));
|
TbMathArgumentValue result = TbMathArgumentValue.fromMessageBody(tbMathArgument, tbMathArgument.getKey(), Optional.ofNullable(JacksonUtil.newObjectNode()));
|
||||||
Assert.assertEquals(5.0, result.getValue(), 0d);
|
Assert.assertEquals(5.0, result.getValue(), 0d);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ public class TbMathArgumentValueTest {
|
|||||||
public void test_fromMessageBody_then_emptyBody() {
|
public void test_fromMessageBody_then_emptyBody() {
|
||||||
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
||||||
Throwable thrown = assertThrows(RuntimeException.class, () -> {
|
Throwable thrown = assertThrows(RuntimeException.class, () -> {
|
||||||
TbMathArgumentValue result = TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.empty());
|
TbMathArgumentValue result = TbMathArgumentValue.fromMessageBody(tbMathArgument, tbMathArgument.getKey(), Optional.empty());
|
||||||
});
|
});
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ public class TbMathArgumentValueTest {
|
|||||||
@Test
|
@Test
|
||||||
public void test_fromMessageBody_then_noKey() {
|
public void test_fromMessageBody_then_noKey() {
|
||||||
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
||||||
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.ofNullable(JacksonUtil.newObjectNode())));
|
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, tbMathArgument.getKey(), Optional.ofNullable(JacksonUtil.newObjectNode())));
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,12 +58,12 @@ public class TbMathArgumentValueTest {
|
|||||||
msgData.putNull("TestKey");
|
msgData.putNull("TestKey");
|
||||||
|
|
||||||
//null value
|
//null value
|
||||||
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData)));
|
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, tbMathArgument.getKey(), Optional.of(msgData)));
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
|
|
||||||
//empty value
|
//empty value
|
||||||
msgData.put("TestKey", "");
|
msgData.put("TestKey", "");
|
||||||
thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData)));
|
thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, tbMathArgument.getKey(), Optional.of(msgData)));
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,26 +74,26 @@ public class TbMathArgumentValueTest {
|
|||||||
msgData.put("TestKey", "Test");
|
msgData.put("TestKey", "Test");
|
||||||
|
|
||||||
//string value
|
//string value
|
||||||
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData)));
|
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, tbMathArgument.getKey(), Optional.of(msgData)));
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
|
|
||||||
//object value
|
//object value
|
||||||
msgData.set("TestKey", JacksonUtil.newObjectNode());
|
msgData.set("TestKey", JacksonUtil.newObjectNode());
|
||||||
thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData)));
|
thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, tbMathArgument.getKey(), Optional.of(msgData)));
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_fromMessageMetadata_then_noKey() {
|
public void test_fromMessageMetadata_then_noKey() {
|
||||||
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
||||||
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageMetadata(tbMathArgument, new TbMsgMetaData()));
|
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageMetadata(tbMathArgument, tbMathArgument.getKey(), new TbMsgMetaData()));
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_fromMessageMetadata_then_valueEmpty() {
|
public void test_fromMessageMetadata_then_valueEmpty() {
|
||||||
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
|
||||||
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageMetadata(tbMathArgument, null));
|
Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageMetadata(tbMathArgument, tbMathArgument.getKey(), null));
|
||||||
Assert.assertNotNull(thrown.getMessage());
|
Assert.assertNotNull(thrown.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,10 @@
|
|||||||
package org.thingsboard.rule.engine.math;
|
package org.thingsboard.rule.engine.math;
|
||||||
|
|
||||||
import com.datastax.oss.driver.api.core.uuid.Uuids;
|
import com.datastax.oss.driver.api.core.uuid.Uuids;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.awaitility.Awaitility;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -26,6 +29,7 @@ import org.mockito.ArgumentCaptor;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.thingsboard.common.util.AbstractListeningExecutor;
|
import org.thingsboard.common.util.AbstractListeningExecutor;
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
|
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
|
||||||
@ -47,7 +51,11 @@ import org.thingsboard.server.dao.attributes.AttributesService;
|
|||||||
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@ -57,6 +65,7 @@ import static org.mockito.Mockito.lenient;
|
|||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@RunWith(MockitoJUnitRunner.class)
|
@RunWith(MockitoJUnitRunner.class)
|
||||||
public class TbMathNodeTest {
|
public class TbMathNodeTest {
|
||||||
|
|
||||||
@ -130,24 +139,52 @@ public class TbMathNodeTest {
|
|||||||
@Test
|
@Test
|
||||||
public void testExp4j() {
|
public void testExp4j() {
|
||||||
var node = initNodeWithCustomFunction("2a+3b",
|
var node = initNodeWithCustomFunction("2a+3b",
|
||||||
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
|
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "${key1}", 2, false, false, null),
|
||||||
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
|
new TbMathArgument("a", TbMathArgumentType.MESSAGE_BODY, "${key2}"),
|
||||||
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")
|
new TbMathArgument("b", TbMathArgumentType.MESSAGE_BODY, "$[key3]")
|
||||||
);
|
);
|
||||||
|
|
||||||
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString());
|
TbMsgMetaData metaData = new TbMsgMetaData();
|
||||||
|
metaData.putValue("key1", "firstMsgResult");
|
||||||
|
metaData.putValue("key2", "argumentA");
|
||||||
|
ObjectNode msgNode = JacksonUtil.newObjectNode()
|
||||||
|
.put("key3", "argumentB").put("argumentA", 2).put("argumentB", 2);
|
||||||
|
TbMsg msg = TbMsg.newMsg("TEST", originator, metaData, msgNode.toString());
|
||||||
|
|
||||||
node.onMsg(ctx, msg);
|
node.onMsg(ctx, msg);
|
||||||
|
|
||||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
ConcurrentMap<EntityId, Semaphore> semaphores = (ConcurrentMap<EntityId, Semaphore>) ReflectionTestUtils.getField(node, "semaphores");
|
||||||
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
Assert.assertNotNull(semaphores);
|
||||||
|
Semaphore originatorSemaphore = semaphores.get(originator);
|
||||||
|
Assert.assertNotNull(originatorSemaphore);
|
||||||
|
|
||||||
TbMsg resultMsg = msgCaptor.getValue();
|
metaData.putValue("key1", "secondMsgResult");
|
||||||
Assert.assertNotNull(resultMsg);
|
metaData.putValue("key2", "argumentC");
|
||||||
Assert.assertNotNull(resultMsg.getData());
|
msgNode = JacksonUtil.newObjectNode()
|
||||||
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
|
.put("key3", "argumentD").put("argumentC", 4).put("argumentD", 3);
|
||||||
Assert.assertTrue(resultJson.has("result"));
|
msg = TbMsg.newMsg("TEST", originator, metaData, msgNode.toString());
|
||||||
Assert.assertEquals(10, resultJson.get("result").asInt());
|
|
||||||
|
node.onMsg(ctx, msg);
|
||||||
|
|
||||||
|
Awaitility.await("Semaphore released").atMost(5, TimeUnit.SECONDS).until(semaphores.get(originator)::tryAcquire);
|
||||||
|
|
||||||
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
||||||
|
Mockito.verify(ctx, Mockito.times(2)).tellSuccess(msgCaptor.capture());
|
||||||
|
|
||||||
|
List<TbMsg> resultMsgs = msgCaptor.getAllValues();
|
||||||
|
Assert.assertFalse(resultMsgs.isEmpty());
|
||||||
|
Assert.assertEquals(2, resultMsgs.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < resultMsgs.size(); i++) {
|
||||||
|
TbMsg outMsg = resultMsgs.get(i);
|
||||||
|
Assert.assertNotNull(outMsg);
|
||||||
|
Assert.assertNotNull(outMsg.getData());
|
||||||
|
var resultJson = JacksonUtil.toJsonNode(outMsg.getData());
|
||||||
|
String resultKey = i == 0 ? "firstMsgResult" : "secondMsgResult";
|
||||||
|
Assert.assertTrue(resultJson.has(resultKey));
|
||||||
|
Assert.assertEquals(i == 0 ? 10 : 17, resultJson.get(resultKey).asInt());
|
||||||
|
}
|
||||||
|
semaphores.remove(originator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user