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.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
|
||||||
* Created by ashvayka on 13.01.18.
|
|
||||||
*/
|
|
||||||
@Data
|
@Data
|
||||||
public final class TbMsgMetaData implements Serializable {
|
public final class TbMsgMetaData implements Serializable {
|
||||||
|
|
||||||
@ -34,7 +31,7 @@ public final class TbMsgMetaData implements Serializable {
|
|||||||
private final Map<String, String> data;
|
private final Map<String, String> data;
|
||||||
|
|
||||||
public TbMsgMetaData() {
|
public TbMsgMetaData() {
|
||||||
this.data = new ConcurrentHashMap<>();
|
data = new ConcurrentHashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TbMsgMetaData(Map<String, String> data) {
|
public TbMsgMetaData(Map<String, String> data) {
|
||||||
@ -46,24 +43,29 @@ public final class TbMsgMetaData implements Serializable {
|
|||||||
* Internal constructor to create immutable TbMsgMetaData.EMPTY
|
* Internal constructor to create immutable TbMsgMetaData.EMPTY
|
||||||
* */
|
* */
|
||||||
private TbMsgMetaData(int ignored) {
|
private TbMsgMetaData(int ignored) {
|
||||||
this.data = Collections.emptyMap();
|
data = Collections.emptyMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getValue(String key) {
|
public String getValue(String key) {
|
||||||
return this.data.get(key);
|
return data.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putValue(String key, String value) {
|
public void putValue(String key, String value) {
|
||||||
if (key != null && value != null) {
|
if (key != null && value != null) {
|
||||||
this.data.put(key, value);
|
data.put(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> values() {
|
public Map<String, String> values() {
|
||||||
return new HashMap<>(this.data);
|
return new HashMap<>(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TbMsgMetaData copy() {
|
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;
|
package org.thingsboard.rule.engine.api.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
||||||
import org.thingsboard.rule.engine.api.TbNodeException;
|
import org.thingsboard.rule.engine.api.TbNodeException;
|
||||||
import org.thingsboard.server.common.data.StringUtils;
|
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.TbMsg;
|
||||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||||
|
|
||||||
@ -29,15 +29,18 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
public final class TbNodeUtils {
|
||||||
* Created by ashvayka on 19.01.18.
|
|
||||||
*/
|
private TbNodeUtils() {
|
||||||
public class TbNodeUtils {
|
throw new IllegalStateException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
private static final Pattern DATA_PATTERN = Pattern.compile("(\\$\\[)(.*?)(])");
|
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 {
|
public static <T> T convert(TbNodeConfiguration configuration, Class<T> clazz) throws TbNodeException {
|
||||||
try {
|
try {
|
||||||
return JacksonUtil.treeToValue(configuration.getData(), clazz);
|
return JacksonUtil.treeToValue(configuration.getData(), clazz);
|
||||||
@ -47,16 +50,19 @@ public class TbNodeUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static List<String> processPatterns(List<String> patterns, TbMsg tbMsg) {
|
public static List<String> processPatterns(List<String> patterns, TbMsg tbMsg) {
|
||||||
if (!CollectionUtils.isEmpty(patterns)) {
|
if (CollectionsUtil.isEmpty(patterns)) {
|
||||||
return patterns.stream().map(p -> processPattern(p, tbMsg)).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
return patterns.stream().map(p -> processPattern(p, tbMsg)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
public static String processPattern(String pattern, TbMsg tbMsg) {
|
public static String processPattern(String pattern, TbMsg tbMsg) {
|
||||||
try {
|
try {
|
||||||
String result = processPattern(pattern, tbMsg.getMetaData());
|
String result = processPattern(pattern, tbMsg.getMetaData());
|
||||||
JsonNode json = JacksonUtil.toJsonNode(tbMsg.getData());
|
JsonNode json = JacksonUtil.toJsonNode(tbMsg.getData());
|
||||||
|
|
||||||
|
result = result.replace(ALL_DATA_TEMPLATE, JacksonUtil.toString(json));
|
||||||
|
|
||||||
if (json.isObject()) {
|
if (json.isObject()) {
|
||||||
Matcher matcher = DATA_PATTERN.matcher(result);
|
Matcher matcher = DATA_PATTERN.matcher(result);
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
@ -64,7 +70,7 @@ public class TbNodeUtils {
|
|||||||
String[] keys = group.split("\\.");
|
String[] keys = group.split("\\.");
|
||||||
JsonNode jsonNode = json;
|
JsonNode jsonNode = json;
|
||||||
for (String key : keys) {
|
for (String key : keys) {
|
||||||
if (!StringUtils.isEmpty(key) && jsonNode != null) {
|
if (StringUtils.isNotEmpty(key) && jsonNode != null) {
|
||||||
jsonNode = jsonNode.get(key);
|
jsonNode = jsonNode.get(key);
|
||||||
} else {
|
} else {
|
||||||
jsonNode = null;
|
jsonNode = null;
|
||||||
@ -83,15 +89,9 @@ public class TbNodeUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(since = "3.6.1", forRemoval = true)
|
private static String processPattern(String pattern, TbMsgMetaData metaData) {
|
||||||
public static List<String> processPatterns(List<String> patterns, TbMsgMetaData metaData) {
|
String replacement = metaData.isEmpty() ? "{}" : JacksonUtil.toString(metaData.getData());
|
||||||
if (!CollectionUtils.isEmpty(patterns)) {
|
pattern = pattern.replace(ALL_METADATA_TEMPLATE, replacement);
|
||||||
return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String processPattern(String pattern, TbMsgMetaData metaData) {
|
|
||||||
return processTemplate(pattern, metaData.values());
|
return processTemplate(pattern, metaData.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,10 +108,11 @@ public class TbNodeUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static String formatDataVarTemplate(String key) {
|
static String formatDataVarTemplate(String key) {
|
||||||
return "$[" + key + ']';
|
return "$[" + key + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
static String formatMetadataVarTemplate(String 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.TbMsg;
|
||||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
@ -167,4 +169,160 @@ public class TbNodeUtilsTest {
|
|||||||
assertThat(TbNodeUtils.formatMetadataVarTemplate(null), is("${null}"));
|
assertThat(TbNodeUtils.formatMetadataVarTemplate(null), is("${null}"));
|
||||||
assertThat(TbNodeUtils.formatMetadataVarTemplate(null), is(String.format(METADATA_VARIABLE_TEMPLATE, (String) 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