AI rule node: add support for all data/metadata patterns in rule nodes
This commit is contained in:
		
							parent
							
								
									9567dd3090
								
							
						
					
					
						commit
						d2d22a44c2
					
				@ -23,9 +23,6 @@ import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by ashvayka on 13.01.18.
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
public final class TbMsgMetaData implements Serializable {
 | 
			
		||||
 | 
			
		||||
@ -34,7 +31,7 @@ public final class TbMsgMetaData implements Serializable {
 | 
			
		||||
    private final Map<String, String> data;
 | 
			
		||||
 | 
			
		||||
    public TbMsgMetaData() {
 | 
			
		||||
        this.data = new ConcurrentHashMap<>();
 | 
			
		||||
        data = new ConcurrentHashMap<>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TbMsgMetaData(Map<String, String> data) {
 | 
			
		||||
@ -46,24 +43,29 @@ public final class TbMsgMetaData implements Serializable {
 | 
			
		||||
     * Internal constructor to create immutable TbMsgMetaData.EMPTY
 | 
			
		||||
     * */
 | 
			
		||||
    private TbMsgMetaData(int ignored) {
 | 
			
		||||
        this.data = Collections.emptyMap();
 | 
			
		||||
        data = Collections.emptyMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getValue(String key) {
 | 
			
		||||
        return this.data.get(key);
 | 
			
		||||
        return data.get(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void putValue(String key, String value) {
 | 
			
		||||
        if (key != null && value != null) {
 | 
			
		||||
            this.data.put(key, value);
 | 
			
		||||
            data.put(key, value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Map<String, String> values() {
 | 
			
		||||
        return new HashMap<>(this.data);
 | 
			
		||||
        return new HashMap<>(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TbMsgMetaData copy() {
 | 
			
		||||
        return new TbMsgMetaData(this.data);
 | 
			
		||||
        return new TbMsgMetaData(data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isEmpty() {
 | 
			
		||||
        return data == null || data.isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,11 +16,11 @@
 | 
			
		||||
package org.thingsboard.rule.engine.api.util;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import org.springframework.util.CollectionUtils;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
 | 
			
		||||
import org.thingsboard.rule.engine.api.TbNodeException;
 | 
			
		||||
import org.thingsboard.server.common.data.StringUtils;
 | 
			
		||||
import org.thingsboard.server.common.data.util.CollectionsUtil;
 | 
			
		||||
import org.thingsboard.server.common.msg.TbMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
 | 
			
		||||
 | 
			
		||||
@ -29,15 +29,18 @@ import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Created by ashvayka on 19.01.18.
 | 
			
		||||
 */
 | 
			
		||||
public class TbNodeUtils {
 | 
			
		||||
public final class TbNodeUtils {
 | 
			
		||||
 | 
			
		||||
    private TbNodeUtils() {
 | 
			
		||||
        throw new IllegalStateException("Utility class");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static final Pattern DATA_PATTERN = Pattern.compile("(\\$\\[)(.*?)(])");
 | 
			
		||||
 | 
			
		||||
    private static final String ALL_DATA_TEMPLATE = "$[*]";
 | 
			
		||||
    private static final String ALL_METADATA_TEMPLATE = "${*}";
 | 
			
		||||
 | 
			
		||||
    public static <T> T convert(TbNodeConfiguration configuration, Class<T> clazz) throws TbNodeException {
 | 
			
		||||
        try {
 | 
			
		||||
            return JacksonUtil.treeToValue(configuration.getData(), clazz);
 | 
			
		||||
@ -47,16 +50,19 @@ public class TbNodeUtils {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static List<String> processPatterns(List<String> patterns, TbMsg tbMsg) {
 | 
			
		||||
        if (!CollectionUtils.isEmpty(patterns)) {
 | 
			
		||||
            return patterns.stream().map(p -> processPattern(p, tbMsg)).collect(Collectors.toList());
 | 
			
		||||
        if (CollectionsUtil.isEmpty(patterns)) {
 | 
			
		||||
            return Collections.emptyList();
 | 
			
		||||
        }
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
        return patterns.stream().map(p -> processPattern(p, tbMsg)).toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String processPattern(String pattern, TbMsg tbMsg) {
 | 
			
		||||
        try {
 | 
			
		||||
            String result = processPattern(pattern, tbMsg.getMetaData());
 | 
			
		||||
            JsonNode json = JacksonUtil.toJsonNode(tbMsg.getData());
 | 
			
		||||
 | 
			
		||||
            result = result.replace(ALL_DATA_TEMPLATE, JacksonUtil.toString(json));
 | 
			
		||||
 | 
			
		||||
            if (json.isObject()) {
 | 
			
		||||
                Matcher matcher = DATA_PATTERN.matcher(result);
 | 
			
		||||
                while (matcher.find()) {
 | 
			
		||||
@ -64,7 +70,7 @@ public class TbNodeUtils {
 | 
			
		||||
                    String[] keys = group.split("\\.");
 | 
			
		||||
                    JsonNode jsonNode = json;
 | 
			
		||||
                    for (String key : keys) {
 | 
			
		||||
                        if (!StringUtils.isEmpty(key) && jsonNode != null) {
 | 
			
		||||
                        if (StringUtils.isNotEmpty(key) && jsonNode != null) {
 | 
			
		||||
                            jsonNode = jsonNode.get(key);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            jsonNode = null;
 | 
			
		||||
@ -83,15 +89,9 @@ public class TbNodeUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated(since = "3.6.1", forRemoval = true)
 | 
			
		||||
    public static List<String> processPatterns(List<String> patterns, TbMsgMetaData metaData) {
 | 
			
		||||
        if (!CollectionUtils.isEmpty(patterns)) {
 | 
			
		||||
            return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList());
 | 
			
		||||
        }
 | 
			
		||||
        return Collections.emptyList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String processPattern(String pattern, TbMsgMetaData metaData) {
 | 
			
		||||
    private static String processPattern(String pattern, TbMsgMetaData metaData) {
 | 
			
		||||
        String replacement = metaData.isEmpty() ? "{}" : JacksonUtil.toString(metaData.getData());
 | 
			
		||||
        pattern = pattern.replace(ALL_METADATA_TEMPLATE, replacement);
 | 
			
		||||
        return processTemplate(pattern, metaData.values());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -108,10 +108,11 @@ public class TbNodeUtils {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static String formatDataVarTemplate(String key) {
 | 
			
		||||
        return "$[" + key + ']';
 | 
			
		||||
        return "$[" + key + "]";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static String formatMetadataVarTemplate(String key) {
 | 
			
		||||
        return "${" + key + '}';
 | 
			
		||||
        return "${" + key + "}";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.msg.TbMsgType;
 | 
			
		||||
import org.thingsboard.server.common.msg.TbMsg;
 | 
			
		||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import static org.hamcrest.CoreMatchers.is;
 | 
			
		||||
import static org.hamcrest.MatcherAssert.assertThat;
 | 
			
		||||
 | 
			
		||||
@ -167,4 +169,160 @@ public class TbNodeUtilsTest {
 | 
			
		||||
        assertThat(TbNodeUtils.formatMetadataVarTemplate(null), is("${null}"));
 | 
			
		||||
        assertThat(TbNodeUtils.formatMetadataVarTemplate(null), is(String.format(METADATA_VARIABLE_TEMPLATE, (String) null)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAllMetadataTemplateReplacement() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "META ${*}";
 | 
			
		||||
        var metadata = new TbMsgMetaData();
 | 
			
		||||
        metadata.putValue("meta_key", "meta_value");
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data(TbMsg.EMPTY_JSON_OBJECT)
 | 
			
		||||
                .metaData(metadata)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "META {\"meta_key\":\"meta_value\"}";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMultipleAllMetadataTemplatesReplacement() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "${*} then again ${*}";
 | 
			
		||||
        var metadata = new TbMsgMetaData();
 | 
			
		||||
        metadata.putValue("meta_key", "meta_value");
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data(TbMsg.EMPTY_JSON_OBJECT)
 | 
			
		||||
                .metaData(metadata)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "{\"meta_key\":\"meta_value\"} then again {\"meta_key\":\"meta_value\"}";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAllDataTemplateReplacement() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "DATA $[*]";
 | 
			
		||||
        var dataJson = JacksonUtil.newObjectNode().put("data_key", "data_value");
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data(JacksonUtil.toString(dataJson))
 | 
			
		||||
                .metaData(TbMsgMetaData.EMPTY)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "DATA {\"data_key\":\"data_value\"}";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMultipleAllDataTemplatesReplacement() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "$[*] then again $[*]";
 | 
			
		||||
        var dataJson = JacksonUtil.newObjectNode().put("data_key", "data_value");
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data(JacksonUtil.toString(dataJson))
 | 
			
		||||
                .metaData(TbMsgMetaData.EMPTY)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "{\"data_key\":\"data_value\"} then again {\"data_key\":\"data_value\"}";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAllDataAndAllMetadataTemplatesSimultaneously() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "META ${*} DATA $[*]";
 | 
			
		||||
 | 
			
		||||
        var metadata = new TbMsgMetaData(Map.of("meta_key", "meta_value"));
 | 
			
		||||
        var dataJson = JacksonUtil.newObjectNode().put("data_key", "data_value");
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data(JacksonUtil.toString(dataJson))
 | 
			
		||||
                .metaData(metadata)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "META {\"meta_key\":\"meta_value\"} DATA {\"data_key\":\"data_value\"}";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAllDataAndAllMetadataTemplatesSimultaneouslyEmpty() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "META ${*} DATA $[*]";
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data(TbMsg.EMPTY_JSON_OBJECT)
 | 
			
		||||
                .metaData(TbMsgMetaData.EMPTY)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "META {} DATA {}";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAllDataTemplateArray() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "DATA $[*]";
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data("[1, \"two\", true]")
 | 
			
		||||
                .metaData(TbMsgMetaData.EMPTY)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "DATA [1,\"two\",true]";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMixedAllDataMetadataAndNormalTemplates() {
 | 
			
		||||
        // GIVEN
 | 
			
		||||
        String pattern = "fullMeta=${*}, singleMeta=${meta_key}, fullData=$[*], singleData=$[data_key]";
 | 
			
		||||
        var metadata = new TbMsgMetaData(Map.of("meta_key", "meta_value"));
 | 
			
		||||
        var dataJson = JacksonUtil.newObjectNode().put("data_key", "data_value");
 | 
			
		||||
 | 
			
		||||
        var msg = TbMsg.newMsg()
 | 
			
		||||
                .data(JacksonUtil.toString(dataJson))
 | 
			
		||||
                .metaData(metadata)
 | 
			
		||||
                .build();
 | 
			
		||||
 | 
			
		||||
        // WHEN
 | 
			
		||||
        String actual = TbNodeUtils.processPattern(pattern, msg);
 | 
			
		||||
 | 
			
		||||
        // THEN
 | 
			
		||||
        String expected = "fullMeta={\"meta_key\":\"meta_value\"}, singleMeta=meta_value, fullData={\"data_key\":\"data_value\"}, singleData=data_value";
 | 
			
		||||
        assertThat(actual, is(expected));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user