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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TbMathArgumentValue fromMessageBody(TbMathArgument arg, Optional<ObjectNode> jsonNodeOpt) {
 | 
			
		||||
        String key = arg.getKey();
 | 
			
		||||
    public static TbMathArgumentValue fromMessageBody(TbMathArgument arg, String argKey, Optional<ObjectNode> jsonNodeOpt) {
 | 
			
		||||
        Double defaultValue = arg.getDefaultValue();
 | 
			
		||||
        if (jsonNodeOpt.isEmpty()) {
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message body is empty!");
 | 
			
		||||
        }
 | 
			
		||||
        var json = jsonNodeOpt.get();
 | 
			
		||||
        if (!json.has(key)) {
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message body has no '" + key + "'!");
 | 
			
		||||
        if (!json.has(argKey)) {
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message body has no '" + argKey + "'!");
 | 
			
		||||
        }
 | 
			
		||||
        JsonNode valueNode = json.get(key);
 | 
			
		||||
        JsonNode valueNode = json.get(argKey);
 | 
			
		||||
        if (valueNode.isNull()) {
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message body has null '" + key + "'!");
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message body has null '" + argKey + "'!");
 | 
			
		||||
        }
 | 
			
		||||
        double value;
 | 
			
		||||
        if (valueNode.isNumber()) {
 | 
			
		||||
@ -69,7 +68,7 @@ public class TbMathArgumentValue {
 | 
			
		||||
                    throw new RuntimeException("Can't convert value '" + valueNode.asText() + "' to double!");
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                return defaultOrThrow(defaultValue, "Message value is empty for '" + key + "'!");
 | 
			
		||||
                return defaultOrThrow(defaultValue, "Message value is empty for '" + argKey + "'!");
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new RuntimeException("Can't convert value '" + valueNode.toString() + "' to double!");
 | 
			
		||||
@ -77,15 +76,14 @@ public class TbMathArgumentValue {
 | 
			
		||||
        return new TbMathArgumentValue(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static TbMathArgumentValue fromMessageMetadata(TbMathArgument arg, TbMsgMetaData metaData) {
 | 
			
		||||
        String key = arg.getKey();
 | 
			
		||||
    public static TbMathArgumentValue fromMessageMetadata(TbMathArgument arg, String argKey, TbMsgMetaData metaData) {
 | 
			
		||||
        Double defaultValue = arg.getDefaultValue();
 | 
			
		||||
        if (metaData == null) {
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message metadata is empty!");
 | 
			
		||||
        }
 | 
			
		||||
        var value = metaData.getValue(key);
 | 
			
		||||
        var value = metaData.getValue(argKey);
 | 
			
		||||
        if (StringUtils.isEmpty(value)) {
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message metadata has no '" + key + "'!");
 | 
			
		||||
            return defaultOrThrow(defaultValue, "Message metadata has no '" + argKey + "'!");
 | 
			
		||||
        }
 | 
			
		||||
        return fromString(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -51,6 +51,8 @@ import java.util.function.BiFunction;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import static org.thingsboard.rule.engine.math.TbMathArgumentType.CONSTANT;
 | 
			
		||||
 | 
			
		||||
@SuppressWarnings("UnstableApiUsage")
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RuleNode(
 | 
			
		||||
@ -121,7 +123,7 @@ public class TbMathNode implements TbNode {
 | 
			
		||||
            var argumentValues = Futures.allAsList(arguments.stream()
 | 
			
		||||
                    .map(arg -> resolveArguments(ctx, msg, msgBodyOpt, arg)).collect(Collectors.toList()));
 | 
			
		||||
            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 -> {
 | 
			
		||||
                try {
 | 
			
		||||
                    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) {
 | 
			
		||||
        TbMathResult mathResultDef = config.getResult();
 | 
			
		||||
        String mathResultKey = getKeyFromTemplate(msg, mathResultDef.getType(), mathResultDef.getKey());
 | 
			
		||||
        switch (mathResultDef.getType()) {
 | 
			
		||||
            case MESSAGE_BODY:
 | 
			
		||||
                return Futures.immediateFuture(addToBody(msg, mathResultDef, msgBodyOpt, result));
 | 
			
		||||
                return Futures.immediateFuture(addToBody(msg, mathResultDef, mathResultKey, msgBodyOpt, result));
 | 
			
		||||
            case MESSAGE_METADATA:
 | 
			
		||||
                return Futures.immediateFuture(addToMeta(msg, mathResultDef, result));
 | 
			
		||||
                return Futures.immediateFuture(addToMeta(msg, mathResultDef, mathResultKey, result));
 | 
			
		||||
            case ATTRIBUTE:
 | 
			
		||||
                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:
 | 
			
		||||
                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:
 | 
			
		||||
                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) {
 | 
			
		||||
        String attributeScope = getAttributeScope(mathResultDef.getAttributeScope());
 | 
			
		||||
        if (isIntegerResult(mathResultDef, config.getOperation())) {
 | 
			
		||||
            var value = toIntValue(mathResultDef, result);
 | 
			
		||||
            var value = toIntValue(result);
 | 
			
		||||
            return ctx.getTelemetryService().saveAttrAndNotify(
 | 
			
		||||
                    ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value);
 | 
			
		||||
        } else {
 | 
			
		||||
@ -194,7 +197,7 @@ public class TbMathNode implements TbNode {
 | 
			
		||||
        return function.isIntegerResult() || mathResultDef.getResultValuePrecision() == 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long toIntValue(TbMathResult mathResultDef, double value) {
 | 
			
		||||
    private long toIntValue(double value) {
 | 
			
		||||
        return (long) value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -217,38 +220,38 @@ public class TbMathNode implements TbNode {
 | 
			
		||||
        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;
 | 
			
		||||
        if (mathResultDef.isAddToBody()) {
 | 
			
		||||
            tmpMsg = addToBody(tmpMsg, mathResultDef, msgBodyOpt, result);
 | 
			
		||||
            tmpMsg = addToBody(tmpMsg, mathResultDef, mathResultKey, msgBodyOpt, result);
 | 
			
		||||
        }
 | 
			
		||||
        if (mathResultDef.isAddToMetadata()) {
 | 
			
		||||
            tmpMsg = addToMeta(tmpMsg, mathResultDef, result);
 | 
			
		||||
            tmpMsg = addToMeta(tmpMsg, mathResultDef, mathResultKey, result);
 | 
			
		||||
        }
 | 
			
		||||
        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();
 | 
			
		||||
        if (isIntegerResult(mathResultDef, config.getOperation())) {
 | 
			
		||||
            body.put(mathResultDef.getKey(), toIntValue(mathResultDef, result));
 | 
			
		||||
            body.put(mathResultKey, toIntValue(result));
 | 
			
		||||
        } else {
 | 
			
		||||
            body.put(mathResultDef.getKey(), toDoubleValue(mathResultDef, result));
 | 
			
		||||
            body.put(mathResultKey, toDoubleValue(mathResultDef, result));
 | 
			
		||||
        }
 | 
			
		||||
        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();
 | 
			
		||||
        if (isIntegerResult(mathResultDef, config.getOperation())) {
 | 
			
		||||
            md.putValue(mathResultDef.getKey(), Long.toString(toIntValue(mathResultDef, result)));
 | 
			
		||||
            md.putValue(mathResultKey, Long.toString(toIntValue(result)));
 | 
			
		||||
        } else {
 | 
			
		||||
            md.putValue(mathResultDef.getKey(), Double.toString(toDoubleValue(mathResultDef, result)));
 | 
			
		||||
            md.putValue(mathResultKey, Double.toString(toDoubleValue(mathResultDef, result)));
 | 
			
		||||
        }
 | 
			
		||||
        return TbMsg.transformMsg(msg, md);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private double calculateResult(TbContext ctx, TbMsg msg, List<TbMathArgumentValue> args) {
 | 
			
		||||
    private double calculateResult(List<TbMathArgumentValue> args) {
 | 
			
		||||
        switch (config.getOperation()) {
 | 
			
		||||
            case ADD:
 | 
			
		||||
                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) {
 | 
			
		||||
        String argKey = getKeyFromTemplate(msg, arg.getType(), arg.getKey());
 | 
			
		||||
        switch (arg.getType()) {
 | 
			
		||||
            case CONSTANT:
 | 
			
		||||
                return Futures.immediateFuture(TbMathArgumentValue.constant(arg));
 | 
			
		||||
            case MESSAGE_BODY:
 | 
			
		||||
                return Futures.immediateFuture(TbMathArgumentValue.fromMessageBody(arg, msgBodyOpt));
 | 
			
		||||
                return Futures.immediateFuture(TbMathArgumentValue.fromMessageBody(arg, argKey, msgBodyOpt));
 | 
			
		||||
            case MESSAGE_METADATA:
 | 
			
		||||
                return Futures.immediateFuture(TbMathArgumentValue.fromMessageMetadata(arg, msg.getMetaData()));
 | 
			
		||||
                return Futures.immediateFuture(TbMathArgumentValue.fromMessageMetadata(arg, argKey, msg.getMetaData()));
 | 
			
		||||
            case ATTRIBUTE:
 | 
			
		||||
                String scope = getAttributeScope(arg.getAttributeScope());
 | 
			
		||||
                return Futures.transform(ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), scope, arg.getKey()),
 | 
			
		||||
                        opt -> getTbMathArgumentValue(arg, opt, "Attribute: " + arg.getKey() + " with scope: " + scope + " not found for entity: " + msg.getOriginator())
 | 
			
		||||
                return Futures.transform(ctx.getAttributesService().find(ctx.getTenantId(), msg.getOriginator(), scope, argKey),
 | 
			
		||||
                        opt -> getTbMathArgumentValue(arg, opt, "Attribute: " + argKey + " with scope: " + scope + " not found for entity: " + msg.getOriginator())
 | 
			
		||||
                        , MoreExecutors.directExecutor());
 | 
			
		||||
            case TIME_SERIES:
 | 
			
		||||
                return Futures.transform(ctx.getTimeseriesService().findLatest(ctx.getTenantId(), msg.getOriginator(), arg.getKey()),
 | 
			
		||||
                        opt -> getTbMathArgumentValue(arg, opt, "Time-series: " + arg.getKey() + " not found for entity: " + msg.getOriginator())
 | 
			
		||||
                return Futures.transform(ctx.getTimeseriesService().findLatest(ctx.getTenantId(), msg.getOriginator(), argKey),
 | 
			
		||||
                        opt -> getTbMathArgumentValue(arg, opt, "Time-series: " + argKey + " not found for entity: " + msg.getOriginator())
 | 
			
		||||
                        , MoreExecutors.directExecutor());
 | 
			
		||||
            default:
 | 
			
		||||
                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) {
 | 
			
		||||
        return StringUtils.isEmpty(attrScope) ? DataConstants.SERVER_SCOPE : attrScope;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,7 @@ public class TbMathArgumentValueTest {
 | 
			
		||||
    public void test_fromMessageBody_then_defaultValue() {
 | 
			
		||||
        TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,7 @@ public class TbMathArgumentValueTest {
 | 
			
		||||
    public void test_fromMessageBody_then_emptyBody() {
 | 
			
		||||
        TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey");
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
@ -47,7 +47,7 @@ public class TbMathArgumentValueTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    public void test_fromMessageBody_then_noKey() {
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -58,12 +58,12 @@ public class TbMathArgumentValueTest {
 | 
			
		||||
        msgData.putNull("TestKey");
 | 
			
		||||
 | 
			
		||||
        //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());
 | 
			
		||||
 | 
			
		||||
        //empty value
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -74,26 +74,26 @@ public class TbMathArgumentValueTest {
 | 
			
		||||
        msgData.put("TestKey", "Test");
 | 
			
		||||
 | 
			
		||||
        //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());
 | 
			
		||||
 | 
			
		||||
        //object value
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void test_fromMessageMetadata_then_noKey() {
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void test_fromMessageMetadata_then_valueEmpty() {
 | 
			
		||||
        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());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,10 @@
 | 
			
		||||
package org.thingsboard.rule.engine.math;
 | 
			
		||||
 | 
			
		||||
import com.datastax.oss.driver.api.core.uuid.Uuids;
 | 
			
		||||
import com.fasterxml.jackson.databind.node.ObjectNode;
 | 
			
		||||
import com.google.common.util.concurrent.Futures;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.awaitility.Awaitility;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Assert;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
@ -26,6 +29,7 @@ import org.mockito.ArgumentCaptor;
 | 
			
		||||
import org.mockito.Mock;
 | 
			
		||||
import org.mockito.Mockito;
 | 
			
		||||
import org.mockito.junit.MockitoJUnitRunner;
 | 
			
		||||
import org.springframework.test.util.ReflectionTestUtils;
 | 
			
		||||
import org.thingsboard.common.util.AbstractListeningExecutor;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
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 java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
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.mockito.ArgumentMatchers.any;
 | 
			
		||||
@ -57,6 +65,7 @@ import static org.mockito.Mockito.lenient;
 | 
			
		||||
import static org.mockito.Mockito.times;
 | 
			
		||||
import static org.mockito.Mockito.verify;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@RunWith(MockitoJUnitRunner.class)
 | 
			
		||||
public class TbMathNodeTest {
 | 
			
		||||
 | 
			
		||||
@ -130,24 +139,52 @@ public class TbMathNodeTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testExp4j() {
 | 
			
		||||
        var node = initNodeWithCustomFunction("2a+3b",
 | 
			
		||||
                new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
 | 
			
		||||
                new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
 | 
			
		||||
                new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")
 | 
			
		||||
                new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "${key1}", 2, false, false, null),
 | 
			
		||||
                new TbMathArgument("a", TbMathArgumentType.MESSAGE_BODY, "${key2}"),
 | 
			
		||||
                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);
 | 
			
		||||
 | 
			
		||||
        ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
 | 
			
		||||
        Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
 | 
			
		||||
        ConcurrentMap<EntityId, Semaphore> semaphores = (ConcurrentMap<EntityId, Semaphore>) ReflectionTestUtils.getField(node, "semaphores");
 | 
			
		||||
        Assert.assertNotNull(semaphores);
 | 
			
		||||
        Semaphore originatorSemaphore = semaphores.get(originator);
 | 
			
		||||
        Assert.assertNotNull(originatorSemaphore);
 | 
			
		||||
 | 
			
		||||
        TbMsg resultMsg = msgCaptor.getValue();
 | 
			
		||||
        Assert.assertNotNull(resultMsg);
 | 
			
		||||
        Assert.assertNotNull(resultMsg.getData());
 | 
			
		||||
        var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
 | 
			
		||||
        Assert.assertTrue(resultJson.has("result"));
 | 
			
		||||
        Assert.assertEquals(10, resultJson.get("result").asInt());
 | 
			
		||||
        metaData.putValue("key1", "secondMsgResult");
 | 
			
		||||
        metaData.putValue("key2", "argumentC");
 | 
			
		||||
        msgNode = JacksonUtil.newObjectNode()
 | 
			
		||||
                .put("key3", "argumentD").put("argumentC", 4).put("argumentD", 3);
 | 
			
		||||
        msg = TbMsg.newMsg("TEST", originator, metaData, msgNode.toString());
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user