diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java index 8804320295..475488847a 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java @@ -16,15 +16,20 @@ package org.thingsboard.rule.engine.api.util; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -34,8 +39,11 @@ public class TbNodeUtils { private static final ObjectMapper mapper = new ObjectMapper(); - private static final String VARIABLE_TEMPLATE = "${%s}"; + private static final String METADATA_VARIABLE_TEMPLATE = "${%s}"; + private static final Pattern DATA_PATTERN = Pattern.compile("(\\$\\[)(.*?)(\\])"); + + private static final String DATA_VARIABLE_TEMPLATE = "$[%s]"; public static T convert(TbNodeConfiguration configuration, Class clazz) throws TbNodeException { try { @@ -45,6 +53,44 @@ public class TbNodeUtils { } } + public static List processPatterns(List patterns, TbMsg tbMsg) { + if (!CollectionUtils.isEmpty(patterns)) { + return patterns.stream().map(p -> processPattern(p, tbMsg)).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static String processPattern(String pattern, TbMsg tbMsg) { + try { + String result = processPattern(pattern, tbMsg.getMetaData()); + JsonNode json = mapper.readTree(tbMsg.getData()); + if (json.isObject()) { + Matcher matcher = DATA_PATTERN.matcher(result); + + while (matcher.find()) { + String group = matcher.group(2); + String[] keys = group.split("\\."); + JsonNode jsonNode = json; + for (String key : keys) { + if (StringUtils.isNotEmpty(key) && jsonNode != null) { + jsonNode = jsonNode.get(key); + } else { + jsonNode = null; + break; + } + } + + if (jsonNode != null && !jsonNode.isObject() && !jsonNode.isArray()) { + result = result.replace(String.format(DATA_VARIABLE_TEMPLATE, group), jsonNode.asText()); + } + } + } + return result; + } catch (Exception e) { + throw new RuntimeException("Failed to process pattern!", e); + } + } + public static List processPatterns(List patterns, TbMsgMetaData metaData) { if (!CollectionUtils.isEmpty(patterns)) { return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList()); @@ -53,15 +99,15 @@ public class TbNodeUtils { } public static String processPattern(String pattern, TbMsgMetaData metaData) { - String result = new String(pattern); - for (Map.Entry keyVal : metaData.values().entrySet()) { + String result = pattern; + for (Map.Entry keyVal : metaData.values().entrySet()) { result = processVar(result, keyVal.getKey(), keyVal.getValue()); } return result; } private static String processVar(String pattern, String key, String val) { - String varPattern = String.format(VARIABLE_TEMPLATE, key); + String varPattern = String.format(METADATA_VARIABLE_TEMPLATE, key); return pattern.replace(varPattern, val); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java index 482e5261f0..b47235572b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java @@ -79,7 +79,7 @@ public abstract class TbAbstractCustomerActionNode getCustomer(TbContext ctx, TbMsg msg) { - String customerTitle = TbNodeUtils.processPattern(this.config.getCustomerNamePattern(), msg.getMetaData()); + String customerTitle = TbNodeUtils.processPattern(this.config.getCustomerNamePattern(), msg); CustomerKey key = new CustomerKey(customerTitle); return ctx.getDbCallbackExecutor().executeAsync(() -> { Optional customerId = customerIdCache.get(key); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index 66a8f28e87..307a863938 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -140,7 +140,7 @@ public abstract class TbAbstractRelationActionNode processAlarm(TbContext ctx, TbMsg msg) { - String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg.getMetaData()); + String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg); ListenableFuture alarmFuture; if (msg.getOriginator().getEntityType().equals(EntityType.ALARM)) { alarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), new AlarmId(msg.getOriginator().getId())); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index dcae930fbd..c941d6ca92 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -70,7 +70,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode messageAttributes = new HashMap<>(); this.config.getMessageAttributes().forEach((k,v) -> { - String name = TbNodeUtils.processPattern(k, msg.getMetaData()); - String val = TbNodeUtils.processPattern(v, msg.getMetaData()); + String name = TbNodeUtils.processPattern(k, msg); + String val = TbNodeUtils.processPattern(v, msg); messageAttributes.put(name, new MessageAttributeValue().withDataType("String").withStringValue(val)); }); sendMsgRequest.setMessageAttributes(messageAttributes); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index d72c56981e..9192f0a55f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -84,7 +84,7 @@ public class TbMsgDelayNode implements TbNode { int periodInSeconds; if (config.isUseMetadataPeriodInSecondsPatterns()) { if (isParsable(msg, config.getPeriodInSecondsPattern())) { - periodInSeconds = Integer.parseInt(TbNodeUtils.processPattern(config.getPeriodInSecondsPattern(), msg.getMetaData())); + periodInSeconds = Integer.parseInt(TbNodeUtils.processPattern(config.getPeriodInSecondsPattern(), msg)); } else { throw new RuntimeException("Can't parse period in seconds from metadata using pattern: " + config.getPeriodInSecondsPattern()); } @@ -95,7 +95,7 @@ public class TbMsgDelayNode implements TbNode { } private boolean isParsable(TbMsg msg, String pattern) { - return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg.getMetaData())); + return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg)); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java index c04de9e070..17c49adfc7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java @@ -91,8 +91,8 @@ public class TbPubSubNode implements TbNode { PubsubMessage.Builder pubsubMessageBuilder = PubsubMessage.newBuilder(); pubsubMessageBuilder.setData(data); this.config.getMessageAttributes().forEach((k, v) -> { - String name = TbNodeUtils.processPattern(k, msg.getMetaData()); - String val = TbNodeUtils.processPattern(v, msg.getMetaData()); + String name = TbNodeUtils.processPattern(k, msg); + String val = TbNodeUtils.processPattern(v, msg); pubsubMessageBuilder.putAttributes(name, val); }); ApiFuture messageIdFuture = this.pubSubClient.publish(pubsubMessageBuilder.build()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 89cc747811..acc86b1e62 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -94,7 +94,7 @@ public class TbKafkaNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg.getMetaData()); + String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg); try { ctx.getExternalCallExecutor().executeAsync(() -> { publish(ctx, msg, topic); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index 854c89e720..8372ebdd10 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -72,18 +72,18 @@ public class TbMsgToEmailNode implements TbNode { private EmailPojo convert(TbMsg msg) throws IOException { EmailPojo.EmailPojoBuilder builder = EmailPojo.builder(); - builder.from(fromTemplate(this.config.getFromTemplate(), msg.getMetaData())); - builder.to(fromTemplate(this.config.getToTemplate(), msg.getMetaData())); - builder.cc(fromTemplate(this.config.getCcTemplate(), msg.getMetaData())); - builder.bcc(fromTemplate(this.config.getBccTemplate(), msg.getMetaData())); - builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg.getMetaData())); - builder.body(fromTemplate(this.config.getBodyTemplate(), msg.getMetaData())); + builder.from(fromTemplate(this.config.getFromTemplate(), msg)); + builder.to(fromTemplate(this.config.getToTemplate(), msg)); + builder.cc(fromTemplate(this.config.getCcTemplate(), msg)); + builder.bcc(fromTemplate(this.config.getBccTemplate(), msg)); + builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg)); + builder.body(fromTemplate(this.config.getBodyTemplate(), msg)); return builder.build(); } - private String fromTemplate(String template, TbMsgMetaData metaData) { + private String fromTemplate(String template, TbMsg msg) { if (!StringUtils.isEmpty(template)) { - return TbNodeUtils.processPattern(template, metaData); + return TbNodeUtils.processPattern(template, msg); } else { return null; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index 5788b2e086..1b0f4c95ef 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -92,10 +92,10 @@ public abstract class TbAbstractGetAttributesNode> failuresMap = new ConcurrentHashMap<>(); ListenableFuture> allFutures = Futures.allAsList( - putLatestTelemetry(ctx, entityId, msg, LATEST_TS, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg.getMetaData()), failuresMap), - putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg.getMetaData()), failuresMap, "cs_"), - putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg.getMetaData()), failuresMap, "shared_"), - putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg.getMetaData()), failuresMap, "ss_") + putLatestTelemetry(ctx, entityId, msg, LATEST_TS, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg), failuresMap), + putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg), failuresMap, "cs_"), + putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg), failuresMap, "shared_"), + putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresMap, "ss_") ); withCallback(allFutures, i -> { if (!failuresMap.isEmpty()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index bb5ffee78d..1174ad6c0c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -103,7 +103,7 @@ public class TbGetTelemetryNode implements TbNode { if (config.isUseMetadataIntervalPatterns()) { checkMetadataKeyPatterns(msg); } - List keys = TbNodeUtils.processPatterns(tsKeyNames, msg.getMetaData()); + List keys = TbNodeUtils.processPatterns(tsKeyNames, msg); ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(msg, keys)); DonAsynchron.withCallback(list, data -> { process(data, msg, keys); @@ -197,10 +197,10 @@ public class TbGetTelemetryNode implements TbNode { Interval interval = new Interval(); if (config.isUseMetadataIntervalPatterns()) { if (isParsable(msg, config.getStartIntervalPattern())) { - interval.setStartTs(Long.parseLong(TbNodeUtils.processPattern(config.getStartIntervalPattern(), msg.getMetaData()))); + interval.setStartTs(Long.parseLong(TbNodeUtils.processPattern(config.getStartIntervalPattern(), msg))); } if (isParsable(msg, config.getEndIntervalPattern())) { - interval.setEndTs(Long.parseLong(TbNodeUtils.processPattern(config.getEndIntervalPattern(), msg.getMetaData()))); + interval.setEndTs(Long.parseLong(TbNodeUtils.processPattern(config.getEndIntervalPattern(), msg))); } } else { long ts = System.currentTimeMillis(); @@ -211,7 +211,7 @@ public class TbGetTelemetryNode implements TbNode { } private boolean isParsable(TbMsg msg, String pattern) { - return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg.getMetaData())); + return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg)); } private void checkMetadataKeyPatterns(TbMsg msg) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 2ecf770e19..4c732e0d97 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -76,7 +76,7 @@ public class TbMqttNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg.getMetaData()); + String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg); this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) .addListener(future -> { if (future.isSuccess()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index 898334781e..f481c875a9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -90,11 +90,11 @@ public class TbRabbitMqNode implements TbNode { private TbMsg publishMessage(TbContext ctx, TbMsg msg) throws Exception { String exchangeName = ""; if (!StringUtils.isEmpty(this.config.getExchangeNamePattern())) { - exchangeName = TbNodeUtils.processPattern(this.config.getExchangeNamePattern(), msg.getMetaData()); + exchangeName = TbNodeUtils.processPattern(this.config.getExchangeNamePattern(), msg); } String routingKey = ""; if (!StringUtils.isEmpty(this.config.getRoutingKeyPattern())) { - routingKey = TbNodeUtils.processPattern(this.config.getRoutingKeyPattern(), msg.getMetaData()); + routingKey = TbNodeUtils.processPattern(this.config.getRoutingKeyPattern(), msg); } AMQP.BasicProperties properties = null; if (!StringUtils.isEmpty(this.config.getMessageProperties())) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 17dc6b5bce..bbe1bccd0d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -172,8 +172,8 @@ public class TbHttpClient { } public void processMessage(TbContext ctx, TbMsg msg) { - String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg.getMetaData()); - HttpHeaders headers = prepareHeaders(msg.getMetaData()); + String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); + HttpHeaders headers = prepareHeaders(msg); HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); HttpEntity entity = new HttpEntity<>(msg.getData(), headers); @@ -232,9 +232,9 @@ public class TbHttpClient { return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); } - private HttpHeaders prepareHeaders(TbMsgMetaData metaData) { + private HttpHeaders prepareHeaders(TbMsg msg) { HttpHeaders headers = new HttpHeaders(); - config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData))); + config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, msg), TbNodeUtils.processPattern(v, msg))); ClientCredentials credentials = config.getCredentials(); if (CredentialsType.BASIC == credentials.getType()) { BasicCredentials basicCredentials = (BasicCredentials) credentials; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java index 0061ff1cf4..df75afe086 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/sms/TbSendSmsNode.java @@ -72,8 +72,8 @@ public class TbSendSmsNode implements TbNode { } private void sendSms(TbContext ctx, TbMsg msg) throws Exception { - String numbersTo = TbNodeUtils.processPattern(this.config.getNumbersToTemplate(), msg.getMetaData()); - String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg.getMetaData()); + String numbersTo = TbNodeUtils.processPattern(this.config.getNumbersToTemplate(), msg); + String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg); String[] numbersToList = numbersTo.split(","); if (this.config.isUseSystemSmsSettings()) { ctx.getSmsService().sendSms(ctx.getTenantId(), numbersToList, message);